license-compliance-audit
Gate G0 (road-to-provenance-and-license-governance, Phase 0) measured both
candidate similarity detectors against a frozen golden corpus and neither
met the pre-registered bar for a CI gate. Council resolved (2026-07-28,
Option A): no lint_code_provenance.ts in any form — not even
advisory. This skill is the result of that decision: the scan capability
that used to be a planned CI gate now exists only as something a human
invokes deliberately, so every hit gets investigated because a human
asked for it — never as a silent pipeline signal nobody reads.
When to use
- Before merging a diff that looks suspiciously similar to something you've
seen elsewhere, or that a reviewer flagged.
- During a periodic self-audit (e.g. the Phase-4 "run the full pipeline on
this repo" dogfood exercise).
- A reviewer or the
origin: uncertain self-flag from
code-provenance names a residual-class
file worth a closer look.
Do NOT use when:
- You expect this to run automatically in CI — it does not, by explicit
council decision; nothing here is wired into any pipeline.
- You expect a clean result to prove the code was not copied — it cannot;
see the scope bound below.
- You already have a specific, known source to check against a specific
license — that's
license-compliance-borrow-check,
a targeted one-source check, not a corpus-wide scan.
Procedure
- Inspect available tooling. The offline layer (L-1) is always
available via
npx:npx jscpd --version
The online layer (L0) is optional — check whether it is installed and
reachable:scanoss-py --version # or: pip show scanoss
Offline-only is expected and fine in most environments; report it
honestly rather than treating it as a failure.
- Run the offline scan (L-1) against the target path or diff:
npx jscpd <path> --min-tokens 25
--min-tokens 25 is the pre-registered shipping value — the best
false-positive-clean point of the S0.3 sweep
(internal/bench/provenance/reports/baseline-2026-07-28.md). Read every
reported clone pair as a candidate, never a verdict.
- If L0 is reachable, run the fingerprint scan:
scanoss-py scan <path>
Only WFP fingerprints leave the machine, never raw source (privacy by
construction, principle 7). If L0 is unreachable, rate-limited, or not
installed, report the result as partial (offline) explicitly — never
claim a full two-layer scan ran.
- Investigate every hit manually. For each match: is the matched file
already in
provenance/borrows.jsonl (cleared)? Does it read like
independent convergence on a common algorithm shape, or an unattributed
real copy? Open both files side by side before deciding — a match id is
a starting point, not a conclusion.
- Route unresolved hits. A hit with no existing ledger entry and no
independent-convergence explanation routes to
license-compliance-borrow-check
to classify and draft the entry. Never clear a hit yourself by simply
deciding it's fine.
- Report the scope bound alongside every result (see below) — a clean
run means "nothing matched this KB/corpus at this sensitivity", never
"no copying happened".
What the measured numbers mean — and don't
On the frozen synthetic corpus (internal/bench/provenance/, 36 files,
content-sha256 dbbc84a7…34bb3):
| layer |
verbatim+rename recall |
rename-only recall |
false positives |
L-1 jscpd --min-tokens 25 |
10/16 |
4/8 |
0/12 |
| L0 scanoss |
4/16 |
0/8 |
2/12 |
| union (both layers) |
12/16 |
4/8 |
2/12 |
Full source: internal/bench/provenance/reports/baseline-2026-07-28.md.
- Scope bound: the corpus is
synthetic-canonical — independently
authored implementations of well-known algorithm shapes, never fetched or
pasted from any upstream file. This measures transformation-depth
sensitivity and false-positive rate on independent code — it does
not measure recall against SCANOSS's real-OSS knowledge base. No
sample here is indexed anywhere, so a KB lookup returning zero hits says
nothing about the detector's real-world recall. Real-KB recall is
unmeasured and would need a second, real-snippet corpus.
- Rename-only laundering is not reliably caught — 0/8 (SCANOSS), 4/8
(jscpd). The provenance ledger, not this scan, is the anti-launder
control (see
code-provenance).
- No tool sees model training data. Unconscious reproduction from
training data is not addressed by this scan at all, regardless of
outcome.
Output format
- Per scan target: which layer(s) actually ran, hit or clean, and — if
the online layer was unreachable — the explicit
partial (offline)
marker (never a silent green presented as a full scan).
- For every hit: the matched file/snippet, a manual same-shape-vs-real-copy
judgment, and the routing decision (already ledgered / route to
license-compliance-borrow-check / independent convergence, no action).
- The scope-bound paragraph from the section above, restated so the
requester never reads a clean result as a copying guarantee.
Gotcha
- A clean scan is not proof of originality. It means "nothing matched
at this sensitivity, in this corpus/KB" — never "not copied from
training data".
- Rename-only copies routinely pass both layers. Do not trust a clean
re-scan after a purely cosmetic rewrite as evidence of real
transformation.
- Both of SCANOSS's measured false positives came from independently
authored files matching on convergent canonical-algorithm shape (MIT
snippet matches) — a hit is an investigation trigger, not an accusation.
- Nothing here is wired into CI. A hit found by this skill never
blocked anything automatically; if you expected a pipeline signal, there
isn't one — that's the point of Gate G0's Option A resolution.
Do NOT
- NEVER claim or imply this scan runs in CI — it explicitly does not, by
council decision (Gate G0, Option A).
- NEVER report a scan as complete when the online layer was unreachable —
always say
partial (offline).
- NEVER present a clean result as "provably not copied" — restate the
scope bound every time a result is shared.
- NEVER clear a hit without either a ledger entry or a documented
independent-convergence judgment.
See also
code-provenance — the rule that makes
the ledger, not this scan, the primary anti-launder control.
license-compliance-borrow-check —
where an unresolved hit routes for classification + ledger drafting.
internal/bench/provenance/reports/baseline-2026-07-28.md — the full
measured baseline this skill's numbers are drawn from.
docs/CLAIMS.md — claim provenance-detector-transformation-sensitivity
(unbacked, carries the same scope bound).
1---2name: license-compliance-audit3description: Run the offline (jscpd) and online (scanoss-py) similarity scanners on demand against a diff or path — the ONLY home of this repo's detection capability; no CI gate exists or ever ran it automatically4---56# license-compliance-audit78Gate G0 (`road-to-provenance-and-license-governance`, Phase 0) measured both9candidate similarity detectors against a frozen golden corpus and neither10met the pre-registered bar for a CI gate. Council resolved (2026-07-28,11Option A): **no `lint_code_provenance.ts` in any form — not even12advisory.** This skill is the result of that decision: the scan capability13that used to be a planned CI gate now exists only as something a human14invokes deliberately, so every hit gets investigated *because* a human15asked for it — never as a silent pipeline signal nobody reads.1617## When to use1819- Before merging a diff that looks suspiciously similar to something you've20 seen elsewhere, or that a reviewer flagged.21- During a periodic self-audit (e.g. the Phase-4 "run the full pipeline on22 this repo" dogfood exercise).23- A reviewer or the `origin: uncertain` self-flag from24 [`code-provenance`](../../rules/code-provenance.md) names a residual-class25 file worth a closer look.2627Do NOT use when:2829- You expect this to run automatically in CI — it does not, by explicit30 council decision; nothing here is wired into any pipeline.31- You expect a clean result to prove the code was not copied — it cannot;32 see the scope bound below.33- You already have a specific, known source to check against a specific34 license — that's [`license-compliance-borrow-check`](../license-compliance-borrow-check/SKILL.md),35 a targeted one-source check, not a corpus-wide scan.3637## Procedure38391. **Inspect available tooling.** The offline layer (L-1) is always40 available via `npx`:41 ```bash42 npx jscpd --version43 ```44 The online layer (L0) is optional — check whether it is installed and45 reachable:46 ```bash47 scanoss-py --version # or: pip show scanoss48 ```49 Offline-only is expected and fine in most environments; report it50 honestly rather than treating it as a failure.512. **Run the offline scan (L-1)** against the target path or diff:52 ```bash53 npx jscpd <path> --min-tokens 2554 ```55 `--min-tokens 25` is the pre-registered shipping value — the best56 false-positive-clean point of the S0.3 sweep57 (`internal/bench/provenance/reports/baseline-2026-07-28.md`). Read every58 reported clone pair as a **candidate**, never a verdict.593. **If L0 is reachable, run the fingerprint scan:**60 ```bash61 scanoss-py scan <path>62 ```63 Only WFP fingerprints leave the machine, never raw source (privacy by64 construction, principle 7). If L0 is unreachable, rate-limited, or not65 installed, report the result as `partial (offline)` explicitly — never66 claim a full two-layer scan ran.674. **Investigate every hit manually.** For each match: is the matched file68 already in `provenance/borrows.jsonl` (cleared)? Does it read like69 independent convergence on a common algorithm shape, or an unattributed70 real copy? Open both files side by side before deciding — a match id is71 a starting point, not a conclusion.725. **Route unresolved hits.** A hit with no existing ledger entry and no73 independent-convergence explanation routes to74 [`license-compliance-borrow-check`](../license-compliance-borrow-check/SKILL.md)75 to classify and draft the entry. Never clear a hit yourself by simply76 deciding it's fine.776. **Report the scope bound alongside every result** (see below) — a clean78 run means "nothing matched this KB/corpus at this sensitivity", never79 "no copying happened".8081## What the measured numbers mean — and don't8283On the frozen synthetic corpus (`internal/bench/provenance/`, 36 files,84content-sha256 `dbbc84a7…34bb3`):8586| layer | verbatim+rename recall | rename-only recall | false positives |87|---|---:|---:|---:|88| L-1 jscpd `--min-tokens 25` | 10/16 | 4/8 | 0/12 |89| L0 scanoss | 4/16 | 0/8 | 2/12 |90| **union (both layers)** | **12/16** | **4/8** | **2/12** |9192Full source: `internal/bench/provenance/reports/baseline-2026-07-28.md`.9394- **Scope bound:** the corpus is `synthetic-canonical` — independently95 authored implementations of well-known algorithm shapes, never fetched or96 pasted from any upstream file. This measures **transformation-depth97 sensitivity and false-positive rate on independent code** — it does98 **not** measure recall against SCANOSS's real-OSS knowledge base. No99 sample here is indexed anywhere, so a KB lookup returning zero hits says100 nothing about the detector's real-world recall. Real-KB recall is101 unmeasured and would need a second, real-snippet corpus.102- **Rename-only laundering is not reliably caught** — 0/8 (SCANOSS), 4/8103 (jscpd). The provenance ledger, not this scan, is the anti-launder104 control (see [`code-provenance`](../../rules/code-provenance.md)).105- **No tool sees model training data.** Unconscious reproduction from106 training data is not addressed by this scan at all, regardless of107 outcome.108109## Output format1101111. Per scan target: which layer(s) actually ran, hit or clean, and — if112 the online layer was unreachable — the explicit `partial (offline)`113 marker (never a silent green presented as a full scan).1142. For every hit: the matched file/snippet, a manual same-shape-vs-real-copy115 judgment, and the routing decision (already ledgered / route to116 `license-compliance-borrow-check` / independent convergence, no action).1173. The scope-bound paragraph from the section above, restated so the118 requester never reads a clean result as a copying guarantee.119120## Gotcha121122- **A clean scan is not proof of originality.** It means "nothing matched123 at this sensitivity, in this corpus/KB" — never "not copied from124 training data".125- **Rename-only copies routinely pass both layers.** Do not trust a clean126 re-scan after a purely cosmetic rewrite as evidence of real127 transformation.128- **Both of SCANOSS's measured false positives came from independently129 authored files** matching on convergent canonical-algorithm shape (MIT130 snippet matches) — a hit is an investigation trigger, not an accusation.131- **Nothing here is wired into CI.** A hit found by this skill never132 blocked anything automatically; if you expected a pipeline signal, there133 isn't one — that's the point of Gate G0's Option A resolution.134135## Do NOT136137- NEVER claim or imply this scan runs in CI — it explicitly does not, by138 council decision (Gate G0, Option A).139- NEVER report a scan as complete when the online layer was unreachable —140 always say `partial (offline)`.141- NEVER present a clean result as "provably not copied" — restate the142 scope bound every time a result is shared.143- NEVER clear a hit without either a ledger entry or a documented144 independent-convergence judgment.145146## See also147148- [`code-provenance`](../../rules/code-provenance.md) — the rule that makes149 the ledger, not this scan, the primary anti-launder control.150- [`license-compliance-borrow-check`](../license-compliance-borrow-check/SKILL.md) —151 where an unresolved hit routes for classification + ledger drafting.152- `internal/bench/provenance/reports/baseline-2026-07-28.md` — the full153 measured baseline this skill's numbers are drawn from.154- `docs/CLAIMS.md` — claim `provenance-detector-transformation-sensitivity`155 (unbacked, carries the same scope bound).