Skip to content

feat(taosnet): license-eligibility classifier - #1723

Merged
jaylfc merged 1 commit into
devfrom
feat/taosnet-license-classifier
Jul 7, 2026
Merged

feat(taosnet): license-eligibility classifier#1723
jaylfc merged 1 commit into
devfrom
feat/taosnet-license-classifier

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 7, 2026

Copy link
Copy Markdown
Owner

First self-contained slice of the taOSnet (model torrent mesh) Phase-2 client work. No dependency on the taos.my server endpoints.

tinyagentos/taosnet/license_eligibility.py: decides license_allows_redistribution for a model manifest. Conservative by default (restrictive markers force False; explicit allow-list for permissive + RAIL + Gemma + Llama-community; unknown -> False for human review). The catalog-publish CLI (next slice) uses this to set the manifest flag that should_use_torrent already gates on.

40 tests, incl. an integration test that classifies every current app-catalog/models manifest without error (so a new/unknown licence string surfaces for review rather than being silently redistributed).

Part of the taOSnet initiative (design: docs/design/model-torrent-mesh.md; taos.my contract owned by @taOS-website-dev).

Summary by CodeRabbit

  • New Features

    • Added automated license checks to determine whether model weights can be redistributed.
    • Improved license handling for common formatting variations, including quotes, extra notes, and case/whitespace differences.
  • Tests

    • Added broad coverage for allowed and restricted license types.
    • Added validation to ensure model manifests return a clear boolean result, including when license information is missing.

First self-contained slice of the taOSnet Phase-2 client work. Decides whether a
model's weights may be redistributed over the swarm, so the catalog-publish CLI
can set each variant's license_allows_redistribution flag (which the download
client already requires before touching the swarm).

Conservative by default: restrictive markers (non-commercial, research, gated,
S-Lab) force False; an explicit allow-list covers permissive + RAIL + Gemma +
Llama-community licences; everything else defaults False for human review.
Covers every licence string currently in app-catalog/models.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new tinyagentos/taosnet/license_eligibility.py module implementing license normalization and redistribution eligibility logic, a classify_manifest wrapper, a module docstring for the taosnet package, and a corresponding pytest test suite covering unit and catalog-integration cases.

Changes

License Eligibility Feature

Layer / File(s) Summary
Policy config and normalization
tinyagentos/taosnet/license_eligibility.py, tinyagentos/taosnet/__init__.py
Adds module docstrings, a restrictive-marker tuple, PERMISSIVE_LICENSES allow-list, and normalize_license to lowercase, strip quotes/notes, and collapse whitespace.
Eligibility and manifest classification logic
tinyagentos/taosnet/license_eligibility.py
Implements license_allows_redistribution (False for blank/restrictive licenses, True only for exact allow-list matches) and classify_manifest wrapping it via the manifest's license field.
Test coverage for eligibility logic
tests/taosnet/test_license_eligibility.py
Adds curated allow/deny license lists, parametrized eligibility tests, case/whitespace and CC-BY-NC edge case tests, normalize_license tests, classify_manifest tests, and a catalog-wide integration test over app-catalog/models manifests.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant classify_manifest
  participant license_allows_redistribution
  participant normalize_license

  Caller->>classify_manifest: classify_manifest(manifest)
  classify_manifest->>license_allows_redistribution: license_allows_redistribution(license)
  license_allows_redistribution->>normalize_license: normalize_license(raw)
  normalize_license-->>license_allows_redistribution: normalized string
  license_allows_redistribution-->>classify_manifest: True/False
  classify_manifest-->>Caller: bool result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a taOSnet license-eligibility classifier.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/taosnet-license-classifier

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

