Skip to content

feat(csl): Community Specification License 1.0 compliance plugin - #449

Open
Marc-cn wants to merge 10 commits into
darnitdevorg:mainfrom
Marc-cn:csl-plugin-v2
Open

Marc-cn wants to merge 10 commits into
darnitdevorg:mainfrom
Marc-cn:csl-plugin-v2

Conversation

@Marc-cn

@Marc-cn Marc-cn commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Adds darnit-csl (framework community-spec): audits specification repos for the Community Specification License 1.0 file set (CLA, license, scope, notices, licenses, governance, plus README discoverability of scope/notices) and remediates via templates or the MCP remediate_community_spec tool. Code of Conduct is org-first (links the project/foundation CoC instead of naming individuals). Includes scripts/csl_onboard.py and an onboarding guide.

Already exercised on secure-systems-lab/dsse#79, in-toto/specification#98, theupdateframework/specification#317, uptane/uptane-standard#271.

40 tests in tests/darnit_csl; ruff clean; validate_sync.py passes. Rebased on current main, including default_authority for the plugin's sieve handler (RFC-0001).

Marc-cn and others added 9 commits September 18, 2026 15:10
…iation): UTF-8 templates

Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…river

- remediate_community_spec MCP tool: writes the CSL 1.0 file set from a
  drafted scope and re-audits; rejects placeholder and mailing-list CoC
  contacts so generated notices never carry fake names
- csl_llm_if_present sieve handler: LLM content checks run first under
  darnit serve; darnit audit falls back to the deterministic regex and
  existence passes (stop_on_llm=False in the driver/tests)
- coc_contact_filled regex rejects common placeholder values
- scripts/csl_onboard.py: clone -> branch -> audit -> remediate from a
  scope file -> placeholder scan -> print PR commands
- server registry: strip _framework_name from builtin tool signatures so
  newer FastMCP accepts them

Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
- coc_contact_filled and _PLACEHOLDER_RE also reject the bracketed
  "[Ideally list two different individuals ...]" boilerplate
- csl_llm_if_present CSL-03.01 prompt updated to match
- 3 regression tests (33 -> 36): leftover guidance fails the audit, notices
  render drops the guidance, CLA render uses upstream's real filenames
- csl_onboard: import-order lint fix

Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
- coc_policy='org': notices point at the project's existing org-level
  Code of Conduct (repo CoC file, org community repo, or foundation CoC
  such as CNCF / LF / JDF) and name no individual contacts
- remediate_community_spec refuses individual contacts when the repo
  already has a Code of Conduct file, and requires a linked
  coc_reference in org mode
- CSL-03.01 prompt and contacts requirement updated to accept an
  org-level reference; csl_onboard gains --coc-policy / --coc-reference
- 4 regression tests (36 -> 40)

Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>

@mlieberman85 mlieberman85 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks correct. The handler design is right in the way that matters most here: a missing file returns a dispositive FAIL, a present file defers to the LLM as suggestive, so the control cannot manufacture a PASS. test_placeholder_fails asserting that a template placeholder does not count as a filled-in file is the same trap #445 and #431 fall into.

Three things to fix, two of them one-liners:

  1. get_framework_config_path() should use importlib.resources -- CLAUDE.md:145 states it as a MUST and the three shipped plugins all do it. Suggestions inline on the three call sites.
  2. The fixtures/ directory at the repo root does not appear to be read by anything.
  3. The server/registry.py change is untested.

On the registry change itself: the approach is right and the _bind_tool_config reference checks out -- factory.py:21-51 does the same wrap-inject-strip. Since that logic now exists in two places, docs/design/builtin-tool-factories.md:72 already proposes extracting it; worth linking from there rather than doing it in this PR.


def get_framework_config_path(self) -> Path | None:
"""Absolute path to the bundled TOML config (the source of truth)."""
return Path(__file__).parent / "community-spec.toml"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CLAUDE.md:145 requires importlib.resources here. The __file__ form works for a normal wheel but breaks under zipimport and frozen installs, which is what feature 021 fixed.

darnit-gittuf also raises FileNotFoundError with a build-problem message when the TOML is missing -- worth copying if you want the same diagnostic.

Suggested change
return Path(__file__).parent / "community-spec.toml"
from importlib.resources import files
return Path(str(files(__package__) / "community-spec.toml"))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done. Went with the full gittuf form including the FileNotFoundError diagnostic, so the failure mode matches darnit-gittuf rather than just the resolution

[project.entry-points."darnit.frameworks"]
community-spec = "darnit_csl:get_framework_path"
"""
return Path(__file__).parent / "community-spec.toml"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

darnit-gittuf has the same two entry points and delegates rather than reimplementing, so both resolve the TOML the same way:

Suggested change
return Path(__file__).parent / "community-spec.toml"
return CommunitySpecImplementation().get_framework_config_path()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

[project.entry-points."darnit.frameworks"]
community-spec-optional = "darnit_csl:get_optional_framework_path"
"""
return Path(__file__).parent / "community-spec-optional.toml"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same for the optional framework. There is no implementation method for this one, so it needs the direct form:

Suggested change
return Path(__file__).parent / "community-spec-optional.toml"
from importlib.resources import files
return Path(str(files(__package__) / "community-spec-optional.toml"))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done with the direct importlib.resources form, plus the same FileNotFoundError diagnostic for symmetry

Comment thread fixtures/compliant/README.md Outdated
@@ -0,0 +1,8 @@
# Foo Spec

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These ten files land in a new top-level fixtures/ directory, and grepping the diff nothing references them -- the tests build their repos with tmp_path.

If they are demo material for docs/CSL_ONBOARDING.md, worth referencing them from it. Otherwise they look like working files that got committed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Working files, deleted.

import inspect

sig = inspect.signature(base_fn)
bound_handler.__signature__ = sig.replace(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This changes how every plugin's MCP tools get exposed and there is no test for it. The test added in this PR (test_non_ascii_template_read_as_utf8) covers template encoding, not the signature stripping.

A test asserting _framework_name is absent from the exposed signature would pin it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added TestBuiltinSignatureStripping in tests/darnit/server/test_registry.py, two tests: _framework_name absent from the exposed signature (and no leading-underscore params at all), and every other parameter preserved in order.

Verified they pin it: checked out main's registry.py over mine.

…ature stripping

thanks for the review, here the implementation:
- get_framework_config_path and both framework entry points now resolve via
  importlib.resources, with gittuf's broken-build diagnostic
- get_framework_path delegates to the implementation
- drop unreferenced top-level fixtures/
- test that _framework_name is stripped from the exposed builtin signature
  (fails 2/18 against main's registry.py, passes with the fix)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants