foretop-lading 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.
- foretop_lading-0.1.0/LICENSE +202 -0
- foretop_lading-0.1.0/PKG-INFO +442 -0
- foretop_lading-0.1.0/README.md +426 -0
- foretop_lading-0.1.0/pyproject.toml +73 -0
- foretop_lading-0.1.0/pyproject.toml.orig +85 -0
- foretop_lading-0.1.0/src/keel/__init__.py +1 -0
- foretop_lading-0.1.0/src/keel/collect/__init__.py +0 -0
- foretop_lading-0.1.0/src/keel/collect/walk.py +95 -0
- foretop_lading-0.1.0/src/keel/finding.py +105 -0
- foretop_lading-0.1.0/src/keel/gate.py +151 -0
- foretop_lading-0.1.0/src/keel/git_ref.py +45 -0
- foretop_lading-0.1.0/src/keel/render/__init__.py +0 -0
- foretop_lading-0.1.0/src/keel/render/annotations.py +62 -0
- foretop_lading-0.1.0/src/keel/render/markdown.py +40 -0
- foretop_lading-0.1.0/src/keel/render/sarif.py +85 -0
- foretop_lading-0.1.0/src/keel/render/terminal.py +38 -0
- foretop_lading-0.1.0/src/keel/render/triage_json.py +15 -0
- foretop_lading-0.1.0/src/keel/report.py +97 -0
- foretop_lading-0.1.0/src/keel/run.py +66 -0
- foretop_lading-0.1.0/src/keel/triage.py +166 -0
- foretop_lading-0.1.0/src/lading/__init__.py +1 -0
- foretop_lading-0.1.0/src/lading/__main__.py +4 -0
- foretop_lading-0.1.0/src/lading/cli.py +303 -0
- foretop_lading-0.1.0/src/lading/curate.py +111 -0
- foretop_lading-0.1.0/src/lading/data/licences.yaml +555 -0
- foretop_lading-0.1.0/src/lading/data/model_overrides.yaml +56 -0
- foretop_lading-0.1.0/src/lading/data/package_overrides.yaml +677 -0
- foretop_lading-0.1.0/src/lading/dependency_scope.py +118 -0
- foretop_lading-0.1.0/src/lading/discover_adapters.py +63 -0
- foretop_lading-0.1.0/src/lading/discover_cyclonedx.py +79 -0
- foretop_lading-0.1.0/src/lading/discover_datasets.py +27 -0
- foretop_lading-0.1.0/src/lading/discover_deps.py +30 -0
- foretop_lading-0.1.0/src/lading/discover_models.py +25 -0
- foretop_lading-0.1.0/src/lading/findings.py +249 -0
- foretop_lading-0.1.0/src/lading/hf_cache.py +46 -0
- foretop_lading-0.1.0/src/lading/licences.py +91 -0
- foretop_lading-0.1.0/src/lading/model_overrides.py +46 -0
- foretop_lading-0.1.0/src/lading/models.py +48 -0
- foretop_lading-0.1.0/src/lading/obligations.py +44 -0
- foretop_lading-0.1.0/src/lading/package_overrides.py +53 -0
- foretop_lading-0.1.0/src/lading/policy.py +116 -0
- foretop_lading-0.1.0/src/lading/render/__init__.py +0 -0
- foretop_lading-0.1.0/src/lading/render/annotations.py +53 -0
- foretop_lading-0.1.0/src/lading/render/json_renderer.py +10 -0
- foretop_lading-0.1.0/src/lading/render/markdown.py +45 -0
- foretop_lading-0.1.0/src/lading/render/mermaid.py +51 -0
- foretop_lading-0.1.0/src/lading/render/row.py +9 -0
- foretop_lading-0.1.0/src/lading/render/sarif.py +12 -0
- foretop_lading-0.1.0/src/lading/render/terminal.py +67 -0
- foretop_lading-0.1.0/src/lading/resolve.py +96 -0
- foretop_lading-0.1.0/src/lading/source_scan.py +94 -0
- foretop_lading-0.1.0/src/lading/triage.py +151 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 the foretop authors
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foretop-lading
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Traces licence obligations across code dependencies and model weights as one dependency graph — resolves each artifact's licence via Hugging Face / PyPI and prints a flat, honest table (unknown rather than guessed).
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Dist: typer>=0.15
|
|
8
|
+
Requires-Dist: rich>=13.9
|
|
9
|
+
Requires-Dist: httpx>=0.28
|
|
10
|
+
Requires-Dist: pydantic>=2.9
|
|
11
|
+
Requires-Dist: pyyaml>=6.0
|
|
12
|
+
Requires-Dist: pathspec>=0.12
|
|
13
|
+
Requires-Dist: huggingface-hub>=1.28
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# lading
|
|
18
|
+
|
|
19
|
+
> Traces licence obligations across code, model weights, datasets and adapters as one
|
|
20
|
+
> dependency graph, and blocks only the policies the team explicitly chose.
|
|
21
|
+
|
|
22
|
+
Full product spec: `../../specs/lading.md`. This file tracks build status; the spec is the
|
|
23
|
+
source of truth for the eventual full scope.
|
|
24
|
+
|
|
25
|
+
## Status
|
|
26
|
+
|
|
27
|
+
**Slice 1 (Session 21): discover Python deps + model IDs in code, resolve via HF/PyPI, print
|
|
28
|
+
a flat obligations table.**
|
|
29
|
+
|
|
30
|
+
`lading` is `specs/lading.md`'s **Shape A — OSS CLI + CI check** product (§0: *"Ship the free
|
|
31
|
+
CLI publicly during Phase 0... The three-paid-pilot gate applies to the hosted tier only."*) —
|
|
32
|
+
same shape ebb/telltale/charter already shipped, so unlike undertow there's no `PILOT_STATUS`
|
|
33
|
+
tension to resolve first.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
uv run --package foretop-lading lading scan <path> [--format table|json]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Decisions locked in from `specs/lading.md` §1's checkpoint**, all as stated defaults —
|
|
40
|
+
`DEC-PAY`/`DEC-AUTH` are hosted-tier-only and untouched until `PILOT_STATUS >= 3`; `DEC-LLM`
|
|
41
|
+
matches what this session already established for undertow (Anthropic API, not used by Slice 1
|
|
42
|
+
itself — decomposition is Slice 2's job); `DEC-01`/`DEC-02`/`DEC-03`/`DEC-04`/`DEC-05`/`DEC-06`
|
|
43
|
+
had no cost/vendor tradeoff the way undertow's `DEC-LLM` did, so none were re-litigated
|
|
44
|
+
interactively — same "defaults locked in, flagged transparently" pattern charter's own Session
|
|
45
|
+
15 used.
|
|
46
|
+
|
|
47
|
+
**`discover_deps.py`** — parses `uv.lock`'s `[[package]]` entries via stdlib `tomllib` (no new
|
|
48
|
+
dependency). Scoped to `uv.lock` only — `specs/lading.md`'s own FR-001 also names pip/poetry
|
|
49
|
+
lockfiles and Node, but Slice 1's milestone text ("discover Python deps + model IDs") doesn't
|
|
50
|
+
name them; a documented gap, not a silent one. Sanity-checked against this monorepo's own real
|
|
51
|
+
`uv.lock` (100+ real packages), not just a hand-built fixture.
|
|
52
|
+
|
|
53
|
+
**`discover_models.py`** — line-based regex scan of `.py` files (via `keel.collect.walk.
|
|
54
|
+
iter_files`, third real consumer after ebb and telltale) for a `{namespace}/{name}`-shaped
|
|
55
|
+
string literal appearing on the same line as a recognized loader call (`from_pretrained`,
|
|
56
|
+
`pipeline(...)`, `hf_hub_download`, `snapshot_download`) — the same "good enough, not
|
|
57
|
+
semantically perfect" bound telltale's PromQL/OTTL matching and charter's rules-only classifier
|
|
58
|
+
already use, chosen to avoid false positives on unrelated slash-shaped strings (file paths,
|
|
59
|
+
URLs) without a full AST parser. Evidence carries `file:line`.
|
|
60
|
+
|
|
61
|
+
**A known, deliberate scope gap, found while smoke-testing against real loader calls**:
|
|
62
|
+
namespace-less canonical Hugging Face model ids (`gpt2`, `bert-base-uncased`,
|
|
63
|
+
`distilbert-base-uncased` — pre-namespace models that predate HF's `org/name` convention) are
|
|
64
|
+
**not** discovered, since they don't contain a `/` and are indistinguishable from any other
|
|
65
|
+
bare string literal without one. Requiring the `/` is what keeps false-positive noise down for
|
|
66
|
+
every other string in a real codebase; broadening the match would need a curated allow-list of
|
|
67
|
+
real bare model names, which is exactly the kind of curated-KB problem Slice 2's `DEC-02`
|
|
68
|
+
override layer is for, not this slice's regex.
|
|
69
|
+
|
|
70
|
+
**`resolve.py`** — `resolve_model`/`resolve_package` against the real, live Hugging Face Hub
|
|
71
|
+
API (`GET https://huggingface.co/api/models/{id}`) and PyPI JSON API (`GET https://pypi.org/
|
|
72
|
+
pypi/{name}/json`) — both fetched and verified live this session, not from memory. HF's own
|
|
73
|
+
`cardData.license` field can be a **string** (confirmed against the real, live
|
|
74
|
+
`meta-llama/Llama-3.2-1B` — a non-SPDX slug, `"llama3.2"`, Meta's own community-license name)
|
|
75
|
+
or a **list of strings** (confirmed against a real dataset card) — both shapes handled.
|
|
76
|
+
PyPI's own `info.license` is free text, not SPDX-validated, and inconsistent across the
|
|
77
|
+
ecosystem in practice — a real run against this monorepo's own `uv.lock` resolved `httpx`,
|
|
78
|
+
`anthropic`, `certifi` and others correctly, and left plenty genuinely `unknown` (packages that
|
|
79
|
+
declare their licence only via a PyPI trove classifier, which this slice doesn't read — a real,
|
|
80
|
+
expected metadata-quality gap, matching `specs/lading.md` §11's own "HF metadata quality is the
|
|
81
|
+
weak link" risk note, mirrored here on the PyPI side). Any failure — malformed id, 404, network
|
|
82
|
+
error, or a response with no licence field at all — resolves to `Resolution(licence=None,
|
|
83
|
+
resolved=False)`, never guessed, per `specs/lading.md` §4's own accuracy strategy. In-memory
|
|
84
|
+
dedup only within one run; no persistent cache yet (`specs/lading.md` §6's 7-day cache-expiry
|
|
85
|
+
rule is real but not this slice's job — a documented gap).
|
|
86
|
+
|
|
87
|
+
Both resolvers use a plain `httpx.Client()`, not `keel.fetch.make_safe_client()` — HF and PyPI
|
|
88
|
+
are two fixed, trusted hosts reached through a regex-validated id, not arbitrary vendor URLs
|
|
89
|
+
the way undertow's catalogue is; the SSRF-via-redirect concern `make_safe_client` exists for
|
|
90
|
+
doesn't apply the same way here. The id/name is still validated against a strict shape regex
|
|
91
|
+
before ever being interpolated into a URL, as a lighter defense-in-depth measure.
|
|
92
|
+
|
|
93
|
+
**`cli.py` / `render.py`** — `lading scan` prints one flat table (artifact, kind, licence,
|
|
94
|
+
resolved?, source URL), Rich-rendered with the same `rich.markup.escape` discipline every other
|
|
95
|
+
terminal renderer in this suite uses (a model id or package name is attacker-influenceable
|
|
96
|
+
source text — the same real bug class charter's Session 15 found and fixed). `--format json`
|
|
97
|
+
too. The table's own footer states "obligations to review, not legal advice" on every run —
|
|
98
|
+
`specs/lading.md` DEC-05's own framing, not optional.
|
|
99
|
+
|
|
100
|
+
Decomposing licences into the full obligation enum (attribution, share-alike, field-of-use,
|
|
101
|
+
...), the curated override KB, policy presets, datasets/adapters, local cache scanning, the
|
|
102
|
+
Mermaid graph, CycloneDX input, and the GitHub Action are all later slices per `specs/
|
|
103
|
+
lading.md` §8's own table — none of that is here yet.
|
|
104
|
+
|
|
105
|
+
**Verified for real, not just via mocks**: `uv run --package foretop-lading pytest apps/lading/
|
|
106
|
+
tests -q` (24 tests, including two live, unauthenticated network calls against the real HF and
|
|
107
|
+
PyPI APIs), `make check` (repo-wide: 636 tests, mypy strict, ruff) — all green. Ran
|
|
108
|
+
`lading scan` against a real scratch project containing this monorepo's own real `uv.lock` plus
|
|
109
|
+
real `transformers`-style loader calls for `meta-llama/Llama-3.2-1B`,
|
|
110
|
+
`cardiffnlp/twitter-roberta-base-sentiment-latest`, confirming real licences resolve
|
|
111
|
+
end-to-end against the live APIs (`llama3.2`, `cc-by-4.0`) in both table and `--format json`
|
|
112
|
+
output.
|
|
113
|
+
|
|
114
|
+
**Slice 2 (Session 22): obligation decomposition + curated KB + policy engine.**
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
uv run --package foretop-lading lading scan <path> [--format table|json] [--policy NAME_OR_PATH]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`specs/lading.md` §8's own milestone text: *"Obligation decomposition + curated KB for 30 top
|
|
121
|
+
models + policy presets."* Slice 1 only ever showed a bare licence string; `DEC-03` (locked in
|
|
122
|
+
Session 21, no cost/vendor tradeoff, not re-litigated) calls for decomposed obligations instead
|
|
123
|
+
of one verdict, because *"Llama's acceptable-use terms are not a copyleft question."*
|
|
124
|
+
|
|
125
|
+
**`obligations.py`** — `ObligationType`: `attribution`, `redistribution`, `acceptable_use`,
|
|
126
|
+
`field_of_use`, `share_alike`. Every `Obligation` carries a non-blank, verbatim `clause` —
|
|
127
|
+
`specs/lading.md` §9's own definition of done: *"No obligation is ever displayed without a
|
|
128
|
+
quoted clause and a working source link."* `SHARE_ALIKE` has no curated example yet — every
|
|
129
|
+
real licence family researched this session (Apache-2.0, MIT, Llama 3/3.1/3.2, Gemma,
|
|
130
|
+
CreativeML OpenRAIL-M, BigScience BLOOM RAIL, OPT-175B, DeepSeek, FLUX.1 [dev] Non-Commercial,
|
|
131
|
+
ChatGLM3) turns out to be genuinely permissive or RAIL-restriction-propagating, never true
|
|
132
|
+
copyleft — a documented gap, not an invented example.
|
|
133
|
+
|
|
134
|
+
**Curation is per licence *family*, not per model** (`licences.py` + `src/lading/data/
|
|
135
|
+
licences.yaml`) — 12 real families curated this session, grounded in licence text fetched
|
|
136
|
+
live (Apache-2.0's and CreativeML OpenRAIL-M's/BLOOM RAIL's raw text fetched directly;
|
|
137
|
+
Meta's and Google's community-license pages are JS-rendered, so their `clause` fields use the
|
|
138
|
+
fragments a fetch tool returned in direct quotation marks against the live page — flagged in
|
|
139
|
+
the YAML's own header comment as a starting point for review, not a final legal read, matching
|
|
140
|
+
`DEC-05`). Real, meaningful differences found between families researched side by side: Llama's
|
|
141
|
+
field-of-use gate is a 700M-monthly-active-users commercial threshold, OPT-175B's is
|
|
142
|
+
non-commercial-research-only, FLUX.1 [dev]'s is non-commercial-only, ChatGLM3's is
|
|
143
|
+
free-for-research/register-for-commercial — four genuinely different shapes a binary verdict
|
|
144
|
+
would have flattened into one. Gemma, confirmed against its own real text, has no MAU-style
|
|
145
|
+
threshold at all, unlike its Llama-shaped siblings.
|
|
146
|
+
|
|
147
|
+
**`model_overrides.py` + `data/model_overrides.yaml`** — `DEC-02`'s hybrid override layer: 6
|
|
148
|
+
real models whose Hugging Face `cardData.license` is missing or the generic `other` value,
|
|
149
|
+
each researched to its real licence this session (`openai/clip-vit-base-patch32` → MIT, via
|
|
150
|
+
OpenAI's own CLIP repo; `facebook/opt-125m` → OPT-175B License; `deepseek-ai/
|
|
151
|
+
deepseek-llm-7b-base` → the DeepSeek License Agreement; `black-forest-labs/FLUX.1-dev` → its
|
|
152
|
+
own Non-Commercial License; `THUDM/chatglm3-6b`/`zai-org/chatglm3-6b` → the ChatGLM3-6B
|
|
153
|
+
License, covering both the old and the renamed org id). `curate.py` checks this file *before*
|
|
154
|
+
ever calling the live HF API for a model — a curated override is trusted more than a live
|
|
155
|
+
lookup that would only report `other` anyway. `stabilityai/stable-diffusion-2-1` was
|
|
156
|
+
investigated and deliberately left uncurated: the real HF API returned a genuine 401 for this
|
|
157
|
+
specific repo's metadata endpoint in this environment (confirmed three ways — curl, Python's
|
|
158
|
+
`urllib`, and a fetch tool), so rather than guess its licence from memory it's left to resolve
|
|
159
|
+
honestly to `unknown`, exactly the behaviour `specs/lading.md` §4 asks for when a real lookup
|
|
160
|
+
fails.
|
|
161
|
+
|
|
162
|
+
**`policy.py`** — pure, total `evaluate(resolution, policy) -> PolicyResult`
|
|
163
|
+
(`specs/lading.md` §6: *"every (obligations, policy) pair produces a decision, never an
|
|
164
|
+
exception"*) plus `DEC-04`'s three presets (`permissive-only`, `no-copyleft`,
|
|
165
|
+
`no-field-of-use-restrictions`) and a YAML override (`load_policy`). An unresolved or
|
|
166
|
+
not-yet-curated licence defaults to `warn`, never `fail`, for every preset — `specs/lading.md`
|
|
167
|
+
§4: *"`unknown` is a policy-configurable outcome (default: warn, not fail)."* `--policy` is
|
|
168
|
+
opt-in; omitting it reproduces Slice 1's exact output and the exit code is always 0.
|
|
169
|
+
`permissive-only` correctly fails a real Llama-family model on its field-of-use obligation —
|
|
170
|
+
`specs/lading.md` §9's own definition-of-done line, verified against the live API (see below),
|
|
171
|
+
not just a synthetic test.
|
|
172
|
+
|
|
173
|
+
**Verified for real**: `uv run --package foretop-lading pytest apps/lading/tests -q` (70
|
|
174
|
+
tests), repo-wide `make check` (682 tests, mypy strict, ruff) — all green. Ran `lading scan
|
|
175
|
+
--policy permissive-only` against a real scratch project referencing
|
|
176
|
+
`meta-llama/Llama-3.2-1B` and `google-bert/bert-base-uncased`, plus a real `requests` dependency
|
|
177
|
+
— confirmed end-to-end against the live HF/PyPI APIs: `requests` (Apache-2.0) and
|
|
178
|
+
`bert-base-uncased` (apache-2.0) both `pass`, `Llama-3.2-1B` (`llama3.2`) correctly `fail`s on
|
|
179
|
+
its real field-of-use obligation, and the process exits 1. Also confirmed the wheel actually
|
|
180
|
+
bundles `data/*.yaml` (`uv build --wheel` + inspected the resulting zip) — the data files live
|
|
181
|
+
inside the package's own source tree the same way `apps/ebb/src/ebb/registries/retirements/
|
|
182
|
+
*.yaml` already does in production, not a new pattern.
|
|
183
|
+
|
|
184
|
+
**Slice 3a (Session 23): datasets, vendored adapters, local HF cache scanning.**
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
uv run --package foretop-lading lading scan <path> [--format ...] [--policy ...] [--scan-hf-cache]
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`specs/lading.md` §8's Slice 3 milestone bundles four things: *"Datasets, adapters, local
|
|
191
|
+
cache scanning, Mermaid graph, CycloneDX input."* Split into this slice (the three discovery-
|
|
192
|
+
breadth additions, all plugging into the existing discover → curate → resolve → policy
|
|
193
|
+
pipeline) and a later Slice 3b (Mermaid + CycloneDX, output/interop concerns with no shared
|
|
194
|
+
code path) — the same "shrink to one coherent slice" call Session 20 made for undertow's own
|
|
195
|
+
Slice 3.
|
|
196
|
+
|
|
197
|
+
**New dependency, asked and approved this session**: `huggingface_hub` (real PyPI: 1.28.0,
|
|
198
|
+
Apache-2.0). `--scan-hf-cache` needs to parse the real `~/.cache/huggingface/hub` layout
|
|
199
|
+
(`models--org--name/snapshots/<rev>/...`, no-namespace repos, non-symlink Windows mode, `xet/`
|
|
200
|
+
chunk dirs that aren't repos at all — confirmed against Hugging Face's own real cache-docs,
|
|
201
|
+
fetched live) — rather than hand-roll a parser for edge cases HF already solved, `hf_cache.py`
|
|
202
|
+
wraps the official `scan_cache_dir()`. **Opt-in only** (`--scan-hf-cache`), matching charter's
|
|
203
|
+
own opt-in `--enumerate` convention — a bare `lading scan .` never silently reaches outside the
|
|
204
|
+
repo directory into a user's real, potentially tens-of-GB machine-wide cache.
|
|
205
|
+
|
|
206
|
+
**A real bug, found by live verification, not by theorizing**: `scan_cache_dir()` *raises*
|
|
207
|
+
`CacheNotFound` rather than returning an empty report when the cache directory doesn't exist
|
|
208
|
+
yet — the ordinary case for a fresh machine or CI runner that's never downloaded anything from
|
|
209
|
+
Hugging Face, not a rare edge case. This environment has no HF cache at all, and running the
|
|
210
|
+
real (unmocked) code against it crashed immediately. Fixed by catching `CacheNotFound` in
|
|
211
|
+
`hf_cache.py` and treating it as "nothing cached" — the same "a missing source is empty, never
|
|
212
|
+
an exception" rule every other discoverer in this suite already follows. A real, hermetic test
|
|
213
|
+
(`HF_HUB_CACHE` pointed at a guaranteed-nonexistent temp path, not relying on any given
|
|
214
|
+
machine's real cache state) locks this in.
|
|
215
|
+
|
|
216
|
+
**`discover_datasets.py`** — same line-based, loader-call-adjacent pattern as
|
|
217
|
+
`discover_models.py` (now factored into a shared `source_scan.py`, genuinely identical logic
|
|
218
|
+
between the two, not merely similar), matching real `load_dataset(...)` calls. Same documented
|
|
219
|
+
false-positive-bounding gap as models: a bare, namespace-less dataset id (`"glue"`, `"squad"`)
|
|
220
|
+
isn't discovered.
|
|
221
|
+
|
|
222
|
+
**`discover_adapters.py`** — walks the repo for a real, vendored `adapter_config.json`
|
|
223
|
+
(confirmed live this session against a real PEFT adapter repo,
|
|
224
|
+
`peft-internal-testing/tiny-OPTForCausalLM-lora`, that this file always carries
|
|
225
|
+
`base_model_name_or_path`). Emits both the adapter itself (`kind=adapter`, identifier = its
|
|
226
|
+
directory path — no HF id of its own until pushed) and its base model
|
|
227
|
+
(`kind=model`) as separate artifacts. An adapter id referenced directly from Python source
|
|
228
|
+
(`PeftModel.from_pretrained(base_model, "org/adapter-id")`) was already caught by
|
|
229
|
+
`discover_model_ids`'s own `from_pretrained` matching before this slice — confirmed live via
|
|
230
|
+
PEFT's own quicktour docs — so this only covers the genuinely new gap: a config file committed
|
|
231
|
+
to the repo with no matching source-code string nearby.
|
|
232
|
+
|
|
233
|
+
**New `ArtifactKind` members**: `DATASET`, `ADAPTER` — additive, matching `specs/lading.md`
|
|
234
|
+
§3's own domain model wording (`artifacts.kind: code|weights|dataset|adapter`) more closely;
|
|
235
|
+
the existing `PYTHON_PACKAGE`/`MODEL` members were kept as-is rather than renamed, to avoid
|
|
236
|
+
unnecessary churn to already-shipped code for a naming-only concern.
|
|
237
|
+
|
|
238
|
+
**3 new curated licence families** (`data/licences.yaml`, 15 total now) — the real Creative
|
|
239
|
+
Commons families datasets actually use, fetched live from CC's own legal code text: `cc-by-4.0`
|
|
240
|
+
(genuinely permissive, attribution only), `cc-by-sa-4.0` (this KB's **first real
|
|
241
|
+
`share_alike` example** — every family curated in Session 22 turned out to be permissive or
|
|
242
|
+
RAIL-restriction-propagating, never true copyleft), `cc-by-nc-4.0` (a real, distinct
|
|
243
|
+
`field_of_use` shape — non-commercial only, confirmed by `specs/lading.md` §9's own
|
|
244
|
+
definition-of-done example: *"a CC-BY-NC dataset"*). `Salesforce/wikitext`'s real, live
|
|
245
|
+
`cc-by-sa-3.0` (not `-4.0`, the only version curated) is a real, honest "not yet decomposed"
|
|
246
|
+
case, not a bug.
|
|
247
|
+
|
|
248
|
+
**Verified for real**: `uv run --package foretop-lading pytest apps/lading/tests -q` (93
|
|
249
|
+
tests), repo-wide `make check` (705 tests, mypy strict, ruff) — all green. Ran `lading scan`
|
|
250
|
+
against a real scratch project with a real `load_dataset("openai/gsm8k", "main")` call, a real
|
|
251
|
+
`from_pretrained("google-bert/bert-base-uncased")` call, and a real vendored
|
|
252
|
+
`adapters/my-lora/adapter_config.json` (real shape, live-fetched) — confirmed end-to-end
|
|
253
|
+
against the live HF API: the dataset resolved to `mit`, the model to `apache-2.0`, the adapter
|
|
254
|
+
correctly `unknown` (no HF id), and its base model (a real internal-testing repo with
|
|
255
|
+
genuinely no `cardData` at all, confirmed via a direct API check) correctly `unknown` too, not
|
|
256
|
+
a bug. `--scan-hf-cache` itself has **no real end-to-end run** in this session — this
|
|
257
|
+
environment has no local HF cache to scan (confirmed: `~/.cache/huggingface` doesn't exist
|
|
258
|
+
here) — so beyond the real `CacheNotFound` bug fix above, this path is mock-verified only; a
|
|
259
|
+
real run needs a machine with actual cached models/datasets, which is the user's own job to
|
|
260
|
+
confirm.
|
|
261
|
+
|
|
262
|
+
**Slice 3b (Session 24): Mermaid graph output + CycloneDX SBOM input.**
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
uv run --package foretop-lading lading scan <path> --format mermaid [--policy ...]
|
|
266
|
+
uv run --package foretop-lading lading scan <path> --sbom bom.json [--format ...]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The second half of `specs/lading.md` §8's Slice 3 milestone, split from Slice 3a since Mermaid
|
|
270
|
+
rendering and CycloneDX ingestion are output/interop concerns with no shared code path with
|
|
271
|
+
3a's discovery-breadth work — the same "shrink to one coherent slice" call Session 20 made for
|
|
272
|
+
undertow's own Slice 3.
|
|
273
|
+
|
|
274
|
+
**`render_mermaid`** (`render.py`) — `DEC-06`: *"Mermaid in the PR comment — reviewable
|
|
275
|
+
in-place, and it is the screenshot people share."* Wired into the existing `--format` flag as
|
|
276
|
+
a third value (`table|json|mermaid`), matching `specs/lading.md` FR-005's own framing of
|
|
277
|
+
Mermaid as a sibling output format, not a separate flag. One root node per scan fans out to
|
|
278
|
+
one node per artifact, coloured by `PolicyResult.decision` (green/yellow/red) via `classDef`/
|
|
279
|
+
`class` when `--policy` was supplied — confirmed live against mermaid.js.org's own current
|
|
280
|
+
docs as the non-deprecated syntax (`flowchart TD`, not the legacy bare `graph` keyword). Node
|
|
281
|
+
*ids* are synthetic (`n0`, `n1`, ...), never an artifact's own identifier — a real HF id like
|
|
282
|
+
`meta-llama/Llama-3.2-1B` would break Mermaid's own node-id syntax (no slashes allowed); the
|
|
283
|
+
real identifier only ever appears inside a quoted, escaped label. `_escape_mermaid_label`
|
|
284
|
+
applies the same defense-in-depth principle as `rich.markup.escape` elsewhere in this suite,
|
|
285
|
+
for Mermaid's own entity-escape syntax instead (`#` → `#35;`, `"` → `#quot;`, `#` escaped
|
|
286
|
+
first so its own escaped output is never re-escaped) — an artifact identifier or licence
|
|
287
|
+
string is text this process didn't originate and is never trusted not to contain characters
|
|
288
|
+
that would otherwise break the diagram.
|
|
289
|
+
|
|
290
|
+
**`discover_cyclonedx.py`** — accepts an existing CycloneDX 1.6 JSON SBOM as *input* via
|
|
291
|
+
`--sbom PATH` (additive to, never a replacement for, this command's own discovery — a team
|
|
292
|
+
composing lading with an existing SCA tool's SBOM still gets lading's own Python/model
|
|
293
|
+
discovery for anything the SBOM didn't cover). Real component shape and both supported purl
|
|
294
|
+
types were fetched live this session, not guessed: `pkg:pypi/{name}@{version}` (no namespace —
|
|
295
|
+
confirmed against cyclonedx.org's own real component examples and package-url/purl-spec's own
|
|
296
|
+
`pypi-definition.json`) and `pkg:huggingface/{namespace}/{name}@{revision}` (confirmed real
|
|
297
|
+
against purl-spec's own `huggingface-definition.json` and its test fixtures — a real ML-BOM
|
|
298
|
+
extension exists in CycloneDX 1.6, `type: "machine-learning-model"`, so a modern SBOM tool can
|
|
299
|
+
plausibly emit HF-model components, not just code packages). Every other purl type (npm,
|
|
300
|
+
maven, docker layers, ...) and any component with no purl at all is skipped, a documented gap
|
|
301
|
+
— real SBOMs commonly carry ecosystems lading doesn't resolve.
|
|
302
|
+
|
|
303
|
+
**A deliberate scope call, not a half-built feature**: a CycloneDX component's own already-
|
|
304
|
+
declared `licenses` field isn't consulted this slice. A parsed component becomes one more
|
|
305
|
+
artifact identifier feeding the exact same live resolve → curate → policy pipeline every other
|
|
306
|
+
discoverer uses, rather than a special-cased skip-the-live-call path — reusing the SBOM's own
|
|
307
|
+
declared licence directly would need a `DiscoveredArtifact` schema change to carry a pre-known
|
|
308
|
+
licence, real future work, not this slice's job.
|
|
309
|
+
|
|
310
|
+
**Verified for real**: `uv run --package foretop-lading pytest apps/lading/tests -q` (107
|
|
311
|
+
tests), repo-wide `make check` (719 tests, mypy strict, ruff) — all green. Ran
|
|
312
|
+
`lading scan --format mermaid --policy permissive-only` against a real scratch project (a real
|
|
313
|
+
`requests` dependency, a real `meta-llama/Llama-3.2-1B` reference) end-to-end against the live
|
|
314
|
+
HF/PyPI APIs: valid `flowchart TD` output, `requests` (Apache-2.0) coloured `pass`,
|
|
315
|
+
`Llama-3.2-1B` (`llama3.2`) coloured `fail` on its real field-of-use obligation, exit code 1 —
|
|
316
|
+
by eye, syntactically sane Mermaid. `--sbom` has no live-network verification of its own to run
|
|
317
|
+
(the SBOM file is local input, not a live API) — its purl-parsing correctness is covered by
|
|
318
|
+
real-shaped fixtures built from the fetched CycloneDX/purl-spec examples above, and its
|
|
319
|
+
resolve/curate/policy behaviour once parsed is exercised end-to-end with mocked HTTP; stated
|
|
320
|
+
plainly as mock-verified for that half, not claimed as more.
|
|
321
|
+
|
|
322
|
+
**Slice 4 (Session 25): GitHub Action, PR comment, CI gate, public mirror.**
|
|
323
|
+
|
|
324
|
+
`specs/lading.md` §8's last CLI-side milestone: *"GitHub Action, PR comment, CI gate,
|
|
325
|
+
Marketplace."* Not a novel design this session — ebb, telltale, and charter (all Shape A)
|
|
326
|
+
already built exactly this shape, read directly from their own code rather than re-derived
|
|
327
|
+
from memory, and replicated here with lading's own domain (obligations/policy decisions
|
|
328
|
+
instead of MCP capability drift or coverage regressions).
|
|
329
|
+
|
|
330
|
+
**Two new renderers** (`render/markdown.py`, `render/annotations.py` — `--format` now
|
|
331
|
+
`table|json|mermaid|markdown|annotations`), and **`render.py` split into a `render/` package**
|
|
332
|
+
(`terminal.py`, `json_renderer.py`, `mermaid.py`, `markdown.py`, `annotations.py`, `row.py`)
|
|
333
|
+
now that lading has reached the same 5-format count ebb's and charter's own `render/` packages
|
|
334
|
+
already use — not done earlier, since 3 formats didn't yet warrant it. `row.py` holds the
|
|
335
|
+
shared `Row` type alias in its own leaf module: `models.py` can't define it directly without
|
|
336
|
+
importing `curate.py`/`policy.py`, which already import from `resolve.py`, which imports from
|
|
337
|
+
`models.py` — a circular import `row.py` avoids by living outside that chain.
|
|
338
|
+
|
|
339
|
+
**`render_markdown`** mirrors `apps/charter/src/charter/render/markdown.py`'s own role
|
|
340
|
+
exactly — a PR-comment-ready document (table + summary) — plus the rendered Mermaid diagram
|
|
341
|
+
embedded in a ` ```mermaid ` fence, reusing `render_mermaid` directly rather than as a second,
|
|
342
|
+
separate CI step. This is what actually makes the graph *the* reviewable, shareable artifact
|
|
343
|
+
DEC-06 asks for, right inside the comment.
|
|
344
|
+
|
|
345
|
+
**`render_annotations`** emits `::error file=...,line=...::` GitHub workflow-commands — the
|
|
346
|
+
same zero-extra-permission mechanism (no `security-events: write`, no Advanced Security
|
|
347
|
+
requirement, unlike a SARIF-upload step) every other product in this suite already chose, for
|
|
348
|
+
the same reason, verified live once already and not re-verified a fourth time. One real
|
|
349
|
+
difference from charter's/telltale's own annotations: lading's `DiscoveredArtifact.evidence`
|
|
350
|
+
is a single string (Slice 1's own design), not a typed `(file, line)` pair, so a small
|
|
351
|
+
`_location` helper recovers the line number from a trailing `:digits` (the shape source-code
|
|
352
|
+
discovery produces) and falls back to line 1 for evidence with none (`uv.lock`, a CycloneDX
|
|
353
|
+
purl, `"hf cache: ..."`). Also unlike charter's/telltale's own drift-only annotations, *every*
|
|
354
|
+
current `fail` is annotated — lading has no `--base`/merge-base axis this CLI tracks yet, so
|
|
355
|
+
there's no "already existed" state to exclude.
|
|
356
|
+
|
|
357
|
+
**`apps/lading/action.yml`** — steps mirror charter's own exactly: scan with `--format
|
|
358
|
+
annotations` → render `--format markdown` for the sticky PR comment → upsert by marker
|
|
359
|
+
(`apps/lading/scripts/upsert_pr_comment.sh`, a byte-identical copy of the generic root-level
|
|
360
|
+
script) → fail on the captured exit code. `policy` defaults to `"permissive-only"` here, unlike
|
|
361
|
+
the bare CLI's own opt-in `--policy` — a CI Action whose whole purpose is being a gate can't
|
|
362
|
+
default to a silent no-op the way the general-purpose CLI reasonably can.
|
|
363
|
+
|
|
364
|
+
**Public mirror, matching `docs/adr/0002-ebb-public-mirror.md` and every predecessor's own
|
|
365
|
+
boundary exactly**: `.github/workflows/lading-{ci,release}.yml` (tag prefix `lading-v*`),
|
|
366
|
+
`scripts/mirror-workflows/lading-{ci,release}.yml`, and `scripts/sync-lading-mirror.sh` were
|
|
367
|
+
added. **Run for real against a scratch directory this session** (never against a real public
|
|
368
|
+
repo — same explicit warning charter's own script carries): first confirmed it reproduced the
|
|
369
|
+
same known packaging gap every predecessor's mirror had — `uv lock` failing because
|
|
370
|
+
`keel.collect.walk` only resolves inside this monorepo's own workspace — then, in a later
|
|
371
|
+
session, that gap was fixed for real across all four mirrors (ebb, telltale, charter, lading):
|
|
372
|
+
`scripts/sync-lading-mirror.sh` now vendors `keel.collect.walk` verbatim into the mirror at
|
|
373
|
+
sync time, and the mirror's own `pyproject.toml` (`scripts/mirror-pyproject/lading.toml`) no
|
|
374
|
+
longer depends on `foretop-keel`. See `packages/keel/README.md`'s own "Packaging note" for the
|
|
375
|
+
full mechanism. Re-verified for real after the fix: `ruff`/`mypy`/`pytest` (118 tests) all
|
|
376
|
+
green standalone, a real wheel built, and the built wheel cold-installed into a fresh venv with
|
|
377
|
+
`lading --help` actually running.
|
|
378
|
+
|
|
379
|
+
**Still the user's job, not this session's** (same split every predecessor used): creating the
|
|
380
|
+
`foretop-dev/lading` repo, actually running `sync-lading-mirror.sh` against it, creating the
|
|
381
|
+
`foretop-lading` PyPI project and configuring its trusted publisher (owner `foretop-dev`,
|
|
382
|
+
repository `lading`, workflow `release.yml`, environment `pypi`), cutting a `lading-v0.1.0`
|
|
383
|
+
release, and GitHub Marketplace submission — `docs/adr/0002` names Marketplace publishing
|
|
384
|
+
explicitly as *"a public launch action, deliberately left to the user."*
|
|
385
|
+
|
|
386
|
+
**Explicit non-goal this slice**: SARIF. Not named in this milestone's own wording (unlike
|
|
387
|
+
charter, where it arrived in an earlier slice) — same "match the milestone table's own words"
|
|
388
|
+
discipline used for every scope call this session.
|
|
389
|
+
|
|
390
|
+
**Verified for real**: `uv run --package foretop-lading pytest apps/lading/tests -q` (118
|
|
391
|
+
tests), repo-wide `make check` (730 tests, mypy strict, ruff) — all green; both new workflow
|
|
392
|
+
YAML files and `action.yml` parse as valid YAML. Ran `lading scan --format annotations
|
|
393
|
+
--policy permissive-only` and `--format markdown --policy permissive-only` against a real
|
|
394
|
+
scratch project (real `requests` + real `meta-llama/Llama-3.2-1B`) end-to-end against the live
|
|
395
|
+
HF/PyPI APIs: the annotation correctly points at `load.py:2` (the real line the
|
|
396
|
+
`from_pretrained` call is on) with the real field-of-use/acceptable-use reasons in its
|
|
397
|
+
message; the markdown output contains the correct table, summary, and a valid embedded
|
|
398
|
+
`flowchart TD` diagram.
|
|
399
|
+
|
|
400
|
+
**Session 28: `--format json` repurposed to emit the shared `Finding` schema.**
|
|
401
|
+
|
|
402
|
+
`SUITE_ARCHITECTURE.md` §3's keystone extraction (`packages/keel/src/keel/finding.py`,
|
|
403
|
+
Session 26) reached lading last of the three remaining adapters (telltale Session 26, charter
|
|
404
|
+
Session 27). `src/lading/findings.py`'s `to_findings()` maps a `Row`
|
|
405
|
+
(`DiscoveredArtifact`/`CuratedResolution`/`PolicyResult | None`) onto `Finding`, using
|
|
406
|
+
§3.1's own identity rule for lading — "artifact canonical id + obligation code" — as
|
|
407
|
+
`(artifact.kind, artifact.identifier, obligation.kind.value | None)`. Two finding kinds per
|
|
408
|
+
artifact, the same split charter's adapter used for (server, tool): one artifact-resolution
|
|
409
|
+
finding (`unknown` if unresolved or resolved-but-undecomposed, `clear` once decomposed,
|
|
410
|
+
regardless of obligation count) and one per `(artifact, obligation)` (`break` only when a
|
|
411
|
+
supplied `--policy` blocks that specific obligation; `clear` otherwise, including when no
|
|
412
|
+
`--policy` was given at all — judgment only happens when a policy exists to judge against,
|
|
413
|
+
mirroring charter's own "no `--base` ⇒ nothing is `break`" precedent).
|
|
414
|
+
|
|
415
|
+
**A real, breaking change, made deliberately, not by accident**: unlike telltale/charter (where
|
|
416
|
+
`json` didn't exist before their own adapter slice), lading already had a documented
|
|
417
|
+
`--format json` with its own row shape (identifier/kind/licence/obligations/decision). Asked
|
|
418
|
+
directly rather than assumed — the choice was to repurpose `--format json` in place to emit
|
|
419
|
+
the shared envelope, matching all three other adapters exactly, rather than add a second
|
|
420
|
+
format and leave lading as the one product where `json` doesn't mean `Finding`.
|
|
421
|
+
`PILOT_STATUS = 0` (nothing published/installed yet) made this the cheap side to choose: the
|
|
422
|
+
only real consumers were this repo's own tests, updated in the same commit.
|
|
423
|
+
|
|
424
|
+
**Evidence is genuinely heterogeneous** across lading's five discoverers — an absolute
|
|
425
|
+
lockfile path, a relative `path:line`, a bare purl, an `"hf cache: <path>"` descriptor — unlike
|
|
426
|
+
ebb/telltale/charter, which all have a real `source_file`/`source_line` pair to anchor on.
|
|
427
|
+
`_artifact_evidence()` parses defensively: strip a trailing `:<line>` if present, hash the real
|
|
428
|
+
file's bytes if the remaining text resolves to one, and otherwise fall back to hashing the
|
|
429
|
+
evidence string itself — still a real, deterministic, non-fabricated hash, just of the
|
|
430
|
+
descriptor text instead of file content when there is no file to hash.
|
|
431
|
+
|
|
432
|
+
**`rule_version` for an obligation finding** is the owning `LicenceEntry.verified_at` date
|
|
433
|
+
(ISO format), not a fabricated counter — `licences.yaml` has no taxonomy-wide version field the
|
|
434
|
+
way charter's `capability_taxonomy.yaml` does, and the per-entry verification date is a more
|
|
435
|
+
honest signal anyway.
|
|
436
|
+
|
|
437
|
+
Verified the same way Sessions 26/27 were: `lading scan --format json` against a real scratch
|
|
438
|
+
project (mocked HF/PyPI, same fixtures `test_cli.py` already builds) diffed key-for-key against
|
|
439
|
+
a fresh `ebb scan --format json` — identical envelope, four products now. `--policy
|
|
440
|
+
permissive-only` against the real `meta-llama/Llama-3.2-1B` field-of-use obligation produced a
|
|
441
|
+
`break` finding; the identical scan with no `--policy` produced `clear` for the same
|
|
442
|
+
obligation, identity unchanged across both runs.
|