collapse internal whitespace, so catalog licence strings compare stably."""
text = raw.strip().strip('"').strip("'").lower()
# Drop a single trailing parenthetical note, e.g. "openrail++ (commercial use allowed)".
if text.endswith(")") and "(" in text:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: normalize_license only strips a single trailing parenthetical, so a licence with multiple notes such as "OpenRAIL++ (commercial use allowed) (v2)" would be left as "openrail++ (commercial use allowed)" after normalisation and would silently fall through to the conservative-False branch instead of matching the allow-list entry. Consider looping or using a regex (e.g. re.sub(r"\s*\([^)]*\)\s*$", "", text) applied repeatedly) to strip any number of trailing parentheticals.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

"by-nc", # CC BY-NC / CC-BY-NC-SA
"-nc-", # sai-nc-community, stable-cascade-nc-community
"nc-community",
"research", # Qwen Research License

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: The "research" and "gated" restrictive markers are bare substrings, so any future permissive licence whose name contains the word "research" (e.g. an "Apache Research Use License" or "Open Research Licence") or "gated" will be silently demoted to non-redistributable. This is an unbounded forward-compatibility risk given the conservative-default policy. Consider anchoring these (e.g. "research-only", " research license") or switching to a token- or regex-based check so the substring cannot appear inside an otherwise-permissive identifier.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

assert manifests, "no model manifests found; wrong path?"
for path in manifests:
data = yaml.safe_load(path.read_text())
result = classify_manifest(data)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: The integration test test_every_catalog_manifest_classifies_without_error asserts isinstance(result, bool), but classify_manifest is statically typed to return bool, so this assertion is tautological and cannot fail regardless of the classification. A regression where a restricted licence (or every licence) starts returning True would still pass this test, defeating the PR's stated goal of "a new/unknown licence string surfaces for review rather than being silently redistributed". Strengthen it by, for example, collecting every manifest whose normalised licence is not in PERMISSIVE_LICENSES and asserting that set is a known/expected review queue (or at least printing the list so CI surfaces unknown licences explicitly).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

return normalized in PERMISSIVE_LICENSES


def classify_manifest(manifest: dict) -> bool:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: classify_manifest accepts manifest: dict with no value-type annotation and will raise AttributeError if a caller passes None, a list, or any non-mapping (e.g. a YAML scalar). Tighten the signature to Mapping[str, Any] | dict[str, Any] and either guard with isinstance(manifest, dict) or document the precondition; this matters because the catalog-publish CLI will be loading arbitrary manifest files where a malformed entry should produce a clear error, not a stack trace.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

def test_every_catalog_manifest_classifies_without_error():
"""Every model manifest must classify to a bool. If this fails on a NEW
licence string, add it to the allow-list or confirm it should stay False."""
catalog = Path(__file__).resolve().parents[2] / "app-catalog" / "models"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The catalog discovery path uses Path(__file__).resolve().parents[2] / "app-catalog" / "models", which silently breaks if this test file is moved, the tests/taosnet directory is restructured, or the repo is vendored. Compute the path once, skip the integration test with pytest.skip when the directory is absent, and consider exposing the catalog root via pyproject.toml / a conftest fixture so the assumption is explicit and documented in one place.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/taosnet/license_eligibility.py 69 normalize_license only strips a single trailing parenthetical; multi-note licences silently fall to False
tinyagentos/taosnet/license_eligibility.py 33 "research" / "gated" restrictive markers are bare substrings and will over-match any future permissive licence containing those words
tests/taosnet/test_license_eligibility.py 102 Integration test asserts isinstance(result, bool), which is tautological given the static return type — provides no regression protection against a permissive-allow or restricted-allow regression

SUGGESTION

File Line Issue
tinyagentos/taosnet/license_eligibility.py 88 classify_manifest lacks a precise value-type annotation and will AttributeError on non-mapping input (e.g. a malformed YAML scalar in the catalog)
tests/taosnet/test_license_eligibility.py 97 parents[2] catalog path is fragile to test-file / repo restructuring; consider a conftest fixture and explicit skip when absent
Files Reviewed (3 files)
  • tinyagentos/taosnet/__init__.py - 0 issues
  • tinyagentos/taosnet/license_eligibility.py - 3 issues
  • tests/taosnet/test_license_eligibility.py - 2 issues

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 30.1K · Output: 5.9K · Cached: 102.6K

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
tests/taosnet/test_license_eligibility.py (1)

16-53: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression coverage for an annotated permissive license.

None of the current cases test a permissive license with a restrictive-marker annotation (e.g. "MIT (non-commercial use)"). This is exactly the scenario that hides the marker-check ordering bug flagged in license_eligibility.py (the parenthetical is stripped before the marker scan runs), so it's worth a dedicated NOT_REDISTRIBUTABLE case here once that's fixed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/taosnet/test_license_eligibility.py` around lines 16 - 53, Add a
regression test in test_license_eligibility for an annotated permissive license
such as “MIT (non-commercial use)” or similar, and assert it belongs in
NOT_REDISTRIBUTABLE. Update the license eligibility coverage around
REDISTRIBUTABLE/NOT_REDISTRIBUTABLE so this case exercises the marker-check
ordering in the eligibility logic and fails if the parenthetical annotation is
stripped before marker detection.
tinyagentos/taosnet/license_eligibility.py (2)

88-90: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard against non-dict manifest input.

classify_manifest assumes manifest is a dict. If a manifest file is empty, yaml.safe_load returns None, and manifest.get(...) raises AttributeError instead of the conservative False default this module otherwise guarantees everywhere else.

🛡️ Proposed fix
 def classify_manifest(manifest: dict) -> bool:
     """Convenience wrapper: read ``manifest['license']`` and classify it."""
-    return license_allows_redistribution(manifest.get("license"))
+    return license_allows_redistribution((manifest or {}).get("license"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/taosnet/license_eligibility.py` around lines 88 - 90,
`classify_manifest` currently assumes `manifest` is always a dict, so
`manifest.get("license")` can crash when `yaml.safe_load` returns `None` for an
empty file. Update `classify_manifest` in `license_eligibility.py` to safely
handle non-dict input by checking the type (or using a guarded fallback) before
accessing `.get`, and return `False` when the manifest is missing or invalid,
matching the conservative behavior of `license_allows_redistribution`.

27-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

LGTM overall, with minor markers redundancy.

"nc-community" (Line 32) is already subsumed by "-nc-" (Line 31) for all current catalog entries — harmless duplication, not worth changing now.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/taosnet/license_eligibility.py` around lines 27 - 38, The
restrictive license marker list in _RESTRICTIVE_MARKERS has redundant overlap
because "nc-community" is already covered by the existing "-nc-" substring check
used in license_eligibility.py. Remove the duplicate marker from the tuple and
keep the remaining entries in _RESTRICTIVE_MARKERS unchanged so the intent stays
clear and the list does not contain unnecessary repetition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tinyagentos/taosnet/license_eligibility.py`:
- Around line 64-85: The restrictive-marker check in
license_allows_redistribution is happening after normalize_license, which can
remove trailing parenthetical annotations and let restricted licenses slip
through. Update license_allows_redistribution to scan the quote-stripped,
lower-cased raw text for _RESTRICTIVE_MARKERS before calling normalize_license,
then use normalize_license only for the PERMISSIVE_LICENSES allow-list lookup.
Keep the fix localized to normalize_license and license_allows_redistribution so
annotated licenses like “Apache-2.0 (non-commercial use only)” are rejected
correctly.

---

Nitpick comments:
In `@tests/taosnet/test_license_eligibility.py`:
- Around line 16-53: Add a regression test in test_license_eligibility for an
annotated permissive license such as “MIT (non-commercial use)” or similar, and
assert it belongs in NOT_REDISTRIBUTABLE. Update the license eligibility
coverage around REDISTRIBUTABLE/NOT_REDISTRIBUTABLE so this case exercises the
marker-check ordering in the eligibility logic and fails if the parenthetical
annotation is stripped before marker detection.

In `@tinyagentos/taosnet/license_eligibility.py`:
- Around line 88-90: `classify_manifest` currently assumes `manifest` is always
a dict, so `manifest.get("license")` can crash when `yaml.safe_load` returns
`None` for an empty file. Update `classify_manifest` in `license_eligibility.py`
to safely handle non-dict input by checking the type (or using a guarded
fallback) before accessing `.get`, and return `False` when the manifest is
missing or invalid, matching the conservative behavior of
`license_allows_redistribution`.
- Around line 27-38: The restrictive license marker list in _RESTRICTIVE_MARKERS
has redundant overlap because "nc-community" is already covered by the existing
"-nc-" substring check used in license_eligibility.py. Remove the duplicate
marker from the tuple and keep the remaining entries in _RESTRICTIVE_MARKERS
unchanged so the intent stays clear and the list does not contain unnecessary
repetition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: edb5c6dd-2330-4931-a7f0-5e996d6798f5

📥 Commits

Reviewing files that changed from the base of the PR and between 76c0c2a and cc2199d.

📒 Files selected for processing (3)
  • tests/taosnet/test_license_eligibility.py
  • tinyagentos/taosnet/__init__.py
  • tinyagentos/taosnet/license_eligibility.py

Comment on lines +64 to +85
def normalize_license(raw: str) -> str:
"""Lower-case, strip surrounding quotes, drop a trailing ``(...)`` note, and
collapse internal whitespace, so catalog licence strings compare stably."""
text = raw.strip().strip('"').strip("'").lower()
# Drop a single trailing parenthetical note, e.g. "openrail++ (commercial use allowed)".
if text.endswith(")") and "(" in text:
text = text[: text.rindex("(")].strip()
return " ".join(text.split())


def license_allows_redistribution(raw: str | None) -> bool:
"""True only when the licence is known to permit redistributing the weights.

