sourcecode 4.0.1__tar.gz → 4.1.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.
- {sourcecode-4.0.1 → sourcecode-4.1.0}/CHANGELOG.md +64 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/PKG-INFO +15 -3
- {sourcecode-4.0.1 → sourcecode-4.1.0}/README.md +14 -2
- {sourcecode-4.0.1 → sourcecode-4.1.0}/pyproject.toml +1 -1
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/cli.py +12 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/envelope.py +10 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/non_coverage.py +8 -5
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/posture.py +90 -0
- sourcecode-4.1.0/src/sourcecode/release_info.py +123 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/rule_catalog.py +1 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/schemas/envelope-v1.schema.json +8 -1
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/security_config_scan.py +132 -1
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_security_audit.py +16 -2
- {sourcecode-4.0.1 → sourcecode-4.1.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/.gitignore +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/.ruff.toml +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/CONTRIBUTING.md +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/LICENSE +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/SECURITY.md +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/raw +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/scripts/perf_harness.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/archetype.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/cache_model.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/license.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/remedies.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/risk.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/supabase/functions/README.md +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.0.1 → sourcecode-4.1.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.1.0] — 2026-08-03
|
|
6
|
+
|
|
7
|
+
Minor, not a patch, and the criterion is this project's own: a new rule can trip
|
|
8
|
+
a gate that was green yesterday. `spring-audit --ci` and `verify --fail-on any`
|
|
9
|
+
will fail on a repository that stores a credential in a deployment descriptor —
|
|
10
|
+
correctly, and for the first time. Nothing is removed, no default moves, and no
|
|
11
|
+
exit code changes meaning.
|
|
12
|
+
|
|
13
|
+
Field audit #8 (deep read-only audit of the eval #6/#7 repository, by a new
|
|
14
|
+
evaluator, 100 % catalogue coverage) ran a build self-reporting **3.5.0** while
|
|
15
|
+
4.0.1 was shipping, and re-derived twelve of its fourteen product complaints from
|
|
16
|
+
rows already closed — one of them the answer to its own critical finding. The
|
|
17
|
+
first entry below is that defect; the rest are the three findings that survived
|
|
18
|
+
verification against 4.0.1.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **SEC-007 — a credential stored in a deployment descriptor** (CL-10). The audit
|
|
23
|
+
found `monitor:{SHA-256}dc20b820…` in
|
|
24
|
+
`src/main/options/{DPA,M3,M3DEV}/WEB-INF/web.xml`: unsalted, the same hash in all
|
|
25
|
+
three per-environment overlays, and found by hand because *"los overlays
|
|
26
|
+
`src/main/options/**` son punto ciego"*. The gap was admission surface, not
|
|
27
|
+
detection — those descriptors are files `environment_resolution`,
|
|
28
|
+
`filter_surface` and `path_filters` already open, and SEC-004 already names a
|
|
29
|
+
fast unsalted digest. The rule fires on the **shape of the value**
|
|
30
|
+
(`{ALGO}<hex>`), never on the parameter's name (VAI), with `${placeholder}` as
|
|
31
|
+
the negative control; the digest is truncated in the finding, because a report
|
|
32
|
+
about a credential must not be a place to read one from; and identical digests
|
|
33
|
+
across descriptors are composed into the statement the audit actually made —
|
|
34
|
+
three environments share one credential, so the least protected decides the
|
|
35
|
+
strength of all three. NC-002 is **narrowed, not deleted**: Helm values, CI
|
|
36
|
+
variable files and shell scripts stay declared out of scope. Zero findings across
|
|
37
|
+
six field repositories.
|
|
38
|
+
- **`ask version` says how old the build is** (C4-12). Age in days alone would not
|
|
39
|
+
have caught the audit's case — that build was three days old. On this project's
|
|
40
|
+
cadence three days is six releases (eight, in fact). So the block publishes the
|
|
41
|
+
release date, the measured release rate (2.07/day over the last 30 releases), an
|
|
42
|
+
**estimate** of releases since with its basis, and an advisory naming the upgrade
|
|
43
|
+
command and the defect ledger. It never claims a newer release exists: offline,
|
|
44
|
+
that would be the confident-wrong answer the ledger exists to prevent. `_meta`
|
|
45
|
+
carries `tool_release_date` only — the date is a constant of the build, the age
|
|
46
|
+
changes daily, and the envelope is byte-identical by contract.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **`posture` now says when the container would not contradict its own resolution**
|
|
51
|
+
(CL-11). `allow-bean-definition-overriding: true` and
|
|
52
|
+
`allow-circular-references: true`, in the repository whose entire security verdict
|
|
53
|
+
turns on which `SecurityConfig` bean wins: with overriding enabled a second
|
|
54
|
+
definition replaces the first silently, so the startup failure that would have
|
|
55
|
+
caught a filter-chain override never happens. The block only ever **lowers**
|
|
56
|
+
confidence, so it sits beside the verdict and is repeated inside
|
|
57
|
+
`security.resolution_is_a_default` — the C1-20 lesson runs the other way here, and
|
|
58
|
+
a caveat a reader has to go and find does not reach the conclusion it was written
|
|
59
|
+
for. An undecidable setting is not read as off.
|
|
60
|
+
- **The platform claim matches what the battery can check** (C3-38). Third field
|
|
61
|
+
report of console mojibake on Windows/PowerShell; C3-3 and C3-12 are both recorded
|
|
62
|
+
*does not reproduce*, both scoped macOS-only. Patching a codec that was never
|
|
63
|
+
reproduced is what C3-27's rule forbids, so the README states instead that
|
|
64
|
+
Windows is a target the battery cannot verify, names what the CLI does do for it,
|
|
65
|
+
and cites the three rows. A documented limit is not a fix: C3-38 stays open until
|
|
66
|
+
a Windows runner exists.
|
|
67
|
+
|
|
68
|
+
|
|
5
69
|
## [4.0.1] — 2026-08-02
|
|
6
70
|
|
|
7
71
|
Patch, and the criterion is the one this project has used since 3.2.0: nothing here
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.1.0
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -97,13 +97,25 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
97
97
|
# pip / pipx
|
|
98
98
|
pipx install sourcecode # or: pip install sourcecode
|
|
99
99
|
|
|
100
|
-
ask version # ask
|
|
100
|
+
ask version # ask 4.1.0 — and, on a build that has aged,
|
|
101
|
+
# how many releases have probably shipped since
|
|
101
102
|
```
|
|
102
103
|
|
|
103
104
|
> **Package vs. command.** The install package is named `sourcecode` this release
|
|
104
105
|
> (renaming the distribution is a separate, breaking change). Installing it gives you the
|
|
105
106
|
> canonical **`ask`** command plus the deprecated **`sourcecode`** alias.
|
|
106
107
|
|
|
108
|
+
> **Platforms.** Developed and tested on macOS and Linux; every release runs its full
|
|
109
|
+
> battery there. **Windows/PowerShell is a target we cannot verify:** the CLI is written
|
|
110
|
+
> for it — UTF-8 streams are forced at both console entry points, a successful run writes
|
|
111
|
+
> nothing to stderr (PowerShell 5.1 raises `NativeCommandError` on any stderr write), and
|
|
112
|
+
> the MCP server strips the BOM PowerShell puts on stdin — but no Windows runner exists in
|
|
113
|
+
> the battery. Three separate field reports of console mojibake (`—` rendering as `â€"`)
|
|
114
|
+
> have never reproduced under measurement, and the only variable those reports share is the
|
|
115
|
+
> platform we cannot exercise (C3-3, C3-12, C3-38 in the
|
|
116
|
+
> [defect ledger](docs/DEFECT-LEDGER.md)). On Windows, prefer `--output <file>` over shell
|
|
117
|
+
> redirection, and report a rendering problem rather than assuming it is known.
|
|
118
|
+
|
|
107
119
|
---
|
|
108
120
|
|
|
109
121
|
## Quickstart
|
|
@@ -331,7 +343,7 @@ reading JSON sees the same boundary a buyer reads here.
|
|
|
331
343
|
| Not covered | Why | What answers it |
|
|
332
344
|
|---|---|---|
|
|
333
345
|
| **Whether request input reaches a sink — no dataflow or taint analysis.** | This engine resolves structure and wiring without compiling. Taint needs value flow through a program, which is a different analysis with a different failure mode: an unsound one produces confident findings that are wrong, and every claim here is meant to be checkable against the line that produced it. | A dataflow scanner (Semgrep, CodeQL). What this product adds on top is reachability: which of that tool's findings sit behind an endpoint that is reachable unauthenticated. |
|
|
334
|
-
| **Secrets outside Java
|
|
346
|
+
| **Secrets outside Java, Spring configuration and the deployment descriptors — a Helm value, a CI variable file, a shell script.** | The file population this analyzer reads is the Java source, the Spring configuration convention and the descriptors it already parses to resolve an environment. A secret elsewhere is not missed by a weak rule; it is outside the set of files anything here opens. Narrowed by CL-10: a credential stored in `web.xml` or a Spring XML context IS reported (SEC-007), because those files were already being read. | A dedicated secret scanner over the whole tree (gitleaks, trufflehog). |
|
|
335
347
|
| **Filter-chain order and per-filter URL patterns — the presence of a custom filter is structural only.** | Which filter runs first is decided by bean ordering this analyzer does not resolve. Where two active configurations both match a request, the answer published is `undecided` rather than a guess. | `ask posture --profile <set>` states per endpoint what the readable rules decide and what they leave undecided. |
|
|
336
348
|
| **Known vulnerabilities in dependencies — no CVE database, no version advisory matching.** | Enriching a vulnerability feed is a different product with a different update cadence; a stale embedded database is worse than no database, because it reads as a clean bill of health. | A dependency scanner (Trivy, OWASP Dependency-Check). `impact-chain` then answers which of its findings anything actually reaches. |
|
|
337
349
|
|
|
@@ -59,13 +59,25 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
59
59
|
# pip / pipx
|
|
60
60
|
pipx install sourcecode # or: pip install sourcecode
|
|
61
61
|
|
|
62
|
-
ask version # ask
|
|
62
|
+
ask version # ask 4.1.0 — and, on a build that has aged,
|
|
63
|
+
# how many releases have probably shipped since
|
|
63
64
|
```
|
|
64
65
|
|
|
65
66
|
> **Package vs. command.** The install package is named `sourcecode` this release
|
|
66
67
|
> (renaming the distribution is a separate, breaking change). Installing it gives you the
|
|
67
68
|
> canonical **`ask`** command plus the deprecated **`sourcecode`** alias.
|
|
68
69
|
|
|
70
|
+
> **Platforms.** Developed and tested on macOS and Linux; every release runs its full
|
|
71
|
+
> battery there. **Windows/PowerShell is a target we cannot verify:** the CLI is written
|
|
72
|
+
> for it — UTF-8 streams are forced at both console entry points, a successful run writes
|
|
73
|
+
> nothing to stderr (PowerShell 5.1 raises `NativeCommandError` on any stderr write), and
|
|
74
|
+
> the MCP server strips the BOM PowerShell puts on stdin — but no Windows runner exists in
|
|
75
|
+
> the battery. Three separate field reports of console mojibake (`—` rendering as `â€"`)
|
|
76
|
+
> have never reproduced under measurement, and the only variable those reports share is the
|
|
77
|
+
> platform we cannot exercise (C3-3, C3-12, C3-38 in the
|
|
78
|
+
> [defect ledger](docs/DEFECT-LEDGER.md)). On Windows, prefer `--output <file>` over shell
|
|
79
|
+
> redirection, and report a rendering problem rather than assuming it is known.
|
|
80
|
+
|
|
69
81
|
---
|
|
70
82
|
|
|
71
83
|
## Quickstart
|
|
@@ -293,7 +305,7 @@ reading JSON sees the same boundary a buyer reads here.
|
|
|
293
305
|
| Not covered | Why | What answers it |
|
|
294
306
|
|---|---|---|
|
|
295
307
|
| **Whether request input reaches a sink — no dataflow or taint analysis.** | This engine resolves structure and wiring without compiling. Taint needs value flow through a program, which is a different analysis with a different failure mode: an unsound one produces confident findings that are wrong, and every claim here is meant to be checkable against the line that produced it. | A dataflow scanner (Semgrep, CodeQL). What this product adds on top is reachability: which of that tool's findings sit behind an endpoint that is reachable unauthenticated. |
|
|
296
|
-
| **Secrets outside Java
|
|
308
|
+
| **Secrets outside Java, Spring configuration and the deployment descriptors — a Helm value, a CI variable file, a shell script.** | The file population this analyzer reads is the Java source, the Spring configuration convention and the descriptors it already parses to resolve an environment. A secret elsewhere is not missed by a weak rule; it is outside the set of files anything here opens. Narrowed by CL-10: a credential stored in `web.xml` or a Spring XML context IS reported (SEC-007), because those files were already being read. | A dedicated secret scanner over the whole tree (gitleaks, trufflehog). |
|
|
297
309
|
| **Filter-chain order and per-filter URL patterns — the presence of a custom filter is structural only.** | Which filter runs first is decided by bean ordering this analyzer does not resolve. Where two active configurations both match a request, the answer published is `undecided` rather than a guess. | `ask posture --profile <set>` states per endpoint what the readable rules decide and what they leave undecided. |
|
|
298
310
|
| **Known vulnerabilities in dependencies — no CVE database, no version advisory matching.** | Enriching a vulnerability feed is a different product with a different update cadence; a stale embedded database is worse than no database, because it reads as a clean bill of health. | A dependency scanner (Trivy, OWASP Dependency-Check). `impact-chain` then answers which of its findings anything actually reaches. |
|
|
299
311
|
|
|
@@ -8934,8 +8934,16 @@ def version_cmd() -> None:
|
|
|
8934
8934
|
{"cli_version": "1.33.11", "mcp_schema_version": "1.33.11",
|
|
8935
8935
|
"compatibility_schema_version": "1.0"}
|
|
8936
8936
|
"""
|
|
8937
|
+
from sourcecode.release_info import advisory_line, build_status
|
|
8938
|
+
|
|
8937
8939
|
if getattr(sys.stdout, "isatty", lambda: False)():
|
|
8938
8940
|
typer.echo(f"ask {__version__}")
|
|
8941
|
+
# C4-12: the field audits builds that are days old and many releases
|
|
8942
|
+
# behind, and nothing said so. Printed only when it applies — an advisory
|
|
8943
|
+
# on every run is one nobody reads.
|
|
8944
|
+
_advisory = advisory_line()
|
|
8945
|
+
if _advisory:
|
|
8946
|
+
typer.echo(_advisory)
|
|
8939
8947
|
else:
|
|
8940
8948
|
import json as _json_ver
|
|
8941
8949
|
from sourcecode.envelope import ENVELOPE_VERSION, available_schemas
|
|
@@ -8943,6 +8951,10 @@ def version_cmd() -> None:
|
|
|
8943
8951
|
"cli_version": __version__,
|
|
8944
8952
|
"mcp_schema_version": __version__,
|
|
8945
8953
|
"compatibility_schema_version": "1.0",
|
|
8954
|
+
# Which release this is, when it shipped, and how far behind it
|
|
8955
|
+
# probably is. The estimate carries its basis and never claims a
|
|
8956
|
+
# newer release exists (C4-12).
|
|
8957
|
+
"build": build_status(),
|
|
8946
8958
|
# What a consumer should actually pin against: the release string
|
|
8947
8959
|
# changes every release, the envelope version only when the output
|
|
8948
8960
|
# contract does.
|
|
@@ -123,6 +123,16 @@ def build_meta(
|
|
|
123
123
|
"tool_version": __version__,
|
|
124
124
|
"command": command,
|
|
125
125
|
}
|
|
126
|
+
# C4-12: a consumer holding a payload could not tell how old the build that
|
|
127
|
+
# produced it was. The DATE is a constant of the build, so it goes here; the
|
|
128
|
+
# AGE changes daily and would break the byte-identical property above, so it
|
|
129
|
+
# is derived where it is asked for (`ask version`), never in the envelope.
|
|
130
|
+
try:
|
|
131
|
+
from sourcecode.release_info import RELEASE_DATE
|
|
132
|
+
|
|
133
|
+
meta["tool_release_date"] = RELEASE_DATE
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
126
136
|
if schema_version:
|
|
127
137
|
meta["schema_version"] = schema_version
|
|
128
138
|
|
|
@@ -74,13 +74,16 @@ NON_COVERAGE: tuple[NonCoverage, ...] = (
|
|
|
74
74
|
id="NC-002",
|
|
75
75
|
surface="security_surface",
|
|
76
76
|
not_covered=(
|
|
77
|
-
"Secrets outside Java
|
|
78
|
-
"
|
|
77
|
+
"Secrets outside Java, Spring configuration and the deployment "
|
|
78
|
+
"descriptors — a Helm value, a CI variable file, a shell script."
|
|
79
79
|
),
|
|
80
80
|
why=(
|
|
81
|
-
"The file population this analyzer reads is the Java source
|
|
82
|
-
"
|
|
83
|
-
"
|
|
81
|
+
"The file population this analyzer reads is the Java source, the Spring "
|
|
82
|
+
"configuration convention and the descriptors it already parses to "
|
|
83
|
+
"resolve an environment. A secret elsewhere is not missed by a weak "
|
|
84
|
+
"rule; it is outside the set of files anything here opens. Narrowed by "
|
|
85
|
+
"CL-10: a credential stored in `web.xml` or a Spring XML context IS "
|
|
86
|
+
"reported (SEC-007), because those files were already being read."
|
|
84
87
|
),
|
|
85
88
|
instead="A dedicated secret scanner over the whole tree (gitleaks, trufflehog).",
|
|
86
89
|
),
|
|
@@ -381,6 +381,87 @@ def resolve_beans(
|
|
|
381
381
|
)
|
|
382
382
|
|
|
383
383
|
|
|
384
|
+
#: CL-11 — container settings that make the resolution below a DEFAULT rather
|
|
385
|
+
#: than a description of what runs. Each says what the container will no longer
|
|
386
|
+
#: report, in the terms of the verdict it qualifies. Published Spring property
|
|
387
|
+
#: names, so this is framework vocabulary, never a client's own configuration.
|
|
388
|
+
_OBSERVABILITY_AMPLIFIERS: "tuple[tuple[str, str], ...]" = (
|
|
389
|
+
(
|
|
390
|
+
"spring.main.allow-bean-definition-overriding",
|
|
391
|
+
"a second bean definition for a name resolved here REPLACES it instead of "
|
|
392
|
+
"failing at startup, so a security configuration can be overridden by one "
|
|
393
|
+
"this resolution never saw and nothing in the deployment reports the "
|
|
394
|
+
"disagreement",
|
|
395
|
+
),
|
|
396
|
+
(
|
|
397
|
+
"spring.main.allow-circular-references",
|
|
398
|
+
"a wiring cycle through the beans resolved here is repaired at runtime by "
|
|
399
|
+
"injecting a proxy rather than refused, so the instance that ends up in the "
|
|
400
|
+
"chain need not be the one this resolution describes",
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
#: Values Spring reads as "on". Compared case-insensitively, as Spring does.
|
|
405
|
+
_TRUTHY = frozenset({"true", "yes", "on", "1"})
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def container_observability(properties: "EffectiveProperties") -> dict:
|
|
409
|
+
"""Whether the container would report a disagreement with this resolution (CL-11).
|
|
410
|
+
|
|
411
|
+
Audit #8 found `allow-bean-definition-overriding: true` and
|
|
412
|
+
`allow-circular-references: true` in the repository whose entire security
|
|
413
|
+
verdict turns on which `SecurityConfig` bean wins. On its own each is a
|
|
414
|
+
preference; beside a conditional security verdict each is the reason that
|
|
415
|
+
verdict is a **default and not a guarantee** — with overriding enabled, a
|
|
416
|
+
silent override of the filter chain does not fail at startup, so the one
|
|
417
|
+
check that would have caught it never runs.
|
|
418
|
+
|
|
419
|
+
This block only ever LOWERS confidence. It is emitted beside the verdict it
|
|
420
|
+
qualifies rather than as a finding underneath it, because the C1-20 lesson
|
|
421
|
+
runs the other way here: a caveat a reader has to go and find is a caveat
|
|
422
|
+
that does not reach the conclusion it was written for.
|
|
423
|
+
"""
|
|
424
|
+
amplifiers: "list[dict]" = []
|
|
425
|
+
for key, effect in _OBSERVABILITY_AMPLIFIERS:
|
|
426
|
+
blocked = properties.blocked(key)
|
|
427
|
+
if blocked:
|
|
428
|
+
# The property exists and nothing decides it. That is not "off": an
|
|
429
|
+
# undecidable safety setting is exactly the case where claiming the
|
|
430
|
+
# safe default would be the confident falsehood.
|
|
431
|
+
amplifiers.append({
|
|
432
|
+
"property": key, "value": None, "state": "undecidable",
|
|
433
|
+
"reason": blocked, "source": None, "profile": None, "effect": effect,
|
|
434
|
+
})
|
|
435
|
+
continue
|
|
436
|
+
found = properties.lookup(key)
|
|
437
|
+
if found is None or found.value.strip().lower() not in _TRUTHY:
|
|
438
|
+
continue
|
|
439
|
+
amplifiers.append({
|
|
440
|
+
"property": key, "value": found.value, "state": "enabled",
|
|
441
|
+
"reason": None, "source": found.source, "profile": found.profile,
|
|
442
|
+
"effect": effect,
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
block: dict = {
|
|
446
|
+
"weakens_this_resolution": bool(amplifiers),
|
|
447
|
+
"amplifiers": amplifiers,
|
|
448
|
+
}
|
|
449
|
+
if amplifiers:
|
|
450
|
+
block["reading"] = (
|
|
451
|
+
"The bean resolution in this payload describes the configuration this "
|
|
452
|
+
"repository declares. " + str(len(amplifiers)) + " container setting(s) "
|
|
453
|
+
"mean the deployment would not report a disagreement with it, so read "
|
|
454
|
+
"every verdict below as a default rather than as what runs."
|
|
455
|
+
)
|
|
456
|
+
else:
|
|
457
|
+
block["reading"] = (
|
|
458
|
+
"No container setting was found that suppresses a startup failure over "
|
|
459
|
+
"the beans resolved here; a disagreement with this resolution would "
|
|
460
|
+
"surface as a failure to start."
|
|
461
|
+
)
|
|
462
|
+
return block
|
|
463
|
+
|
|
464
|
+
|
|
384
465
|
#: Published Spring Security vocabulary that makes a bean part of the request
|
|
385
466
|
#: chain's authorization configuration. Recorded as evidence; never branched on
|
|
386
467
|
#: a client's own class name.
|
|
@@ -1017,11 +1098,20 @@ def _posture(
|
|
|
1017
1098
|
"supplied": sorted(properties or {}),
|
|
1018
1099
|
},
|
|
1019
1100
|
**resolution.to_dict(),
|
|
1101
|
+
# CL-11. Whether the container would even report a disagreement with the
|
|
1102
|
+
# resolution above. Beside it, not underneath it: this block only lowers
|
|
1103
|
+
# confidence in the verdicts that follow, and a caveat a reader has to go
|
|
1104
|
+
# looking for does not reach the conclusion it was written for.
|
|
1105
|
+
"container": container_observability(resolved_properties),
|
|
1020
1106
|
# Called out separately because a profile that switches the security
|
|
1021
1107
|
# configuration is not one bean among many — it is the difference
|
|
1022
1108
|
# between an authenticated surface and an open one.
|
|
1023
1109
|
"security": security_projection(cir, resolution),
|
|
1024
1110
|
}
|
|
1111
|
+
# The verdict a reader acts on is the security slice, so the qualifier
|
|
1112
|
+
# travels with it rather than only at the top of the document.
|
|
1113
|
+
if payload["container"]["weakens_this_resolution"]:
|
|
1114
|
+
payload["security"]["resolution_is_a_default"] = payload["container"]["reading"]
|
|
1025
1115
|
payload["endpoints"] = endpoint_projection(cir, payload["security"])
|
|
1026
1116
|
# Which chain is wired is a fact about a bean; what that chain permits is the
|
|
1027
1117
|
# fact about the request, and it is the one a reviewer asked for.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""What this build is, and how far behind it is likely to be (C4-12).
|
|
2
|
+
|
|
3
|
+
Field audit #8 ran a build self-reporting 3.5.0 while 4.0.1 was shipping and
|
|
4
|
+
re-derived, by hand, twelve product complaints from rows already closed — one of
|
|
5
|
+
them the answer to its own critical finding. Nothing in the output told the
|
|
6
|
+
operator their build was eight releases behind, so remediation speed, the
|
|
7
|
+
property `docs/DEFECT-LEDGER.md` exists to make visible, was invisible to the
|
|
8
|
+
person it was for.
|
|
9
|
+
|
|
10
|
+
Age in days is the wrong unit on its own, and measuring it is what shows why:
|
|
11
|
+
that build was **three days old**. On this project's cadence three days is six
|
|
12
|
+
releases. So the fact published here is the release date (a constant, in the
|
|
13
|
+
build), and the derived figure is an **estimate** of how many releases have
|
|
14
|
+
shipped since — labelled as an estimate, because a CLI cannot see an index it
|
|
15
|
+
has no network to read. It never claims a newer release exists.
|
|
16
|
+
|
|
17
|
+
`_meta.tool_release_date` carries the constant half; the envelope is
|
|
18
|
+
byte-identical across runs by contract, so the age — which changes every day —
|
|
19
|
+
is computed only where it is asked for, in `ask version`.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import datetime
|
|
25
|
+
from typing import Any, Optional
|
|
26
|
+
|
|
27
|
+
#: The date `__version__` was released, from that version's CHANGELOG entry.
|
|
28
|
+
#: `tests/test_release_info.py` fails the build if the two disagree, so this is
|
|
29
|
+
#: a copy of a fact rather than a second authority for it.
|
|
30
|
+
RELEASE_DATE = "2026-08-03"
|
|
31
|
+
|
|
32
|
+
#: Releases per day, measured over the 30 releases before this one (the same
|
|
33
|
+
#: computation the test re-runs against the CHANGELOG). Stable across window
|
|
34
|
+
#: sizes when it was taken — 2.25 over 10, 2.11 over 20, 2.07 over 30 — which is
|
|
35
|
+
#: why a single figure is publishable at all.
|
|
36
|
+
RELEASE_RATE_PER_DAY = 2.07
|
|
37
|
+
|
|
38
|
+
#: How many releases behind is worth telling the operator about. Four, which on
|
|
39
|
+
#: the measured cadence is about two days: one release is half a day and warning
|
|
40
|
+
#: about it would put a line on almost every run, which is how an advisory stops
|
|
41
|
+
#: being read. The field case — three days, an estimated six releases, eight in
|
|
42
|
+
#: fact — is well past it. An assumption, not a measurement.
|
|
43
|
+
STALE_AFTER_RELEASES = 4
|
|
44
|
+
|
|
45
|
+
#: What the estimate is, in the words the payload uses. The rate is a
|
|
46
|
+
#: measurement of the past, not a promise about the future, and the distinction
|
|
47
|
+
#: is the whole reason this string is published beside the number.
|
|
48
|
+
ESTIMATE_BASIS = (
|
|
49
|
+
f"{RELEASE_RATE_PER_DAY} releases/day, measured over the 30 releases before "
|
|
50
|
+
f"this one. An estimate of what has shipped since, not an observation: this "
|
|
51
|
+
f"build has no index to read and never claims a newer release exists."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
#: Where this version's known-wrong answers are written down.
|
|
55
|
+
DEFECT_LEDGER = "docs/DEFECT-LEDGER.md"
|
|
56
|
+
|
|
57
|
+
#: How to move off this build. Named here so the advisory cannot drift from the
|
|
58
|
+
#: distribution name in `pyproject.toml`.
|
|
59
|
+
UPGRADE_COMMAND = "pip install -U sourcecode"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _today() -> datetime.date:
|
|
63
|
+
return datetime.date.today()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def release_age_days(today: "Optional[datetime.date]" = None) -> int:
|
|
67
|
+
"""Days between this build's release and *today*, never negative.
|
|
68
|
+
|
|
69
|
+
A build produced from a working tree ahead of the last CHANGELOG entry, or a
|
|
70
|
+
machine whose clock is behind, reads as age 0 rather than as a negative
|
|
71
|
+
number that would flow into the estimate as a negative release count.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
released = datetime.date.fromisoformat(RELEASE_DATE)
|
|
75
|
+
except ValueError:
|
|
76
|
+
return 0
|
|
77
|
+
return max(0, ((today or _today()) - released).days)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def estimated_releases_since(today: "Optional[datetime.date]" = None) -> int:
|
|
81
|
+
"""Releases likely shipped since this build. Rounded down: a floor, not a guess up."""
|
|
82
|
+
return int(release_age_days(today) * RELEASE_RATE_PER_DAY)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_status(today: "Optional[datetime.date]" = None) -> "dict[str, Any]":
|
|
86
|
+
"""What this build is and how far behind it probably is.
|
|
87
|
+
|
|
88
|
+
`advisory` is `None` on a fresh build — a line that appears on every run is a
|
|
89
|
+
line nobody reads, and this one exists to be read once.
|
|
90
|
+
"""
|
|
91
|
+
from sourcecode import __version__
|
|
92
|
+
|
|
93
|
+
age = release_age_days(today)
|
|
94
|
+
behind = estimated_releases_since(today)
|
|
95
|
+
stale = behind >= STALE_AFTER_RELEASES
|
|
96
|
+
|
|
97
|
+
status: dict[str, Any] = {
|
|
98
|
+
"version": __version__,
|
|
99
|
+
"released": RELEASE_DATE,
|
|
100
|
+
"age_days": age,
|
|
101
|
+
"estimated_releases_since": behind,
|
|
102
|
+
"estimate_basis": ESTIMATE_BASIS,
|
|
103
|
+
"stale": stale,
|
|
104
|
+
"known_defects": (
|
|
105
|
+
f"{DEFECT_LEDGER} records what this version is known to get wrong. "
|
|
106
|
+
f"Rows closed after it shipped are not in this copy."
|
|
107
|
+
),
|
|
108
|
+
"advisory": None,
|
|
109
|
+
}
|
|
110
|
+
if stale:
|
|
111
|
+
status["advisory"] = (
|
|
112
|
+
f"This build is {age} day{'s' if age != 1 else ''} old and this project "
|
|
113
|
+
f"released {RELEASE_RATE_PER_DAY} times a day over its last 30 releases "
|
|
114
|
+
f"(~{behind} since). Upgrade before reading an absent finding as a "
|
|
115
|
+
f"clean result: `{UPGRADE_COMMAND}`. What this version is known to get "
|
|
116
|
+
f"wrong is in {DEFECT_LEDGER}."
|
|
117
|
+
)
|
|
118
|
+
return status
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def advisory_line(today: "Optional[datetime.date]" = None) -> "Optional[str]":
|
|
122
|
+
"""The one-line human form of `build_status()['advisory']`, or None when fresh."""
|
|
123
|
+
return build_status(today)["advisory"]
|
|
@@ -54,6 +54,7 @@ RULES: tuple[Rule, ...] = (
|
|
|
54
54
|
Rule("SEC-004", "SEC", "Password stored under a fast unsalted digest (SHA-1, MD5)"),
|
|
55
55
|
Rule("SEC-005", "SEC", "CSRF disabled under session-bearing authentication"),
|
|
56
56
|
Rule("SEC-006", "SEC", "Cookie created without the Secure attribute"),
|
|
57
|
+
Rule("SEC-007", "SEC", "Credential stored in a deployment descriptor"),
|
|
57
58
|
)
|
|
58
59
|
|
|
59
60
|
FAMILY_ORDER: tuple[str, ...] = ("TX", "SEC")
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
"description": "Release that produced the payload.",
|
|
20
20
|
"type": "string"
|
|
21
21
|
},
|
|
22
|
+
"tool_release_date": {
|
|
23
|
+
"description": "Date the release that produced this payload shipped (ISO 8601). A constant of the build, so the envelope stays byte-identical across runs; the AGE is derived by the consumer, or printed by `ask version`, which also estimates how many releases have shipped since.",
|
|
24
|
+
"type": "string",
|
|
25
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
|
|
26
|
+
},
|
|
22
27
|
"command": {
|
|
23
28
|
"description": "Full path through the command tree, e.g. \"retrieve endpoint-inventory\".",
|
|
24
29
|
"type": "string",
|
|
@@ -27,7 +32,9 @@
|
|
|
27
32
|
"scope": {
|
|
28
33
|
"description": "What was analysed. Absent when the command addresses no path.",
|
|
29
34
|
"type": "object",
|
|
30
|
-
"required": [
|
|
35
|
+
"required": [
|
|
36
|
+
"path"
|
|
37
|
+
],
|
|
31
38
|
"properties": {
|
|
32
39
|
"path": {
|
|
33
40
|
"description": "Absolute path the command was pointed at.",
|