narrativetrace 0.4.0__tar.gz → 0.4.2__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.
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/LICENSE +2 -2
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/PKG-INFO +1 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/pyproject.toml +1 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/__init__.py +1 -1
- narrativetrace-0.4.2/src/narrativetrace/_generated.py +70 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/add-narrative-tracing/SKILL.md +40 -7
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-doctor/SKILL.md +2 -2
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-explain/SKILL.md +1 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-feedback/SKILL.md +1 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-verify/SKILL.md +3 -3
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/catalogue.json +1 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/add-narrative-tracing/SKILL.md +40 -7
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-doctor/SKILL.md +3 -3
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-explain/SKILL.md +1 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-feedback/SKILL.md +1 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-verify/SKILL.md +3 -3
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/config.py +4 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/context.py +39 -6
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/environment.py +24 -0
- narrativetrace-0.4.2/src/narrativetrace/exclusions.py +171 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/metadata.py +38 -3
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/redaction.py +65 -11
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/trace_object.py +55 -2
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_doctor_cli_bin.py +1 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_doctor_environment.py +36 -0
- narrativetrace-0.4.2/tests/test_adversarial_openai_plugin_pack.py +130 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context.py +63 -1
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context7_check.py +5 -3
- narrativetrace-0.4.2/tests/test_exclusions.py +285 -0
- narrativetrace-0.4.2/tests/test_exclusions_docs.py +57 -0
- narrativetrace-0.4.2/tests/test_gate_lint_never_trusts_a_cache.py +51 -0
- narrativetrace-0.4.2/tests/test_generated_targets.py +119 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_identity.py +30 -0
- narrativetrace-0.4.2/tests/test_metadata.py +78 -0
- narrativetrace-0.4.2/tests/test_mutation_baseline_selection.py +94 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_notice_render_targets.py +5 -0
- narrativetrace-0.4.2/tests/test_openai_plugin_pack.py +273 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_promotion_render.py +23 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_redaction.py +82 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_skills_render.py +40 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_smoke.py +1 -1
- narrativetrace-0.4.2/tests/test_trace_object_catch_all_warning.py +91 -0
- narrativetrace-0.4.0/tests/test_metadata.py +0 -44
- narrativetrace-0.4.0/tests/test_mutation_baseline_selection.py +0 -57
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/.gitignore +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/README.md +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_boundary.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/add-narrativetrace-clarity/SKILL.md +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-debug/SKILL.md +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/add-narrativetrace-clarity/SKILL.md +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-debug/SKILL.md +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/canonical.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/chapter.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/cli.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/concurrency.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/context_export.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/decorators.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/__init__.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/cli_bin.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/feedback_cli.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/gh_auth_probe.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/escape.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/events.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/export.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/groups.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/identity.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/ids.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/levels.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/logging_bridge.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/loss.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/markers.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/namer.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/national_id_shapes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/nodes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/outcomes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/__init__.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/approval.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/artifact_identity.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/line_diff.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/manifest.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/paths.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/reporter.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/run_identity.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/structural_delta.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/warnings.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/writer.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/__init__.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/bounded_buffer.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/buffered_consumer.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/dual_path.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/event_store.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/perishable.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/subscriber.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/py.typed +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/redacted_paths.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/__init__.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/base.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/camel.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/concurrency.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/frontmatter.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/indented.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/markdown.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/prose.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/scenario.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/scenario_result.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/span_id.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/structural.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/value_delta.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/value_reference.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/rendering.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/secret_value_shapes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/signature.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/span.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/template.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/tree.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/tree_canonical.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/tree_walk.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/values.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/conftest.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_adversarial_feedback_cli.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_built_wheels.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_feedback_arguments.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_feedback_verb.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_installer_arguments.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_installer_verbs.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_gh_auth_probe.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/fixtures/java-negative-expense.nt +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/live_annotation_support.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/logging_wiring.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/stress_support.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_adversarial_skills_render.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_adversarial_vendor_validation.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_adversarial_verify_all_heavy.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_approval.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_artifact_identity.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_async_concurrency_kind.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_async_lifecycle_contract.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_async_snapshot_adoption.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_bench_capture.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_bench_redaction.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_bench_rendering.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_canonical.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_canonical_props.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_capture_identity.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_chapter.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_chapter_props.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_check_no_license_headers.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_cli.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_comment_hygiene.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_concurrency_info.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_config.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_console_delta.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context_export.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context_extra.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context_stress.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_contract_check.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_contract_dispatch_coverage.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_contract_lint.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_coverage_source_completeness.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_decorators.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_deep_fixture_budget.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_distribution_licensing.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_duplication_check.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_duplication_report.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_errors_only_level.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_escape.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_events.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_export.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_feedback_docs.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_framework_table_docs.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_fuzz_report.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_groups.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_identity_conformance.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_ids.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_installer_from_another_package.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_issue_form_publishes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_levels.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_live_snapshot_scope_visibility.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_logging_bridge.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_logging_wiring.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_manifest.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_mutation_accounting.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_mutation_gate.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_mutation_tooling.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_mutation_workers.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_namer.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_national_id_shapes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_nested_chain_adoption.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_no_poison_contract.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_nodes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_outcomes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_output.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_parameter_name_redaction.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_parameter_value_redaction.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_buffered.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_dual_path.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_perishable.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_props.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_stress.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_subscriber.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_platform_pointer_from_another_package.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_platform_table_docs.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_publish_agents_md_section.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_publish_public.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_publish_public_host_venv.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_publish_workflow_packages.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_quality_gate_status.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_redacted_paths.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_redacted_value_containment.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_render.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_render_reads_state.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_render_reentrancy.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_rendering.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_rendering_extra.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_run_identity.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_run_security_tool.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_scenario_result.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_secret_value_shapes.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_signature.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_skills_carrier_wheel_contents.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_snippet_check.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_span.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_span_id.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_span_id_flavours.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_span_id_props.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_structlog_dependency.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_structural.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_structural_conformance.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_structural_delta.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_template.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_timeout_floor.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_trace_loss.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_trace_object.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_trace_stack_live_child.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_translation_check.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree_canonical.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree_props.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree_walk.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree_writes_guard.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_value_delta.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_value_reference.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_values.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_vendor_validation.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_arch_stress.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_exec.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_heavy.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_schema.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_security.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_static.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_testrun.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_publication_packages.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_publication_provenance.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_publication_registry.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_version_guard_ignores_a_stale_installed_carrier.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_version_literals.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_version_literals_props.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_writer_structural.py +0 -0
- {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/wheel_support.py +0 -0
|
@@ -7,7 +7,7 @@ Parameters
|
|
|
7
7
|
|
|
8
8
|
Licensor: Empower Agile
|
|
9
9
|
|
|
10
|
-
Licensed Work: NarrativeTrace for Python (the narrativetrace PyPI distributions) version 0.4.
|
|
10
|
+
Licensed Work: NarrativeTrace for Python (the narrativetrace PyPI distributions) version 0.4.2. The Licensed Work is (c) 2026 Empower Agile.
|
|
11
11
|
|
|
12
12
|
Additional Use Grant: You may make production use of the Licensed Work for any
|
|
13
13
|
purpose, including internal use and use in products and
|
|
@@ -18,7 +18,7 @@ Additional Use Grant: You may make production use of the Licensed Work for any
|
|
|
18
18
|
parties as a logging, tracing, or code-narrative product
|
|
19
19
|
or service.
|
|
20
20
|
|
|
21
|
-
Change Date: Four years from the date the Licensed Work is published. (2030-10-
|
|
21
|
+
Change Date: Four years from the date the Licensed Work is published. (2030-10-20 for this version)
|
|
22
22
|
|
|
23
23
|
Change License: Apache License, Version 2.0
|
|
24
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: narrativetrace
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Narrative execution tracing for Python — human- and LLM-readable traces
|
|
5
5
|
Project-URL: Homepage, https://github.com/narrativetrace/narrativetrace-python
|
|
6
6
|
Project-URL: Source, https://github.com/narrativetrace/narrativetrace-python
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
# Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four
|
|
3
|
+
# years from publication; Change License: Apache-2.0
|
|
4
|
+
# Copyright (c) 2026 Empower Agile
|
|
5
|
+
"""Exclusion list 1 -- what is structurally never a business boundary in Python.
|
|
6
|
+
|
|
7
|
+
Built in and not configurable: no setting re-enables a member, and nothing here is reported.
|
|
8
|
+
Membership is a structural property of the object, never a vendor's module name:
|
|
9
|
+
|
|
10
|
+
* a target whose type is defined by the interpreter itself (``type(target).__module__ ==
|
|
11
|
+
"builtins"``: ``list``, ``dict``, ``str``, ...) -- C-accelerated, no business logic to narrate;
|
|
12
|
+
* a ``functools.partial`` / ``functools.partialmethod`` object, as a target or as a member -- a
|
|
13
|
+
library-built forwarder whose only behaviour is the callable it wraps;
|
|
14
|
+
* a member the C runtime supplies (builtin functions and bound builtin methods, method wrappers,
|
|
15
|
+
slot wrappers, method descriptors) -- ``types.BuiltinFunctionType`` and its siblings;
|
|
16
|
+
* a member whose ``__name__`` is not an identifier (``<lambda>``, ``<genexpr>``): a name no
|
|
17
|
+
``def`` statement can produce, only the compiler;
|
|
18
|
+
* a ``_private`` / ``__dunder__`` member -- machinery the runtime calls on its own behalf.
|
|
19
|
+
|
|
20
|
+
**@llmNote** ``functools.lru_cache`` wrappers and ``functools.wraps``-decorated functions are
|
|
21
|
+
deliberately NOT members: ``wraps`` makes the wrapper indistinguishable from the function it
|
|
22
|
+
wraps, and a cached business method is still a boundary the reader wants to see.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import functools
|
|
28
|
+
import types
|
|
29
|
+
|
|
30
|
+
GENERATED_MEMBERS: tuple[tuple[str, str], ...] = (
|
|
31
|
+
(
|
|
32
|
+
"builtins.*",
|
|
33
|
+
"The target's type is defined by the interpreter itself (`list`, `dict`, `str`)",
|
|
34
|
+
),
|
|
35
|
+
(
|
|
36
|
+
"functools.partial",
|
|
37
|
+
"A `functools.partial` / `partialmethod` object, as a target or as a member",
|
|
38
|
+
),
|
|
39
|
+
(
|
|
40
|
+
"types.BuiltinFunctionType",
|
|
41
|
+
"A member the C runtime supplies: builtin functions, bound builtin methods, method "
|
|
42
|
+
"wrappers, slot wrappers, method descriptors",
|
|
43
|
+
),
|
|
44
|
+
("<lambda>", "A member whose `__name__` is not an identifier (`<lambda>`, `<genexpr>`)"),
|
|
45
|
+
("_name", "A `_private` or `__dunder__` member: machinery the runtime calls for itself"),
|
|
46
|
+
)
|
|
47
|
+
"""List 1 as the configuration guide publishes it: (what is never traced, its structural
|
|
48
|
+
property). A test holds the guide's table to this tuple."""
|
|
49
|
+
|
|
50
|
+
_C_RUNTIME_CALLABLES: tuple[type, ...] = (
|
|
51
|
+
types.BuiltinFunctionType,
|
|
52
|
+
types.MethodWrapperType,
|
|
53
|
+
types.WrapperDescriptorType,
|
|
54
|
+
types.MethodDescriptorType,
|
|
55
|
+
)
|
|
56
|
+
_FUNCTOOLS_FORWARDERS: tuple[type, ...] = (functools.partial, functools.partialmethod)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_generated_target(target: object) -> bool:
|
|
60
|
+
"""True when ``target`` itself is never a business boundary (see the module docstring)."""
|
|
61
|
+
return type(target).__module__ == "builtins" or isinstance(target, _FUNCTOOLS_FORWARDERS)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def is_generated_member(name: str, member: object) -> bool:
|
|
65
|
+
"""True when member ``name`` (already fetched as ``member``) is never a business boundary."""
|
|
66
|
+
if name.startswith("_"):
|
|
67
|
+
return True
|
|
68
|
+
if isinstance(member, _C_RUNTIME_CALLABLES + _FUNCTOOLS_FORWARDERS):
|
|
69
|
+
return True
|
|
70
|
+
return not getattr(member, "__name__", "x").isidentifier()
|
|
@@ -8,7 +8,8 @@ description: "Installs NarrativeTrace into a Python project and gets it to a fir
|
|
|
8
8
|
## 1. Install with the real toolchain
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
uv
|
|
11
|
+
uv add narrativetrace
|
|
12
|
+
uv sync
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
@@ -19,6 +20,10 @@ if bad:
|
|
|
19
20
|
sys.exit(1 if bad else 0)
|
|
20
21
|
'`
|
|
21
22
|
|
|
23
|
+
**failure:** the project is managed by pip, poetry or pdm rather than uv, or `uv add` just wrote a uv.lock beside another manager's lockfile — this page spells its commands in uv, and the project uses another manager. Fix: remove the uv.lock uv wrote; a project on pip, poetry or pdm adds the same distribution with its own command and checks with its own clean install
|
|
24
|
+
|
|
25
|
+
**failure:** `uv add` exits 2 with `No pyproject.toml found` — the directory is no Python project yet, and `uv add` does not create one. Fix: run `uv init --bare` once, then `uv add narrativetrace` again
|
|
26
|
+
|
|
22
27
|
**failure:** a sibling narrativetrace-* package disagrees on version — an installed distribution outside the lockstep version the other narrativetrace-* packages share. Fix: run the narrativetrace-doctor skill's toolchain.package-versions check, then pin every narrativetrace-* dependency to the same version and `uv sync`
|
|
23
28
|
|
|
24
29
|
## 2. Wire the frameworks this project already uses
|
|
@@ -41,7 +46,7 @@ sys.exit(1 if bad else 0)
|
|
|
41
46
|
|
|
42
47
|
## 3. First trace: wrap, call, render, run
|
|
43
48
|
|
|
44
|
-
**when:** if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a demo main.py: run the application the way it already runs, exercise one real boundary, and read that request's trace; the verify below is for the standalone script, and in an existing application the step is done when that request's trace is in the output; otherwise create the smallest script as follows
|
|
49
|
+
**when:** if the project already has tests — a test directory, a runner's table in its pyproject.toml or a test runner among its dependencies — and the doctor reports a test integration for that runner, do not add a demo script: run one existing test through that integration with output on, which writes that test's narrative files, .md and .nt, under the integration's output directory — the files narrativetrace-doctor, -verify, -debug and -explain read; print the rendered file so the trace is also on the console; the step is done when that test's files exist; a project whose runner has no integration keeps that runner and takes the console path below; if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a demo main.py: run the application the way it already runs, exercise one real boundary, and read that request's trace; the verify below is for the standalone script, and in an existing application the step is done when that request's trace is in the output; otherwise create the smallest script as follows; nothing is written to disk by this script — the application's log is the record once the logging bridge in the later step is wired; this page never adds a test runner or a test to a project that has none
|
|
45
50
|
|
|
46
51
|
<!-- snippet: examples/sixty_seconds/main.py -->
|
|
47
52
|
```python
|
|
@@ -76,7 +81,35 @@ print(IndentedTextRenderer().render(context.capture_trace()))
|
|
|
76
81
|
|
|
77
82
|
**failure:** a *args method's parameters render as one args: [...] value — inspect.signature has nothing named to reconstruct per-argument that Python itself does not have. Fix: supply explicit names: @traced("first", "second", ...) above the method
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
**failure:** a bare function, a lambda or a functools.partial gives no spans at all, or a span carries the wrong class name — a span is named after type(target).__name__, and trace_object never traces a function, lambda or partial. Fix: trace an instance of a class of your own, or pass class_name="..." to trace_object to show a different name
|
|
85
|
+
|
|
86
|
+
**failure:** trace_object returns the object unwrapped and stderr says it cannot trace it (excluded module ...) — the class is defined under a module prefix on the exclude list, whose shipped default covers test and mocking libraries and APM runtimes. Fix: wrap a class of your own, or set NARRATIVETRACE_EXCLUDE (or the exclude key) to the full list you want, which replaces the default
|
|
87
|
+
|
|
88
|
+
## 4. Name the service
|
|
89
|
+
|
|
90
|
+
<!-- snippet: examples/sixty_seconds/main_named_service.py -->
|
|
91
|
+
```python
|
|
92
|
+
# main.py
|
|
93
|
+
from narrativetrace import ContextVarNarrativeContext, ServiceIdentity
|
|
94
|
+
|
|
95
|
+
context = ContextVarNarrativeContext(service_identity=ServiceIdentity("orders", "1.4.0", "prod"))
|
|
96
|
+
```
|
|
97
|
+
<!-- /snippet -->
|
|
98
|
+
|
|
99
|
+
**done when:** the service has its own real name — set once, where the context is built or in the environment the service runs in (NARRATIVETRACE_SERVICE_NAME), never a placeholder; in an existing application the line that builds the context changes, no second context is added
|
|
100
|
+
|
|
101
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
102
|
+
report = json.load(sys.stdin)
|
|
103
|
+
by_id = {f["id"]: f for f in report["findings"]}
|
|
104
|
+
finding = by_id.get("config.service-identity")
|
|
105
|
+
if finding is None or finding["status"] != "pass":
|
|
106
|
+
print(finding["fix"] if finding else "the doctor has no config.service-identity finding")
|
|
107
|
+
sys.exit(0 if finding is not None and finding["status"] == "pass" else 1)
|
|
108
|
+
'`
|
|
109
|
+
|
|
110
|
+
**failure:** traces say unknown_service:python on the platform — nobody named the service, so the fallback name is all there is. Fix: pass service_identity=ServiceIdentity("orders") to the ContextVarNarrativeContext, or set NARRATIVETRACE_SERVICE_NAME (and NARRATIVETRACE_SERVICE_VERSION, NARRATIVETRACE_ENVIRONMENT) in the environment the service runs in
|
|
111
|
+
|
|
112
|
+
## 5. Send it to your logger
|
|
80
113
|
|
|
81
114
|
**when:** if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a second main.py or a second logging setup: add the filter and the export_to_logger call to the application's own logging configuration and the boundary you exercised; the verify below is for the standalone script, and in an existing application the step is done when that boundary's trace reaches the application's own logger; otherwise create the smallest script as follows
|
|
82
115
|
|
|
@@ -132,7 +165,7 @@ export_to_logger(trace) # new: sends the same captured trace through the config
|
|
|
132
165
|
|
|
133
166
|
**verify:** `uv run python main_with_logger.py`
|
|
134
167
|
|
|
135
|
-
##
|
|
168
|
+
## 6. Run the doctor and resolve its findings
|
|
136
169
|
|
|
137
170
|
```bash
|
|
138
171
|
uv run narrativetrace doctor || true
|
|
@@ -141,10 +174,10 @@ uv run narrativetrace doctor || true
|
|
|
141
174
|
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
142
175
|
report = json.load(sys.stdin)
|
|
143
176
|
findings = report.get("findings")
|
|
144
|
-
sys.exit(1 if not isinstance(findings, list) or len(findings) !=
|
|
177
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
|
|
145
178
|
'`
|
|
146
179
|
|
|
147
|
-
##
|
|
180
|
+
## 7. Install the skills for next time
|
|
148
181
|
|
|
149
182
|
```bash
|
|
150
183
|
uv run narrativetrace init --dry-run
|
|
@@ -169,7 +202,7 @@ sys.exit(0 if result.returncode == 0 and shown and untouched else 1)
|
|
|
169
202
|
|
|
170
203
|
## Always
|
|
171
204
|
|
|
172
|
-
-
|
|
205
|
+
- Check the install with a clean `uv sync` after adding the package, rather than trusting whatever is already in the virtual environment. (`uv add` installs; the clean sync is the check that the lockfile it wrote reproduces -- a mismatched sibling version or a stale lockfile is the single most common install failure, and it only surfaces on a clean install)
|
|
173
206
|
|
|
174
207
|
## Never
|
|
175
208
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: narrativetrace-doctor
|
|
3
|
-
description: "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
|
|
3
|
+
description: "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed in this project and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# narrativetrace-doctor
|
|
@@ -14,7 +14,7 @@ uv run narrativetrace doctor || true
|
|
|
14
14
|
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
15
15
|
report = json.load(sys.stdin)
|
|
16
16
|
findings = report.get("findings")
|
|
17
|
-
sys.exit(1 if not isinstance(findings, list) or len(findings) !=
|
|
17
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
|
|
18
18
|
'`
|
|
19
19
|
|
|
20
20
|
**failure:** the CLI's JSON output does not parse, or is missing findings — the CLI crashed instead of reporting a finding. Fix: re-run `uv run narrativetrace doctor --json` directly and read the raw output -- a crash here is a doctor bug, never a project finding
|
|
@@ -57,7 +57,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/t
|
|
|
57
57
|
|
|
58
58
|
## 8. Explain the run by span id, and mark what you inferred
|
|
59
59
|
|
|
60
|
-
**done when:** the explanation is in the user's own words and in the trace's own order: the calls the flow made, each claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt or .md read in this session count — the branch it took, and where the time went when that was the question. Every sentence that goes beyond what the trace shows is marked as an inference, and the last part names what was NOT observed: the code no span covers, because only wrapped or intercepted code has one
|
|
60
|
+
**done when:** the explanation is in the user's own words and in the trace's own order: the calls the flow made, each claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt or .md read in this session count — the branch it took, and where the time went when that was the question. Every sentence that goes beyond what the trace shows is marked as an inference, and the last part names what was NOT observed: the code no span covers, because only wrapped or intercepted code has one, and the branches this run did not take
|
|
61
61
|
|
|
62
62
|
## Which flavour answers which question
|
|
63
63
|
|
|
@@ -14,7 +14,7 @@ uv run narrativetrace doctor || true
|
|
|
14
14
|
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
15
15
|
report = json.load(sys.stdin)
|
|
16
16
|
findings = report.get("findings")
|
|
17
|
-
sys.exit(1 if not isinstance(findings, list) or len(findings) !=
|
|
17
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
|
|
18
18
|
'`
|
|
19
19
|
|
|
20
20
|
**failure:** the CLI is not found, or the project has no pyproject.toml — NarrativeTrace is not installed in this project's environment. Fix: report under the prompt or library category instead -- those do not need a doctor report
|
|
@@ -65,7 +65,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/s
|
|
|
65
65
|
|
|
66
66
|
## 5. Open values on the span that looks wrong, and only there
|
|
67
67
|
|
|
68
|
-
**when:**
|
|
68
|
+
**when:** when the structural read named a span that does not match the intent, or the shape matches but a symptom points at data — an assertion failed on a value, the output is wrong — then open the one span the symptom points at; otherwise go on to the pin
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
71
|
uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/traces").rglob("*.md")), sep="\n")'
|
|
@@ -75,7 +75,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/t
|
|
|
75
75
|
|
|
76
76
|
## 6. Fix, re-run, read again
|
|
77
77
|
|
|
78
|
-
**when:** only when the structural read named a span that does not match the intent; otherwise go on to the pin
|
|
78
|
+
**when:** only when the structural read named a span that does not match the intent, or the value read in step 5 was wrong; otherwise go on to the pin
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
uv run pytest <the smallest test that drives the real path>
|
|
@@ -118,7 +118,7 @@ uv run narrativetrace-approve
|
|
|
118
118
|
|
|
119
119
|
## 11. Report what the trace showed
|
|
120
120
|
|
|
121
|
-
**done when:** two sentences on what the trace showed, every claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt that was read count — with the .nt attached or quoted; 'tests pass' alone is not the report
|
|
121
|
+
**done when:** two sentences on what the trace showed on the path this run took, every claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt that was read count — with the .nt attached or quoted; the report names the branches and inputs this run did not exercise, because one trace shows one path, not every execution; 'tests pass' alone is not the report
|
|
122
122
|
|
|
123
123
|
## Which flavour answers which question
|
|
124
124
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"skills": [
|
|
4
4
|
{
|
|
5
5
|
"name": "narrativetrace-doctor",
|
|
6
|
-
"description": "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it.",
|
|
6
|
+
"description": "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed in this project and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it.",
|
|
7
7
|
"agents": "agents/narrativetrace-doctor/SKILL.md",
|
|
8
8
|
"claude": "claude/narrativetrace-doctor/SKILL.md"
|
|
9
9
|
},
|
|
@@ -10,7 +10,8 @@ allowed-tools: Bash(uv *), Bash(git *)
|
|
|
10
10
|
## 1. Install with the real toolchain
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
uv
|
|
13
|
+
uv add narrativetrace
|
|
14
|
+
uv sync
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
@@ -21,6 +22,10 @@ if bad:
|
|
|
21
22
|
sys.exit(1 if bad else 0)
|
|
22
23
|
'`
|
|
23
24
|
|
|
25
|
+
**failure:** the project is managed by pip, poetry or pdm rather than uv, or `uv add` just wrote a uv.lock beside another manager's lockfile — this page spells its commands in uv, and the project uses another manager. Fix: remove the uv.lock uv wrote; a project on pip, poetry or pdm adds the same distribution with its own command and checks with its own clean install
|
|
26
|
+
|
|
27
|
+
**failure:** `uv add` exits 2 with `No pyproject.toml found` — the directory is no Python project yet, and `uv add` does not create one. Fix: run `uv init --bare` once, then `uv add narrativetrace` again
|
|
28
|
+
|
|
24
29
|
**failure:** a sibling narrativetrace-* package disagrees on version — an installed distribution outside the lockstep version the other narrativetrace-* packages share. Fix: run the narrativetrace-doctor skill's toolchain.package-versions check, then pin every narrativetrace-* dependency to the same version and `uv sync`
|
|
25
30
|
|
|
26
31
|
## 2. Wire the frameworks this project already uses
|
|
@@ -43,7 +48,7 @@ sys.exit(1 if bad else 0)
|
|
|
43
48
|
|
|
44
49
|
## 3. First trace: wrap, call, render, run
|
|
45
50
|
|
|
46
|
-
**when:** if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a demo main.py: run the application the way it already runs, exercise one real boundary, and read that request's trace; the verify below is for the standalone script, and in an existing application the step is done when that request's trace is in the output; otherwise create the smallest script as follows
|
|
51
|
+
**when:** if the project already has tests — a test directory, a runner's table in its pyproject.toml or a test runner among its dependencies — and the doctor reports a test integration for that runner, do not add a demo script: run one existing test through that integration with output on, which writes that test's narrative files, .md and .nt, under the integration's output directory — the files narrativetrace-doctor, -verify, -debug and -explain read; print the rendered file so the trace is also on the console; the step is done when that test's files exist; a project whose runner has no integration keeps that runner and takes the console path below; if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a demo main.py: run the application the way it already runs, exercise one real boundary, and read that request's trace; the verify below is for the standalone script, and in an existing application the step is done when that request's trace is in the output; otherwise create the smallest script as follows; nothing is written to disk by this script — the application's log is the record once the logging bridge in the later step is wired; this page never adds a test runner or a test to a project that has none
|
|
47
52
|
|
|
48
53
|
<!-- snippet: examples/sixty_seconds/main.py -->
|
|
49
54
|
```python
|
|
@@ -78,7 +83,35 @@ print(IndentedTextRenderer().render(context.capture_trace()))
|
|
|
78
83
|
|
|
79
84
|
**failure:** a *args method's parameters render as one args: [...] value — inspect.signature has nothing named to reconstruct per-argument that Python itself does not have. Fix: supply explicit names: @traced("first", "second", ...) above the method
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
**failure:** a bare function, a lambda or a functools.partial gives no spans at all, or a span carries the wrong class name — a span is named after type(target).__name__, and trace_object never traces a function, lambda or partial. Fix: trace an instance of a class of your own, or pass class_name="..." to trace_object to show a different name
|
|
87
|
+
|
|
88
|
+
**failure:** trace_object returns the object unwrapped and stderr says it cannot trace it (excluded module ...) — the class is defined under a module prefix on the exclude list, whose shipped default covers test and mocking libraries and APM runtimes. Fix: wrap a class of your own, or set NARRATIVETRACE_EXCLUDE (or the exclude key) to the full list you want, which replaces the default
|
|
89
|
+
|
|
90
|
+
## 4. Name the service
|
|
91
|
+
|
|
92
|
+
<!-- snippet: examples/sixty_seconds/main_named_service.py -->
|
|
93
|
+
```python
|
|
94
|
+
# main.py
|
|
95
|
+
from narrativetrace import ContextVarNarrativeContext, ServiceIdentity
|
|
96
|
+
|
|
97
|
+
context = ContextVarNarrativeContext(service_identity=ServiceIdentity("orders", "1.4.0", "prod"))
|
|
98
|
+
```
|
|
99
|
+
<!-- /snippet -->
|
|
100
|
+
|
|
101
|
+
**done when:** the service has its own real name — set once, where the context is built or in the environment the service runs in (NARRATIVETRACE_SERVICE_NAME), never a placeholder; in an existing application the line that builds the context changes, no second context is added
|
|
102
|
+
|
|
103
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
104
|
+
report = json.load(sys.stdin)
|
|
105
|
+
by_id = {f["id"]: f for f in report["findings"]}
|
|
106
|
+
finding = by_id.get("config.service-identity")
|
|
107
|
+
if finding is None or finding["status"] != "pass":
|
|
108
|
+
print(finding["fix"] if finding else "the doctor has no config.service-identity finding")
|
|
109
|
+
sys.exit(0 if finding is not None and finding["status"] == "pass" else 1)
|
|
110
|
+
'`
|
|
111
|
+
|
|
112
|
+
**failure:** traces say unknown_service:python on the platform — nobody named the service, so the fallback name is all there is. Fix: pass service_identity=ServiceIdentity("orders") to the ContextVarNarrativeContext, or set NARRATIVETRACE_SERVICE_NAME (and NARRATIVETRACE_SERVICE_VERSION, NARRATIVETRACE_ENVIRONMENT) in the environment the service runs in
|
|
113
|
+
|
|
114
|
+
## 5. Send it to your logger
|
|
82
115
|
|
|
83
116
|
**when:** if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a second main.py or a second logging setup: add the filter and the export_to_logger call to the application's own logging configuration and the boundary you exercised; the verify below is for the standalone script, and in an existing application the step is done when that boundary's trace reaches the application's own logger; otherwise create the smallest script as follows
|
|
84
117
|
|
|
@@ -134,7 +167,7 @@ export_to_logger(trace) # new: sends the same captured trace through the config
|
|
|
134
167
|
|
|
135
168
|
**verify:** `uv run python main_with_logger.py`
|
|
136
169
|
|
|
137
|
-
##
|
|
170
|
+
## 6. Run the doctor and resolve its findings
|
|
138
171
|
|
|
139
172
|
```bash
|
|
140
173
|
uv run narrativetrace doctor || true
|
|
@@ -143,10 +176,10 @@ uv run narrativetrace doctor || true
|
|
|
143
176
|
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
144
177
|
report = json.load(sys.stdin)
|
|
145
178
|
findings = report.get("findings")
|
|
146
|
-
sys.exit(1 if not isinstance(findings, list) or len(findings) !=
|
|
179
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
|
|
147
180
|
'`
|
|
148
181
|
|
|
149
|
-
##
|
|
182
|
+
## 7. Install the skills for next time
|
|
150
183
|
|
|
151
184
|
```bash
|
|
152
185
|
uv run narrativetrace init --dry-run
|
|
@@ -171,7 +204,7 @@ sys.exit(0 if result.returncode == 0 and shown and untouched else 1)
|
|
|
171
204
|
|
|
172
205
|
## Always
|
|
173
206
|
|
|
174
|
-
-
|
|
207
|
+
- Check the install with a clean `uv sync` after adding the package, rather than trusting whatever is already in the virtual environment. (`uv add` installs; the clean sync is the check that the lockfile it wrote reproduces -- a mismatched sibling version or a stale lockfile is the single most common install failure, and it only surfaces on a clean install)
|
|
175
208
|
|
|
176
209
|
## Never
|
|
177
210
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: narrativetrace-doctor
|
|
3
|
-
description: "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
|
|
4
|
-
when_to_use: "A project already has NarrativeTrace installed and something about it is not working, or an agent wants a pre-flight check before wiring it into new code."
|
|
3
|
+
description: "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed in this project and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
|
|
4
|
+
when_to_use: "A project already has NarrativeTrace installed and something about it is not working, or an agent wants a pre-flight check before wiring it into new code. Also when a user installed these skills as a personal plugin in their agent rather than in this project: a plugin install is invisible to this check by design, and only a project install reaches teammates and other agents."
|
|
5
5
|
allowed-tools: Bash(uv *), Bash(git *)
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -16,7 +16,7 @@ uv run narrativetrace doctor || true
|
|
|
16
16
|
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
17
17
|
report = json.load(sys.stdin)
|
|
18
18
|
findings = report.get("findings")
|
|
19
|
-
sys.exit(1 if not isinstance(findings, list) or len(findings) !=
|
|
19
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
|
|
20
20
|
'`
|
|
21
21
|
|
|
22
22
|
**failure:** the CLI's JSON output does not parse, or is missing findings — the CLI crashed instead of reporting a finding. Fix: re-run `uv run narrativetrace doctor --json` directly and read the raw output -- a crash here is a doctor bug, never a project finding
|
|
@@ -58,7 +58,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/t
|
|
|
58
58
|
|
|
59
59
|
## 8. Explain the run by span id, and mark what you inferred
|
|
60
60
|
|
|
61
|
-
**done when:** the explanation is in the user's own words and in the trace's own order: the calls the flow made, each claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt or .md read in this session count — the branch it took, and where the time went when that was the question. Every sentence that goes beyond what the trace shows is marked as an inference, and the last part names what was NOT observed: the code no span covers, because only wrapped or intercepted code has one
|
|
61
|
+
**done when:** the explanation is in the user's own words and in the trace's own order: the calls the flow made, each claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt or .md read in this session count — the branch it took, and where the time went when that was the question. Every sentence that goes beyond what the trace shows is marked as an inference, and the last part names what was NOT observed: the code no span covers, because only wrapped or intercepted code has one, and the branches this run did not take
|
|
62
62
|
|
|
63
63
|
## Which flavour answers which question
|
|
64
64
|
|
|
@@ -15,7 +15,7 @@ uv run narrativetrace doctor || true
|
|
|
15
15
|
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
16
16
|
report = json.load(sys.stdin)
|
|
17
17
|
findings = report.get("findings")
|
|
18
|
-
sys.exit(1 if not isinstance(findings, list) or len(findings) !=
|
|
18
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
|
|
19
19
|
'`
|
|
20
20
|
|
|
21
21
|
**failure:** the CLI is not found, or the project has no pyproject.toml — NarrativeTrace is not installed in this project's environment. Fix: report under the prompt or library category instead -- those do not need a doctor report
|
|
@@ -66,7 +66,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/s
|
|
|
66
66
|
|
|
67
67
|
## 5. Open values on the span that looks wrong, and only there
|
|
68
68
|
|
|
69
|
-
**when:**
|
|
69
|
+
**when:** when the structural read named a span that does not match the intent, or the shape matches but a symptom points at data — an assertion failed on a value, the output is wrong — then open the one span the symptom points at; otherwise go on to the pin
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
72
|
uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/traces").rglob("*.md")), sep="\n")'
|
|
@@ -76,7 +76,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/t
|
|
|
76
76
|
|
|
77
77
|
## 6. Fix, re-run, read again
|
|
78
78
|
|
|
79
|
-
**when:** only when the structural read named a span that does not match the intent; otherwise go on to the pin
|
|
79
|
+
**when:** only when the structural read named a span that does not match the intent, or the value read in step 5 was wrong; otherwise go on to the pin
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
uv run pytest <the smallest test that drives the real path>
|
|
@@ -119,7 +119,7 @@ uv run narrativetrace-approve
|
|
|
119
119
|
|
|
120
120
|
## 11. Report what the trace showed
|
|
121
121
|
|
|
122
|
-
**done when:** two sentences on what the trace showed, every claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt that was read count — with the .nt attached or quoted; 'tests pass' alone is not the report
|
|
122
|
+
**done when:** two sentences on what the trace showed on the path this run took, every claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt that was read count — with the .nt attached or quoted; the report names the branches and inputs this run did not exercise, because one trace shows one path, not every execution; 'tests pass' alone is not the report
|
|
123
123
|
|
|
124
124
|
## Which flavour answers which question
|
|
125
125
|
|
|
@@ -60,9 +60,12 @@ def _load_toml(path: Path) -> dict[str, Any] | None:
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def _canonical(value: Any) -> str:
|
|
63
|
-
"""Renders a TOML
|
|
63
|
+
"""Renders a TOML value the way a properties file would carry it (``true``, not ``True``; an
|
|
64
|
+
array as its items joined by ``;``, the delimiter list-valued keys such as ``exclude`` use)."""
|
|
64
65
|
if isinstance(value, bool):
|
|
65
66
|
return "true" if value else "false"
|
|
67
|
+
if isinstance(value, list):
|
|
68
|
+
return ";".join(_canonical(item) for item in value)
|
|
66
69
|
return str(value)
|
|
67
70
|
|
|
68
71
|
|
|
@@ -223,8 +223,21 @@ class NarrativeContext(ABC):
|
|
|
223
223
|
"""Returns the current trace id, generating one eagerly if absent."""
|
|
224
224
|
return TraceId.generate()
|
|
225
225
|
|
|
226
|
-
def adopt_trace_id(
|
|
227
|
-
|
|
226
|
+
def adopt_trace_id(
|
|
227
|
+
self,
|
|
228
|
+
trace_id: TraceId,
|
|
229
|
+
remote_parent_span_id: SpanId | None = None,
|
|
230
|
+
trace_flags: int = 0,
|
|
231
|
+
) -> None:
|
|
232
|
+
"""Adopts an inbound (e.g. W3C ``traceparent``) trace id for the current scope.
|
|
233
|
+
|
|
234
|
+
``remote_parent_span_id`` is the caller's span the inbound request came from (the
|
|
235
|
+
``traceparent`` parent-id field). When given, this scope's root spans record it as their
|
|
236
|
+
``parent_span_id``, so a consumer that follows parent ids links this service's trace to the
|
|
237
|
+
caller's; it never appears as a span in this scope's own tree. ``trace_flags`` is the
|
|
238
|
+
``traceparent`` flags byte (bit 0 = sampled); every span this scope creates carries it as
|
|
239
|
+
:attr:`~narrativetrace.span.SpanContext.trace_flags`.
|
|
240
|
+
"""
|
|
228
241
|
|
|
229
242
|
def story_id(self) -> str | None:
|
|
230
243
|
"""Story id derived from the first root-level call, or ``None``."""
|
|
@@ -290,6 +303,8 @@ class _TraceStack:
|
|
|
290
303
|
trace_id: TraceId | None = None
|
|
291
304
|
scoped_parent: SpanId | None = None
|
|
292
305
|
snapshot_parent: SpanId | None = None
|
|
306
|
+
remote_parent: SpanId | None = None
|
|
307
|
+
trace_flags: int = 0
|
|
293
308
|
story_id: str | None = None
|
|
294
309
|
chapter_id: str | None = None
|
|
295
310
|
metadata: _RequestMetadata = _EMPTY_METADATA
|
|
@@ -490,6 +505,11 @@ class ContextVarNarrativeContext(NarrativeContext):
|
|
|
490
505
|
|
|
491
506
|
The stack lives in a :class:`contextvars.ContextVar`, so a fresh thread and a freshly
|
|
492
507
|
created asyncio task each lazily obtain their own isolated stack while sharing one store.
|
|
508
|
+
|
|
509
|
+
``service_identity`` names the service on every span. Left ``None`` it is read from
|
|
510
|
+
``NARRATIVETRACE_SERVICE_NAME`` / ``NARRATIVETRACE_SERVICE_VERSION`` /
|
|
511
|
+
``NARRATIVETRACE_ENVIRONMENT`` (:meth:`ServiceIdentity.from_environment`); with neither, traces
|
|
512
|
+
say ``unknown_service:python``.
|
|
493
513
|
"""
|
|
494
514
|
|
|
495
515
|
def __init__(
|
|
@@ -499,7 +519,9 @@ class ContextVarNarrativeContext(NarrativeContext):
|
|
|
499
519
|
store: EventStore | None = None,
|
|
500
520
|
) -> None:
|
|
501
521
|
self._config = config if config is not None else NarrativeTraceConfig()
|
|
502
|
-
self._service_identity =
|
|
522
|
+
self._service_identity = (
|
|
523
|
+
service_identity if service_identity is not None else ServiceIdentity.from_environment()
|
|
524
|
+
)
|
|
503
525
|
self._store = store if store is not None else EventStore()
|
|
504
526
|
self._span_contexts: dict[SpanId, SpanContext] = {}
|
|
505
527
|
self._span_lock = threading.Lock()
|
|
@@ -556,7 +578,9 @@ class ContextVarNarrativeContext(NarrativeContext):
|
|
|
556
578
|
active = stack.peek_active()
|
|
557
579
|
if active is not None:
|
|
558
580
|
return active
|
|
559
|
-
|
|
581
|
+
if stack.snapshot_parent is not None:
|
|
582
|
+
return stack.snapshot_parent
|
|
583
|
+
return stack.remote_parent
|
|
560
584
|
|
|
561
585
|
def _create_span_context(
|
|
562
586
|
self, stack: _TraceStack, parent_span_id: SpanId | None
|
|
@@ -571,6 +595,7 @@ class ContextVarNarrativeContext(NarrativeContext):
|
|
|
571
595
|
trace_id=trace_id,
|
|
572
596
|
span_id=SpanId.generate(),
|
|
573
597
|
parent_span_id=parent_span_id,
|
|
598
|
+
trace_flags=stack.trace_flags,
|
|
574
599
|
http_method=metadata.http_method,
|
|
575
600
|
http_route=metadata.http_route,
|
|
576
601
|
client_ip=metadata.client_ip,
|
|
@@ -744,8 +769,16 @@ class ContextVarNarrativeContext(NarrativeContext):
|
|
|
744
769
|
stack.trace_id = TraceId.generate()
|
|
745
770
|
return stack.trace_id
|
|
746
771
|
|
|
747
|
-
def adopt_trace_id(
|
|
748
|
-
self
|
|
772
|
+
def adopt_trace_id(
|
|
773
|
+
self,
|
|
774
|
+
trace_id: TraceId,
|
|
775
|
+
remote_parent_span_id: SpanId | None = None,
|
|
776
|
+
trace_flags: int = 0,
|
|
777
|
+
) -> None:
|
|
778
|
+
stack = self._get_stack()
|
|
779
|
+
stack.trace_id = trace_id
|
|
780
|
+
stack.remote_parent = remote_parent_span_id
|
|
781
|
+
stack.trace_flags = trace_flags
|
|
749
782
|
|
|
750
783
|
def story_id(self) -> str | None:
|
|
751
784
|
return self._get_stack().story_id
|
|
@@ -22,8 +22,10 @@ import sys
|
|
|
22
22
|
import tomllib
|
|
23
23
|
from importlib import metadata as importlib_metadata
|
|
24
24
|
from pathlib import Path
|
|
25
|
+
from typing import Literal
|
|
25
26
|
|
|
26
27
|
from narrativetrace.config import ConfigResolver
|
|
28
|
+
from narrativetrace.exclusions import ExcludedModules
|
|
27
29
|
from narrativetrace_tooling.doctor.types import DoctorSnapshot, Env, PackageInfo
|
|
28
30
|
from narrativetrace_tooling.frameworks.manifests import is_manifest
|
|
29
31
|
from narrativetrace_tooling.init import (
|
|
@@ -222,6 +224,24 @@ class _Walker:
|
|
|
222
224
|
self._visit_file(entry)
|
|
223
225
|
|
|
224
226
|
|
|
227
|
+
def _resolve_exclude(
|
|
228
|
+
resolver: ConfigResolver, env: Env
|
|
229
|
+
) -> tuple[tuple[str, ...] | None, Literal["default", "configured"], str | None]:
|
|
230
|
+
"""The snapshot's exclusion-list fields: (effective list, where it came from, parse error).
|
|
231
|
+
|
|
232
|
+
``env`` is consulted first so a snapshot built from an explicit environment reports that
|
|
233
|
+
environment; otherwise the resolver's own chain (process environment, config file) decides."""
|
|
234
|
+
given = env.get("NARRATIVETRACE_EXCLUDE")
|
|
235
|
+
try:
|
|
236
|
+
excluded = (
|
|
237
|
+
ExcludedModules.parse(given) if given is not None else ExcludedModules.resolve(resolver)
|
|
238
|
+
)
|
|
239
|
+
except ValueError as error:
|
|
240
|
+
return None, "default", str(error)
|
|
241
|
+
configured = given is not None or resolver.resolve("exclude", None) is not None
|
|
242
|
+
return excluded.prefixes, "configured" if configured else "default", None
|
|
243
|
+
|
|
244
|
+
|
|
225
245
|
def build_snapshot(cwd: str, env: Env) -> DoctorSnapshot:
|
|
226
246
|
"""Builds a :class:`~narrativetrace_tooling.doctor.types.DoctorSnapshot` from the real
|
|
227
247
|
filesystem rooted at ``cwd``."""
|
|
@@ -230,6 +250,7 @@ def build_snapshot(cwd: str, env: Env) -> DoctorSnapshot:
|
|
|
230
250
|
config = resolver.file_values
|
|
231
251
|
output_dir_name = str(config.get("output_dir") or "narrative-traces")
|
|
232
252
|
approved_dir_name = str(config.get("approved_dir") or "test-narratives")
|
|
253
|
+
exclude = _resolve_exclude(resolver, env)
|
|
233
254
|
walker = _Walker(root, output_dir_name, approved_dir_name)
|
|
234
255
|
walker.walk()
|
|
235
256
|
project = _read_project_state(root)
|
|
@@ -248,4 +269,7 @@ def build_snapshot(cwd: str, env: Env) -> DoctorSnapshot:
|
|
|
248
269
|
pointer_readings=_resolve_pointer_readings(project),
|
|
249
270
|
catalogue_skill_names=_resolve_catalogue_skill_names(),
|
|
250
271
|
narrativetrace_version=project_family_version(),
|
|
272
|
+
effective_exclude=exclude[0],
|
|
273
|
+
exclude_source=exclude[1],
|
|
274
|
+
exclude_error=exclude[2],
|
|
251
275
|
)
|