Conservative: an unknown, empty, or restrictively-marked licence returns
False. See module docstring for the policy.
"""
if not raw or not raw.strip():
return False
normalized = normalize_license(raw)
if any(marker in normalized for marker in _RESTRICTIVE_MARKERS):
return False
return normalized in PERMISSIVE_LICENSES

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

Restrictive markers are checked too late
license_allows_redistribution() normalizes first, which strips trailing (...) annotations before the restrictive-marker scan. That lets annotated permissive licenses like Apache-2.0 (non-commercial use only) collapse to apache-2.0 and pass the allow-list. Check markers on the quote-stripped/lower-cased text first, then apply normalize_license() only for the allow-list lookup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/taosnet/license_eligibility.py` around lines 64 - 85, The
restrictive-marker check in license_allows_redistribution is happening after
normalize_license, which can remove trailing parenthetical annotations and let
restricted licenses slip through. Update license_allows_redistribution to scan
the quote-stripped, lower-cased raw text for _RESTRICTIVE_MARKERS before calling
normalize_license, then use normalize_license only for the PERMISSIVE_LICENSES
allow-list lookup. Keep the fix localized to normalize_license and
license_allows_redistribution so annotated licenses like “Apache-2.0
(non-commercial use only)” are rejected correctly.

@jaylfc
jaylfc merged commit 0cff19c into dev Jul 7, 2026
11 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in TinyAgentOS Roadmap Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant