wardline 0.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.
- wardline-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +23 -0
- wardline-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +14 -0
- wardline-0.1.0/.github/dependabot.yml +18 -0
- wardline-0.1.0/.github/workflows/ci.yml +67 -0
- wardline-0.1.0/.github/workflows/release.yml +40 -0
- wardline-0.1.0/.gitignore +23 -0
- wardline-0.1.0/CHANGELOG.md +41 -0
- wardline-0.1.0/CODE_OF_CONDUCT.md +7 -0
- wardline-0.1.0/CONTRIBUTING.md +27 -0
- wardline-0.1.0/LICENSE +21 -0
- wardline-0.1.0/PKG-INFO +39 -0
- wardline-0.1.0/README.md +3 -0
- wardline-0.1.0/SECURITY.md +11 -0
- wardline-0.1.0/docs/integration/2026-05-29-wardline-loom-integration-brief.md +253 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-29-wardline-sp0-skeleton.md +1204 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-29-wardline-sp1a-qualnames.md +1024 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-29-wardline-sp1b-taint-lattice-l1.md +850 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-29-wardline-sp1c-l2-minimum-scope.md +833 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-l2-match-handling.md +319 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp1d-callgraph-propagation.md +1530 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp1e-summary-cache.md +751 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp1f-analyzer-wiring.md +1241 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp2a-vocabulary-registry.md +538 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp2b-decorator-provider.md +495 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp2c-rules-and-severity.md +1730 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp2d-vocabulary-descriptor.md +331 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp3-baseline-and-waivers.md +1135 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp4-outputs-and-loom.md +940 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp5-llm-triage-judge.md +2079 -0
- wardline-0.1.0/docs/superpowers/plans/2026-05-30-wardline-sp6-release-engineering.md +788 -0
- wardline-0.1.0/docs/superpowers/specs/2026-05-29-wardline-sp0-skeleton-design.md +261 -0
- wardline-0.1.0/docs/superpowers/specs/2026-05-29-wardline-sp1-analyzer-core-design.md +149 -0
- wardline-0.1.0/docs/superpowers/specs/2026-05-30-wardline-sp2-rules-and-vocabulary-design.md +148 -0
- wardline-0.1.0/docs/superpowers/specs/2026-05-30-wardline-sp3-baseline-and-waivers-design.md +196 -0
- wardline-0.1.0/docs/superpowers/specs/2026-05-30-wardline-sp4-outputs-and-loom-design.md +333 -0
- wardline-0.1.0/docs/superpowers/specs/2026-05-30-wardline-sp5-llm-triage-judge-design.md +406 -0
- wardline-0.1.0/docs/superpowers/specs/2026-05-30-wardline-sp6-release-engineering-design.md +204 -0
- wardline-0.1.0/pyproject.toml +72 -0
- wardline-0.1.0/src/wardline/__init__.py +5 -0
- wardline-0.1.0/src/wardline/_version.py +1 -0
- wardline-0.1.0/src/wardline/cli/__init__.py +0 -0
- wardline-0.1.0/src/wardline/cli/judge.py +175 -0
- wardline-0.1.0/src/wardline/cli/main.py +101 -0
- wardline-0.1.0/src/wardline/cli/scan.py +106 -0
- wardline-0.1.0/src/wardline/core/__init__.py +0 -0
- wardline-0.1.0/src/wardline/core/baseline.py +98 -0
- wardline-0.1.0/src/wardline/core/config.py +118 -0
- wardline-0.1.0/src/wardline/core/config_schema.py +44 -0
- wardline-0.1.0/src/wardline/core/descriptor.py +53 -0
- wardline-0.1.0/src/wardline/core/discovery.py +39 -0
- wardline-0.1.0/src/wardline/core/emit.py +26 -0
- wardline-0.1.0/src/wardline/core/errors.py +29 -0
- wardline-0.1.0/src/wardline/core/filigree_emit.py +164 -0
- wardline-0.1.0/src/wardline/core/finding.py +150 -0
- wardline-0.1.0/src/wardline/core/judge.py +432 -0
- wardline-0.1.0/src/wardline/core/judged.py +153 -0
- wardline-0.1.0/src/wardline/core/protocols.py +23 -0
- wardline-0.1.0/src/wardline/core/qualname.py +104 -0
- wardline-0.1.0/src/wardline/core/registry.py +65 -0
- wardline-0.1.0/src/wardline/core/sarif.py +116 -0
- wardline-0.1.0/src/wardline/core/source_excerpt.py +37 -0
- wardline-0.1.0/src/wardline/core/suppression.py +72 -0
- wardline-0.1.0/src/wardline/core/taints.py +94 -0
- wardline-0.1.0/src/wardline/core/triage.py +100 -0
- wardline-0.1.0/src/wardline/core/vocabulary.yaml +13 -0
- wardline-0.1.0/src/wardline/core/waivers.py +79 -0
- wardline-0.1.0/src/wardline/decorators/__init__.py +8 -0
- wardline-0.1.0/src/wardline/decorators/_base.py +76 -0
- wardline-0.1.0/src/wardline/decorators/trust.py +73 -0
- wardline-0.1.0/src/wardline/py.typed +0 -0
- wardline-0.1.0/src/wardline/scanner/__init__.py +22 -0
- wardline-0.1.0/src/wardline/scanner/analyzer.py +241 -0
- wardline-0.1.0/src/wardline/scanner/ast_primitives.py +195 -0
- wardline-0.1.0/src/wardline/scanner/context.py +87 -0
- wardline-0.1.0/src/wardline/scanner/diagnostics.py +265 -0
- wardline-0.1.0/src/wardline/scanner/index.py +113 -0
- wardline-0.1.0/src/wardline/scanner/rules/__init__.py +47 -0
- wardline-0.1.0/src/wardline/scanner/rules/_ast_helpers.py +104 -0
- wardline-0.1.0/src/wardline/scanner/rules/boundary_without_rejection.py +84 -0
- wardline-0.1.0/src/wardline/scanner/rules/broad_exception.py +71 -0
- wardline-0.1.0/src/wardline/scanner/rules/metadata.py +21 -0
- wardline-0.1.0/src/wardline/scanner/rules/severity_model.py +38 -0
- wardline-0.1.0/src/wardline/scanner/rules/silent_exception.py +70 -0
- wardline-0.1.0/src/wardline/scanner/rules/untrusted_reaches_trusted.py +101 -0
- wardline-0.1.0/src/wardline/scanner/taint/__init__.py +1 -0
- wardline-0.1.0/src/wardline/scanner/taint/call_taint_map.py +94 -0
- wardline-0.1.0/src/wardline/scanner/taint/callgraph.py +76 -0
- wardline-0.1.0/src/wardline/scanner/taint/decorator_provider.py +162 -0
- wardline-0.1.0/src/wardline/scanner/taint/function_level.py +67 -0
- wardline-0.1.0/src/wardline/scanner/taint/minimum_scope.py +173 -0
- wardline-0.1.0/src/wardline/scanner/taint/module_summariser.py +61 -0
- wardline-0.1.0/src/wardline/scanner/taint/project_resolver.py +189 -0
- wardline-0.1.0/src/wardline/scanner/taint/propagation.py +704 -0
- wardline-0.1.0/src/wardline/scanner/taint/provider.py +76 -0
- wardline-0.1.0/src/wardline/scanner/taint/resolver_metadata.py +88 -0
- wardline-0.1.0/src/wardline/scanner/taint/reverse_edge_index.py +80 -0
- wardline-0.1.0/src/wardline/scanner/taint/stdlib_taint.py +105 -0
- wardline-0.1.0/src/wardline/scanner/taint/stdlib_taint.yaml +67 -0
- wardline-0.1.0/src/wardline/scanner/taint/summary.py +95 -0
- wardline-0.1.0/src/wardline/scanner/taint/summary_cache.py +205 -0
- wardline-0.1.0/src/wardline/scanner/taint/variable_level.py +675 -0
- wardline-0.1.0/tests/conformance/__init__.py +0 -0
- wardline-0.1.0/tests/conformance/clarion_qualname_parity.json +145 -0
- wardline-0.1.0/tests/conformance/qualnames.json +107 -0
- wardline-0.1.0/tests/conformance/test_clarion_qualname_parity.py +61 -0
- wardline-0.1.0/tests/conformance/test_qualname_conformance.py +42 -0
- wardline-0.1.0/tests/conftest.py +1 -0
- wardline-0.1.0/tests/e2e/test_judge_live.py +33 -0
- wardline-0.1.0/tests/fixtures/sample_project/src/pkg/__init__.py +0 -0
- wardline-0.1.0/tests/fixtures/sample_project/src/pkg/mod.py +2 -0
- wardline-0.1.0/tests/fixtures/sample_project/wardline.yaml +1 -0
- wardline-0.1.0/tests/test_self_hosting.py +25 -0
- wardline-0.1.0/tests/unit/cli/test_cli.py +542 -0
- wardline-0.1.0/tests/unit/core/test_baseline.py +98 -0
- wardline-0.1.0/tests/unit/core/test_config.py +166 -0
- wardline-0.1.0/tests/unit/core/test_descriptor.py +71 -0
- wardline-0.1.0/tests/unit/core/test_discovery.py +28 -0
- wardline-0.1.0/tests/unit/core/test_emit.py +31 -0
- wardline-0.1.0/tests/unit/core/test_errors.py +13 -0
- wardline-0.1.0/tests/unit/core/test_filigree_emit.py +192 -0
- wardline-0.1.0/tests/unit/core/test_finding.py +120 -0
- wardline-0.1.0/tests/unit/core/test_judge.py +288 -0
- wardline-0.1.0/tests/unit/core/test_judged.py +85 -0
- wardline-0.1.0/tests/unit/core/test_loom_mapping.py +55 -0
- wardline-0.1.0/tests/unit/core/test_qualname.py +111 -0
- wardline-0.1.0/tests/unit/core/test_registry.py +40 -0
- wardline-0.1.0/tests/unit/core/test_sarif.py +105 -0
- wardline-0.1.0/tests/unit/core/test_source_excerpt.py +39 -0
- wardline-0.1.0/tests/unit/core/test_suppression.py +125 -0
- wardline-0.1.0/tests/unit/core/test_taints.py +77 -0
- wardline-0.1.0/tests/unit/core/test_triage.py +102 -0
- wardline-0.1.0/tests/unit/core/test_waivers.py +59 -0
- wardline-0.1.0/tests/unit/decorators/__init__.py +0 -0
- wardline-0.1.0/tests/unit/decorators/test_base.py +117 -0
- wardline-0.1.0/tests/unit/decorators/test_trust.py +74 -0
- wardline-0.1.0/tests/unit/scanner/rules/__init__.py +0 -0
- wardline-0.1.0/tests/unit/scanner/rules/test_ast_helpers.py +66 -0
- wardline-0.1.0/tests/unit/scanner/rules/test_boundary_without_rejection.py +71 -0
- wardline-0.1.0/tests/unit/scanner/rules/test_broad_exception.py +80 -0
- wardline-0.1.0/tests/unit/scanner/rules/test_default_registry.py +55 -0
- wardline-0.1.0/tests/unit/scanner/rules/test_severity_model.py +31 -0
- wardline-0.1.0/tests/unit/scanner/rules/test_silent_exception.py +59 -0
- wardline-0.1.0/tests/unit/scanner/rules/test_untrusted_reaches_trusted.py +145 -0
- wardline-0.1.0/tests/unit/scanner/rules/test_vocabulary_shape_pin.py +39 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_call_taint_map.py +129 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_callgraph.py +104 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_decorator_provider.py +164 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_function_level.py +71 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_minimum_scope.py +140 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_module_summariser.py +62 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_project_resolver.py +287 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_propagation.py +126 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_provider.py +54 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_provider_seedcontext.py +23 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_resolver_metadata.py +53 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_reverse_edge_index.py +62 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_stdlib_taint.py +107 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_summary.py +84 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_summary_cache.py +128 -0
- wardline-0.1.0/tests/unit/scanner/taint/test_variable_level.py +282 -0
- wardline-0.1.0/tests/unit/scanner/test_analyzer.py +199 -0
- wardline-0.1.0/tests/unit/scanner/test_ast_primitives.py +231 -0
- wardline-0.1.0/tests/unit/scanner/test_context.py +60 -0
- wardline-0.1.0/tests/unit/scanner/test_diagnostics.py +98 -0
- wardline-0.1.0/tests/unit/scanner/test_index.py +160 -0
- wardline-0.1.0/tests/unit/scanner/test_noop.py +9 -0
- wardline-0.1.0/tests/unit/test_package.py +6 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Report incorrect behaviour in Wardline
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what-happened
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened?
|
|
9
|
+
description: What did you run, what did you expect, what did you get?
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: input
|
|
13
|
+
id: version
|
|
14
|
+
attributes:
|
|
15
|
+
label: Wardline version
|
|
16
|
+
placeholder: "0.1.0"
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: repro
|
|
21
|
+
attributes:
|
|
22
|
+
label: Minimal reproduction
|
|
23
|
+
description: A code snippet or config that triggers the issue.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Suggest an improvement
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: What problem are you trying to solve?
|
|
9
|
+
validations:
|
|
10
|
+
required: true
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: proposal
|
|
13
|
+
attributes:
|
|
14
|
+
label: Proposed solution
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "pip"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
open-pull-requests-limit: 5
|
|
8
|
+
groups:
|
|
9
|
+
python-minor-patch:
|
|
10
|
+
update-types: ["minor", "patch"]
|
|
11
|
+
- package-ecosystem: "github-actions"
|
|
12
|
+
directory: "/"
|
|
13
|
+
schedule:
|
|
14
|
+
interval: "weekly"
|
|
15
|
+
open-pull-requests-limit: 5
|
|
16
|
+
groups:
|
|
17
|
+
actions-minor-patch:
|
|
18
|
+
update-types: ["minor", "patch"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "0 2 * * 0"
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
security-events: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
gate:
|
|
16
|
+
name: Tests + Lint + Types
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- name: Install
|
|
24
|
+
run: python -m pip install -e ".[dev]"
|
|
25
|
+
- name: Ruff
|
|
26
|
+
run: ruff check src tests
|
|
27
|
+
- name: Mypy
|
|
28
|
+
run: mypy src
|
|
29
|
+
- name: Pytest
|
|
30
|
+
run: pytest -q
|
|
31
|
+
|
|
32
|
+
self-hosting-scan:
|
|
33
|
+
name: Self-Hosting Scan (dogfood)
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
needs: gate
|
|
36
|
+
if: github.event_name != 'schedule'
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- uses: actions/setup-python@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.12"
|
|
42
|
+
- name: Install
|
|
43
|
+
run: python -m pip install -e ".[dev]"
|
|
44
|
+
- name: Scan self -> SARIF
|
|
45
|
+
run: wardline scan src/wardline --format sarif --output results.sarif
|
|
46
|
+
- name: Upload SARIF
|
|
47
|
+
if: always()
|
|
48
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
49
|
+
with:
|
|
50
|
+
sarif_file: results.sarif
|
|
51
|
+
category: wardline-self-hosting
|
|
52
|
+
|
|
53
|
+
network:
|
|
54
|
+
name: Live judge e2e (weekly)
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
if: github.event_name == 'schedule'
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
- uses: actions/setup-python@v5
|
|
60
|
+
with:
|
|
61
|
+
python-version: "3.12"
|
|
62
|
+
- name: Install
|
|
63
|
+
run: python -m pip install -e ".[dev]"
|
|
64
|
+
- name: Network tests
|
|
65
|
+
run: pytest -m network -v
|
|
66
|
+
env:
|
|
67
|
+
WARDLINE_OPENROUTER_API_KEY: ${{ secrets.WARDLINE_OPENROUTER_API_KEY }}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Build distributions
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- name: Build
|
|
20
|
+
run: |
|
|
21
|
+
python -m pip install build
|
|
22
|
+
python -m build
|
|
23
|
+
- uses: actions/upload-artifact@v4
|
|
24
|
+
with:
|
|
25
|
+
name: dist
|
|
26
|
+
path: dist/
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
name: Publish to PyPI
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
needs: build
|
|
32
|
+
environment: pypi
|
|
33
|
+
permissions:
|
|
34
|
+
id-token: write
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/download-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
|
|
13
|
+
# Wardline runtime output
|
|
14
|
+
findings.jsonl
|
|
15
|
+
.wardline-cache/
|
|
16
|
+
|
|
17
|
+
# Filigree issue tracker
|
|
18
|
+
.filigree/
|
|
19
|
+
.env
|
|
20
|
+
|
|
21
|
+
# Filigree-generated agent operator playbooks (local tooling, not project docs)
|
|
22
|
+
CLAUDE.md
|
|
23
|
+
AGENTS.md
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-05-30
|
|
11
|
+
|
|
12
|
+
First public release. A generic, lightweight semantic-tainting static analyzer
|
|
13
|
+
for Python — enterprise-class trust-boundary analysis at small-team weight.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Taint engine** — AST-based semantic taint analysis with a trust lattice,
|
|
18
|
+
call-graph propagation, function-summary caching, and `match`-statement
|
|
19
|
+
handling. Zero runtime dependencies in the base package.
|
|
20
|
+
- **Trust vocabulary** — decorator-based trust markers (`@trusted`,
|
|
21
|
+
`@boundary`, validators) resolved through a configurable vocabulary
|
|
22
|
+
descriptor.
|
|
23
|
+
- **Rules** — `PY-WL-101` (untrusted-reaches-trusted), `PY-WL-102`
|
|
24
|
+
(boundary-without-rejection), `PY-WL-103` (broad-except), `PY-WL-104`
|
|
25
|
+
(silent-except), with per-rule severity overrides.
|
|
26
|
+
- **Outputs** — `wardline scan` emits findings as JSONL or SARIF, with a native
|
|
27
|
+
Filigree emitter and Clarion producer-conformance support for Loom
|
|
28
|
+
integration.
|
|
29
|
+
- **Suppression model** — baseline files and waivers (with expiry), plus an
|
|
30
|
+
opt-in LLM triage layer.
|
|
31
|
+
- **LLM triage judge** — opt-in `wardline judge` reads each active finding cold
|
|
32
|
+
and labels it true/false positive with a rationale, writing confirmed
|
|
33
|
+
false positives to `.wardline/judged.yaml`. Dependency-free transport
|
|
34
|
+
(stdlib `urllib` → OpenRouter); requires `WARDLINE_OPENROUTER_API_KEY`.
|
|
35
|
+
- **Configuration** — `wardline.yaml`, validated fail-loud against a JSON
|
|
36
|
+
Schema (unknown or mistyped keys are hard errors).
|
|
37
|
+
- **Packaging** — MIT-licensed; optional extras `scanner` (config + CLI) and
|
|
38
|
+
`loom` (HTTP integrations).
|
|
39
|
+
|
|
40
|
+
[Unreleased]: https://github.com/foundryside-dev/wardline/compare/v0.1.0...HEAD
|
|
41
|
+
[0.1.0]: https://github.com/foundryside-dev/wardline/releases/tag/v0.1.0
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
This project adopts the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/),
|
|
4
|
+
version 2.1. Be respectful, assume good faith, and keep discussion focused on
|
|
5
|
+
the work.
|
|
6
|
+
|
|
7
|
+
Report unacceptable behaviour to **john@wardline.dev**.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Contributing to Wardline
|
|
2
|
+
|
|
3
|
+
Wardline is a lightweight semantic-tainting static analyzer for Python, built
|
|
4
|
+
for small teams who want capable tooling without enterprise weight.
|
|
5
|
+
|
|
6
|
+
## Development setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/foundryside-dev/wardline
|
|
10
|
+
cd wardline
|
|
11
|
+
python -m venv .venv && . .venv/bin/activate
|
|
12
|
+
pip install -e ".[dev]"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Before opening a PR
|
|
16
|
+
|
|
17
|
+
Run the full gate and make sure it is green:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pytest -q
|
|
21
|
+
ruff check src tests
|
|
22
|
+
mypy src
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- Follow TDD: write the failing test first.
|
|
26
|
+
- Keep changes focused; one concern per PR.
|
|
27
|
+
- New behaviour needs tests. New `wardline.yaml` keys need a `config_schema.py` update.
|
wardline-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 John Morrissey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
wardline-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wardline
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generic semantic-tainting static analyzer for Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/foundryside-dev/wardline
|
|
6
|
+
Project-URL: Repository, https://github.com/foundryside-dev/wardline
|
|
7
|
+
Project-URL: Issues, https://github.com/foundryside-dev/wardline/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/foundryside-dev/wardline/blob/main/CHANGELOG.md
|
|
9
|
+
Author: John Morrissey
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: security,static-analysis,taint-analysis,trust-boundaries
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: click>=8.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: jsonschema>=4.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: mypy>=1.13.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-randomly; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: types-jsonschema; extra == 'dev'
|
|
28
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
29
|
+
Provides-Extra: loom
|
|
30
|
+
Requires-Dist: httpx>=0.27; extra == 'loom'
|
|
31
|
+
Provides-Extra: scanner
|
|
32
|
+
Requires-Dist: click>=8.0; extra == 'scanner'
|
|
33
|
+
Requires-Dist: jsonschema>=4.0; extra == 'scanner'
|
|
34
|
+
Requires-Dist: pyyaml>=6.0; extra == 'scanner'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# Wardline
|
|
38
|
+
|
|
39
|
+
Wardline is a generic, lightweight semantic-tainting static analyzer for Python. It tracks the flow of untrusted data through a codebase, identifies trust-boundary violations, and emits structured findings — without requiring runtime instrumentation. Wardline is part of the Loom suite alongside Clarion (code intelligence) and Filigree (issue tracking). To use the scanner CLI, install the extra dependencies with `pip install wardline[scanner]`.
|
wardline-0.1.0/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Wardline
|
|
2
|
+
|
|
3
|
+
Wardline is a generic, lightweight semantic-tainting static analyzer for Python. It tracks the flow of untrusted data through a codebase, identifies trust-boundary violations, and emits structured findings — without requiring runtime instrumentation. Wardline is part of the Loom suite alongside Clarion (code intelligence) and Filigree (issue tracking). To use the scanner CLI, install the extra dependencies with `pip install wardline[scanner]`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
Only the latest released version of Wardline receives security fixes.
|
|
6
|
+
|
|
7
|
+
## Reporting a Vulnerability
|
|
8
|
+
|
|
9
|
+
Please report security issues privately to **john@wardline.dev** rather than
|
|
10
|
+
opening a public issue. Include a description, reproduction steps, and the
|
|
11
|
+
affected version. We aim to acknowledge reports within 7 days.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Wardline ↔ Loom Integration Brief
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-05-29
|
|
4
|
+
**From:** Wardline (generic build, in flight)
|
|
5
|
+
**To:** Filigree and Clarion maintainers
|
|
6
|
+
**Status:** request-for-adaptation — sibling-side changes that let the generic Wardline emit findings natively into the suite
|
|
7
|
+
**Charter:** [`loom.md`](../../) §5 — integration is *additive, not load-bearing*. Every ask below is enrichment; Wardline boots, self-tests, and analyzes with both siblings absent.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Context
|
|
12
|
+
|
|
13
|
+
Wardline is being rebuilt as a **generic, lightweight semantic-tainting static analyzer** for any Python project (language-pluggable later), with light-touch, single-user-friendly governance (a plain git-committed baseline; no signing, no counter-signatures, no coverage gates). It fuses the rigorous taint engine of the prior reference impl with an opt-in LLM judge.
|
|
14
|
+
|
|
15
|
+
It needs to emit findings **natively** into the suite:
|
|
16
|
+
|
|
17
|
+
- **Filigree** — receive Wardline findings into its scan-results lifecycle (the authoritative home for finding *state*).
|
|
18
|
+
- **Clarion** — enrich those findings by reconciling Wardline's Python qualnames to Clarion entity IDs.
|
|
19
|
+
|
|
20
|
+
This brief states exactly what Wardline will emit and the small, bounded changes (mostly confirmations) it needs on each side. **Wardline deliberately does not ask either tool to grow new columns for Wardline-specific semantics** — those ride a namespaced `metadata` slot, per the charter.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. The shared contract — Wardline's `Finding`
|
|
25
|
+
|
|
26
|
+
Wardline's internal `Finding` is a pure analysis fact. It is designed as a **superset** of Filigree's `ScanFinding` intake so emission is serialization, not translation. Wardline does **not** model finding lifecycle (`status`, `scan_run_id`, `seen_count`, `issue_id`, timestamps) — that is Filigree's authoritative domain.
|
|
27
|
+
|
|
28
|
+
| Wardline field | Wire destination |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `rule_id` (`WLN-*`, namespaced) | Filigree `rule_id` — preserved byte-for-byte |
|
|
31
|
+
| `message` | Filigree `message` |
|
|
32
|
+
| `suggestion` | Filigree `suggestion` |
|
|
33
|
+
| `severity` (internal `INFO\|WARN\|ERROR\|CRITICAL`, `NONE` for facts) | Filigree `severity` (mapped, see §5) |
|
|
34
|
+
| `location.path` | Filigree `file_path` (→ `file_id` resolved server-side) |
|
|
35
|
+
| `location.line_start/end` | Filigree `line_start`/`line_end` |
|
|
36
|
+
| `fingerprint` (stable per-finding hash) | see §3 ask B |
|
|
37
|
+
| `qualname` (`module.qualified_name`, dotted) | `metadata.wardline.qualname` (see §4) |
|
|
38
|
+
| `kind` (`defect\|fact\|classification\|metric\|suggestion`) | `metadata.wardline.kind` |
|
|
39
|
+
| `confidence`, `related_entities` | `metadata.wardline.*` |
|
|
40
|
+
| `properties` (per-rule extension) | `metadata.wardline.properties.*` |
|
|
41
|
+
|
|
42
|
+
### The `metadata.wardline.*` namespace
|
|
43
|
+
|
|
44
|
+
All Wardline-specific richness lands under a single namespaced key, preserved verbatim by both siblings:
|
|
45
|
+
|
|
46
|
+
```jsonc
|
|
47
|
+
"metadata": {
|
|
48
|
+
"wardline": {
|
|
49
|
+
"fingerprint": "<64-hex stable hash>",
|
|
50
|
+
"qualname": "auth.tokens.TokenManager.issue",
|
|
51
|
+
"kind": "defect",
|
|
52
|
+
"internal_severity": "ERROR", // round-trips the 4-level original
|
|
53
|
+
"confidence": 0.92, // optional
|
|
54
|
+
"related_entities": [], // optional
|
|
55
|
+
"properties": { "cwe": "CWE-200" } // arbitrary per-rule extension
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 3. Asks for Filigree
|
|
63
|
+
|
|
64
|
+
**A. (confirm) Batch scan-results ingest.** Confirm `POST /api/v1/scan-results` accepts `scan_source` plus a list of findings, each carrying `file_path, rule_id, message, severity, line_start, line_end, suggestion, metadata`, and preserves `metadata` verbatim. (Your `ScanIngestResponseLoom` implies this exists — just confirming the field set, especially `suggestion` and a free-form `metadata` on the *ingest* path, not only on the stored row.)
|
|
65
|
+
|
|
66
|
+
**B. (the one real change) Dedup on a Wardline-supplied `fingerprint`.** Wardline computes a stable per-finding fingerprint; it is the spine of Wardline's baseline/drift detection. **Request:** Filigree treats this fingerprint as the finding's cross-run identity for `seen_count`/lifecycle, rather than recomputing its own key — so Wardline's baseline and Filigree's lifecycle never disagree. Either:
|
|
67
|
+
- a top-level `fingerprint` field on the scan-results finding (preferred), or
|
|
68
|
+
- an agreed convention that Filigree dedups on `metadata.wardline.fingerprint` when `scan_source == "wardline"`.
|
|
69
|
+
|
|
70
|
+
Please call which you'd rather implement.
|
|
71
|
+
|
|
72
|
+
**C. (confirm) Standalone `file_path` → `file_id` resolution.** Confirm path-anchored ingest resolves `file_id` in **standalone Filigree** (no Clarion registry backend), so the (Wardline, Filigree) pair composes without Clarion present — pairwise composability per `loom.md` §4.
|
|
73
|
+
|
|
74
|
+
**D. (confirm) `scan_source = "wardline"`** and the `WLN-*` `rule_id` namespace are accepted and stored byte-for-byte (no normalization).
|
|
75
|
+
|
|
76
|
+
**Not asking for:** new columns for `qualname`/`kind`/`fingerprint`/`confidence`. They stay in `metadata.wardline.*`. The only schema touch requested is the dedup-key decision in (B).
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 4. Asks for Clarion
|
|
81
|
+
|
|
82
|
+
**A. (confirm) qualname → entity-ID reconciliation.** Wardline emits `metadata.wardline.qualname` as the **combined dotted `module.qualified_name`** (e.g. `auth.tokens.TokenManager.issue`) — deliberately matching Clarion's L7 form to resolve the [ADR-018](../../clarion/adr/ADR-018-identity-reconciliation.md) deferred clash in Clarion's favor. Confirm Clarion reconciles this to an `EntityId` at ingest/enrichment and that the dotted form is what you want (vs. the old `(module, qualified_name)` tuple).
|
|
83
|
+
|
|
84
|
+
**B. (decision) Transport role under "native" emission.** [ADR-015](../../clarion/adr/ADR-015-wardline-filigree-emission.md) scoped Wardline→Filigree through Clarion's `clarion sarif import` for v0.1, with a native Wardline emitter as the v0.2 retirement. Wardline now intends to ship the **native Filigree emitter directly** (it's the ~1-day path the ADR-015 spike already costed). Consequence: **Clarion is no longer on the transport path** for the (Wardline, Filigree) pair, and `loom.md` §5 asterisk 1 can retire. Clarion's role becomes pure **enrichment** (entity reconciliation), not a bridge. Please confirm you're happy to (a) keep the SARIF translator as a general-purpose path for *other* SARIF tools, and (b) treat Wardline as a native emitter. This likely warrants an ADR-015 "Revision 2".
|
|
85
|
+
|
|
86
|
+
**C. (optional, later) Entity associations.** If Wardline later binds findings to Clarion entities directly (beyond qualname reconciliation), it would use [ADR-029](../../clarion/adr/ADR-029-entity-associations-binding.md) `add_entity_association` with `content_hash_at_attach`. Not needed for the first cut — flagged so it's not a surprise.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 5. Severity mapping
|
|
91
|
+
|
|
92
|
+
Wardline internal (4 levels + facts) → Filigree (5 levels). Original preserved in `metadata.wardline.internal_severity`.
|
|
93
|
+
|
|
94
|
+
| Wardline | Filigree |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `CRITICAL` | `critical` |
|
|
97
|
+
| `ERROR` | `high` |
|
|
98
|
+
| `WARN` | `medium` |
|
|
99
|
+
| `INFO` | `low` |
|
|
100
|
+
| `NONE` (facts/metrics) | `info` |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 6. Federation discipline (what Wardline will NOT do)
|
|
105
|
+
|
|
106
|
+
- Wardline has **no runtime dependency** on either sibling. `wardline scan` runs and writes `findings.jsonl` standalone; Filigree/Clarion emission is opt-in (`--filigree-url`, `--clarion-*`), absent → degrade silently.
|
|
107
|
+
- Wardline does **not** push its semantics into sibling schemas. Rich fields live in `metadata.wardline.*`.
|
|
108
|
+
- Wardline does **not** read finding *state* back as authoritative — Filigree owns lifecycle; Wardline owns the analysis fact and the local baseline.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 7. Decisions requested back
|
|
113
|
+
|
|
114
|
+
1. **Filigree §3.B** — top-level `fingerprint` field, or dedup on `metadata.wardline.fingerprint`?
|
|
115
|
+
2. **Clarion §4.A** — confirm dotted `module.qualified_name` is the reconciliation key you want.
|
|
116
|
+
3. **Clarion §4.B** — agree Wardline ships as a native Filigree emitter and ADR-015 gets a Revision 2 (Clarion off the transport path, translator stays for other tools).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Round 1 — Clarion response (2026-05-29) & Wardline reply
|
|
121
|
+
|
|
122
|
+
**Clarion decisions: §4.A ✅ (with a normalization contract), §4.B ✅ (asterisk 1 stays live until ship). Decision 1 is Filigree's.** Two route-backs to Wardline; both answered below.
|
|
123
|
+
|
|
124
|
+
### Reply to §4.A — qualname normalization contract (accepted)
|
|
125
|
+
|
|
126
|
+
- Wardline's emitter composes **only the module portion**, applying Clarion's exact `module_dotted_name()` rules (src/ strip, `.py` drop, `__init__.py` collapse, namespace + non-src layouts). **Please send that function**; Wardline vendors it as the single source of truth.
|
|
127
|
+
- Python `__qualname__` is preserved **byte-for-byte**: `<locals>` markers and dotted class chains pass through untouched, never re-dotted. Final key = `module_dotted_name(path) + "." + __qualname__`.
|
|
128
|
+
- **Proposal — shared qualname conformance corpus.** A committed JSON of `{layout, file, symbol} → expected_qualname` covering the landmines (src/namespace/non-src, `__init__` collapse, nested class, `<locals>`/closure, decorated/overloaded). Both tools run it in their own CI; federation-safe design-review artifact (nothing imports it). Wardline seeds it from Clarion's function. **Q back: where does it live — Clarion repo or a neutral suite location?** This converts byte-equality from assumption to test, on both sides.
|
|
129
|
+
|
|
130
|
+
### Reply to §4.B — transport / asterisk 1 (agreed in full)
|
|
131
|
+
|
|
132
|
+
Rev 2 rewrites the retirement *mechanism* (Wardline-side native emitter); asterisk 1 stays **live** in `loom.md` §5 under `release:1.1` until the emitter ships and is verified. No deletion on promise. Agreed.
|
|
133
|
+
|
|
134
|
+
### Reply to the REGISTRY route-back — asterisk 2 (the important one)
|
|
135
|
+
|
|
136
|
+
The rebuild **preserves the registry**: `wardline.core.registry.{REGISTRY, REGISTRY_VERSION, RegistryEntry}` + the decorator/annotation groups are core to Wardline (SP2), same package path. Wardline's commitment is **belt-and-suspenders**:
|
|
137
|
+
|
|
138
|
+
1. **Keep the `wardline.core.registry` import surface intact** in the rebuild → Clarion's plugin startup contract does **not** break (zero-day compat bridge).
|
|
139
|
+
2. **Additionally land the NG-25 descriptor** (versioned YAML/JSON vocabulary export carrying `REGISTRY_VERSION` + entries) so the plugin can *read* instead of *import* — the federation-clean retirement of asterisk 2.
|
|
140
|
+
|
|
141
|
+
Asterisk 2 retires when Clarion's plugin switches to the descriptor (same ship-not-promise discipline). **Q back: can the plugin consume an NG-25 descriptor, and on what timeline?** Until then the import keeps it running. (Descriptor-only-from-day-one is possible but couples our release timelines — plugin must switch before Wardline's first tag — so the compat bridge is recommended.)
|
|
142
|
+
|
|
143
|
+
### Accepted consequential ADR touches (Clarion-side)
|
|
144
|
+
|
|
145
|
+
- **ADR-018** gets its own dated entry: reconciliation entry point becomes "Wardline emits pre-composed `metadata.wardline.qualname`; Clarion matches via `find_entity`-by-qualname off the Filigree finding" — replacing the file-path composition rule.
|
|
146
|
+
- **ADR-017**'s Wardline severity-mapping role retires; the §5 table here is now the authority for the (direct) Wardline→Filigree mapping.
|
|
147
|
+
- **ADR-029** entity associations — acknowledged, not first-cut.
|
|
148
|
+
|
|
149
|
+
### New Wardline-side obligations captured from Round 1
|
|
150
|
+
|
|
151
|
+
- **SP1/SP2:** qualname emitter must replicate Clarion's `module_dotted_name()` byte-for-byte and preserve `__qualname__` verbatim.
|
|
152
|
+
- **Shared:** seed + maintain the qualname conformance corpus.
|
|
153
|
+
- **SP2:** preserve `wardline.core.registry` import surface **and** emit an NG-25 vocabulary descriptor.
|
|
154
|
+
- **SP4:** prefer a **top-level `fingerprint` field** for Filigree dedup (shared preference with Clarion).
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Round 1 — Filigree response (2026-05-29, schema v16) & Wardline reply
|
|
159
|
+
|
|
160
|
+
**Filigree: all four asks confirmed. §3.B accepted as the generic top-level `fingerprint` field (Wardline's preference). Two corrections + two questions back, answered below.**
|
|
161
|
+
|
|
162
|
+
### §3.B accepted — generic top-level `fingerprint` (Wardline confirms commitment)
|
|
163
|
+
|
|
164
|
+
Filigree will add an **optional, first-class `fingerprint`** column (not a Wardline metadata special-case): when non-empty it is the cross-run identity (keyed with `scan_source`); when absent, dedup falls back to `(file_id, scan_source, rule_id, coalesce(line_start,-1))`. Cost owned by Filigree: schema **v16→v17** (replace the non-partial UNIQUE dedup index with a partial one + add partial unique on `(scan_source, fingerprint)`), dedup-query change, wire/adapter/fixture update, and a behavioral fix (the update path must refresh `line_start`, not just `line_end`, since line can move while fingerprint holds).
|
|
165
|
+
|
|
166
|
+
**Wardline confirms it supplies a stable top-level `fingerprint`** — it is the spine of SP3's baseline/drift detection, so the migration is justified by a real consumer. Bonus that strengthens the case: Filigree's own line-based key is unstable (`_normalize_line_attribution_for_existing_files` clamps/clears `line_start`), which silently re-keys findings; a Wardline fingerprint pins identity through exactly that churn.
|
|
167
|
+
|
|
168
|
+
> **New constraint captured (SP1/SP3):** Filigree flags the plausible case of **two taint paths into one sink** — two findings at the same `file/rule/line` with different fingerprints. Wardline's fingerprint **must distinguish findings by taint path**, not just `(file, rule, line)`, or the two collapse. This is a fingerprint-composition requirement for the engine.
|
|
169
|
+
|
|
170
|
+
### §3.A confirmed — two corrections folded in
|
|
171
|
+
|
|
172
|
+
1. **`metadata` is preserved as a semantic JSON object, not byte-for-byte** (round-trips `json.dumps`/`loads`). Wardline will **not** rely on key order, whitespace, or duplicate keys in `metadata.wardline.*`. (`rule_id` *is* byte-identical — stored column, no transform. Brief §2/§3 wording corrected here.)
|
|
173
|
+
2. **`suggestion` is truncated at 10 000 chars** with a warning → Wardline caps/sizes fix text accordingly.
|
|
174
|
+
|
|
175
|
+
### §3.A — endpoint generation (question answered)
|
|
176
|
+
|
|
177
|
+
Filigree has two front doors sharing one request body: `POST /api/v1/scan-results` (classic) and `POST /api/loom/scan-results` (Loom vocab; `/api/scan-results` aliases Loom). **Wardline will emit into `/api/loom/scan-results`** — Wardline is a Loom-native citizen and wants the `ScanIngestResponseLoom` envelope. (SP4 emitter targets the Loom endpoint.)
|
|
178
|
+
|
|
179
|
+
### §3.C / §3.D / §5 — confirmed
|
|
180
|
+
|
|
181
|
+
Standalone `file_path`→`file_id` via the default `local` registry (composes with Clarion absent ✓); `scan_source="wardline"` + `WLN-*` stored as-is ✓; severity CHECK is exactly the 5-level set, our client-side map lands clean (fail-soft maps unknown→`info`, which we won't hit) ✓.
|
|
182
|
+
|
|
183
|
+
### §4.B federation note — agreed
|
|
184
|
+
|
|
185
|
+
When ADR-015 Rev 2 lands (Clarion off transport, Wardline native emitter), Filigree adds a one-line note that `scan-results` is a first-class external entry point for native emitters. No objection from Filigree's side; the endpoint doesn't care who calls it. Wardline agrees.
|
|
186
|
+
|
|
187
|
+
### New Wardline-side obligations from Filigree Round 1
|
|
188
|
+
|
|
189
|
+
- **SP1/SP3:** fingerprint composition must disambiguate same-`(file,rule,line)` findings by **taint path**.
|
|
190
|
+
- **SP4:** emit to `POST /api/loom/scan-results`; send top-level `fingerprint` (may mirror into `metadata.wardline.fingerprint`); cap `suggestion` ≤ 10 000 chars.
|
|
191
|
+
- **Contract hygiene:** treat `metadata` as semantic-JSON (no reliance on key order / dupes).
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Contract status (end of Round 1)
|
|
196
|
+
|
|
197
|
+
| Item | Status |
|
|
198
|
+
|---|---|
|
|
199
|
+
| Filigree top-level `fingerprint` dedup (v17) | ✅ agreed — Filigree implements |
|
|
200
|
+
| Loom scan-results endpoint as native entry point | ✅ Wardline emits to `/api/loom/scan-results` |
|
|
201
|
+
| Clarion qualname reconciliation (dotted, pre-composed) | ✅ agreed — pending `module_dotted_name()` handover + conformance corpus |
|
|
202
|
+
| Native emitter / ADR-015 Rev 2 / asterisk 1 live until ship | ✅ agreed |
|
|
203
|
+
| REGISTRY import preserved + NG-25 descriptor (asterisk 2) | ✅ Wardline commits both; pending Clarion plugin-reader timeline |
|
|
204
|
+
| Severity 4→5 map (Wardline-owned) | ✅ confirmed |
|
|
205
|
+
| Entity associations (ADR-029) | ⏸ deferred, not first-cut |
|
|
206
|
+
|
|
207
|
+
**Open loops (non-blocking for SP0):** qualname corpus location (Clarion repo vs neutral); Clarion NG-25 plugin-reader timeline.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Round 2 — sibling implementations shipped & verified (2026-05-29)
|
|
212
|
+
|
|
213
|
+
Both siblings implemented their side; Wardline reviewed the code. Verified outcomes:
|
|
214
|
+
|
|
215
|
+
### Filigree (branch `docs/2.1.0-release-prep`) — COMPLIANT, with naming/version notes
|
|
216
|
+
|
|
217
|
+
- **Schema shipped as v19** (not v17 — two unrelated migrations landed between). SP4 must target schema **v19** (`db_schema.py:528`).
|
|
218
|
+
- **Top-level `fingerprint`** column `TEXT NOT NULL DEFAULT ''` (`db_schema.py:160`), generic (any scanner).
|
|
219
|
+
- **Dedup:** fingerprint non-empty → identity `(scan_source, fingerprint)`; empty → legacy `(file_id, scan_source, rule_id, coalesce(line_start,-1)) WHERE fingerprint=''` (`db_files.py:1063-1080`). Partial unique indexes per contract.
|
|
220
|
+
- **`line_start` refresh fix** present (`db_files.py:1189-1211`); test covers line-move under stable fingerprint.
|
|
221
|
+
- **`suggestion` truncated at 10 000 chars** with `"\n[truncated]"` (`db_files.py:1053-1061`).
|
|
222
|
+
- Standalone `path`→`file_id` via local registry works (Clarion absent) ✓.
|
|
223
|
+
|
|
224
|
+
**⚠️ SP4 wire gotchas (exact):**
|
|
225
|
+
- Endpoint **`POST /api/loom/scan-results`**. Per-finding path key is **`path`**, NOT `file_path` (sending `file_path` → 400).
|
|
226
|
+
- Per-finding keys: `path`(req), `rule_id`(req), `message`(req), `severity`(opt, 5-level lowercase), `line_start`/`line_end`(opt), `fingerprint`(opt top-level string; `null`≡omitted≡`""`), `suggestion`(opt), `metadata`(opt object), `language`(opt). Body also takes `scan_source`(req), `scan_run_id`, `mark_unseen`, `create_observations`, `complete_scan_run`.
|
|
227
|
+
- `metadata` preserved as semantic JSON (no key-order/dupes). Line clamping: a `line_start` beyond the on-disk file length is cleared to `null`.
|
|
228
|
+
|
|
229
|
+
### Clarion (Python plugin) — qualname PRODUCER contract fixed; CONSUMER not yet built
|
|
230
|
+
|
|
231
|
+
**Reconciliation read-path is NOT yet implemented** (schema-reserved `wardline_json` column, all `None`; `wardline_probe.py` only proves the import/version handshake; full join is ADR-018 / WP3 scope). So Wardline emitting the correct qualname now is what makes future reconciliation work. **The producer format is fixed and is the SP1 contract:**
|
|
232
|
+
|
|
233
|
+
**`module_dotted_name(rel_path)`** (`extractor.py:210-234`) — operate on the **project-relative POSIX path** (never absolute):
|
|
234
|
+
1. Strip exactly **one** leading `src/` component.
|
|
235
|
+
2. Drop the `.py` suffix; if the resulting stem is `__init__`, **remove that component** (collapse to parent).
|
|
236
|
+
3. Join remaining components with `.`.
|
|
237
|
+
- Examples: `demo.py`→`demo`; `src/demo.py`→`demo`; `pkg/__init__.py`→`pkg`; `src/pkg/sub/mod.py`→`pkg.sub.mod`; `src/src/pkg/mod.py`→`src.pkg.mod` (one level only).
|
|
238
|
+
- Top-level `__init__.py` → empty module → **emit no entity** (do not emit empty qualname).
|
|
239
|
+
|
|
240
|
+
**`reconstruct_qualname`** (`qualname.py:34-48`) — start with the symbol's bare name; walk ancestors innermost→outermost:
|
|
241
|
+
- `FunctionDef`/`AsyncFunctionDef` parent → prepend `"{name}.<locals>."`
|
|
242
|
+
- `ClassDef` parent → prepend `"{name}."`
|
|
243
|
+
- all other nodes (Module/If/With/…) → skipped.
|
|
244
|
+
- Final key: **`f"{dotted_module}.{qualname}"`**.
|
|
245
|
+
- Examples: `demo.Foo.bar`; `demo.Outer.Inner.method`; `demo.outer.<locals>.inner`; nested-class-in-closure → `demo.Foo.bar.<locals>.Local.meth`. The literal `<locals>` (with angle brackets) is a verbatim component, never re-dotted.
|
|
246
|
+
|
|
247
|
+
**Divergence gotchas SP1 must honor to keep reconciliation lossless:**
|
|
248
|
+
- `<locals>` comes from the **function** ancestor only (a class nested in a function gets one `<locals>` from the function, not from itself).
|
|
249
|
+
- `@overload` stubs (bare/`typing.overload`/`typing_extensions.overload`) are **dropped** before entity emission; aliased `overload as o` is not recognized.
|
|
250
|
+
- Duplicate qualnames (redefinition, `singledispatch def _`) → **first-wins**.
|
|
251
|
+
- `async def` ≡ `def` for qualname purposes.
|
|
252
|
+
|
|
253
|
+
> **SP1 obligation:** implement `module_dotted_name` + `reconstruct_qualname` to match the above byte-for-byte, and stand up the shared qualname conformance corpus (seeded from these examples + the edge cases) so both tools test it in CI.
|