Skip to content
Open
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a4c6ae3
Add SEP-2640 protocol types and validation
Sep 9, 2026
573fc06
Add SEP-2640 server support
Sep 9, 2026
0cc8dbc
Add SEP-2640 client support
Sep 9, 2026
0729d95
Document SEP-2640 Python SDK support
Sep 9, 2026
3a3725b
Strengthen SEP-2640 validation test coverage
Sep 9, 2026
d31511c
Rename skill resource-URI validator for clarity
Sep 9, 2026
c00a1e7
Extract parallel directory-URI validator in Skills handlers
Sep 9, 2026
b4edd53
Pin the skill-name grammar rejection cases
Sep 9, 2026
b845490
Fix documentation accuracy in the Skills guide
Sep 9, 2026
2f363d5
Add end-to-end dynamic-skill and cursor-resume client tests
Sep 10, 2026
1adba1d
Report Skills handler-output faults as INTERNAL_ERROR, not INVALID_PA…
Sep 10, 2026
70493de
Reject directory-shaped skill resource URIs and dot-segment directory…
Sep 10, 2026
38706c1
Seed the pagination cursor and preserve request _meta across skills p…
Sep 10, 2026
f1d2920
Correct Skills docs on read_skill_uri return type and verify on dynam…
Sep 10, 2026
63254f8
Construct ListSkillsParams _meta via model_validate in the skills cli…
Sep 10, 2026
7e32fc9
Test that request _meta is camelCase on the wire and snake_case to a …
Sep 10, 2026
473804e
Empty commit to re-trigger CI after a transient PyPI network flake
Sep 10, 2026
a2e49d5
empty coomit to re-trigger ci check
Sep 10, 2026
071ad0e
added changes for cache-attributes and its tests
Sep 11, 2026
6972162
Merge branch 'main' into sep-2640-python-sdk-support
vijaydeepsinha Sep 20, 2026
e90e294
Merge branch 'main' into sep-2640-python-sdk-support
vijaydeepsinha Sep 23, 2026
cde5cff
incorporated review comments and defined skills as extensions
Sep 23, 2026
1ddace7
removed restriction to allow only 512 files and 16 mb for each skill,…
Sep 23, 2026
488e706
removed separate skill validations as per review suggestion
Sep 23, 2026
7ca165a
re validate the outbound payload for mutation
Sep 24, 2026
662ce93
modified the docs and added check for de-dup across pages
Sep 24, 2026
083a5dc
relaxed the de-dup failure
Sep 24, 2026
12957be
removed extra logging and extra exports
Sep 24, 2026
03e24f0
Merge branch 'main' into sep-2640-python-sdk-support
vijaydeepsinha Sep 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Strengthen SEP-2640 validation test coverage
Parametrize the digest-format rejection test over near-miss cases
(uppercase, wrong length, missing/wrong prefix), and add explicit JSON
round-trip tests for both shapes of the resources union type (a static
array and the "dynamic" marker) to prove neither collapses or mistags
on the wire.
  • Loading branch information
vijay
vijay committed Sep 9, 2026
commit 3a3725b48cc34cad2ad83524fcd7cc73ec74fd61
40 changes: 38 additions & 2 deletions tests/shared/test_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,29 @@ def test_skill_name_from_uri_rejects_a_uri_with_no_recoverable_name() -> None:
skill_name_from_uri("skill:///SKILL.md")


def test_skill_with_a_static_resources_array_round_trips_through_json() -> None:
"""SEP-2640 Resources: `resources` MUST serialize as a JSON array of `{uri, digest, size}`
triples - proves the union type doesn't collapse or mistag on the wire."""
original = _skill()
dumped = original.model_dump(mode="json", by_alias=True)
assert isinstance(dumped["resources"], list)
restored = Skill.model_validate(dumped)
assert restored == original


def test_skill_with_dynamic_resources_round_trips_through_json_as_the_literal_string() -> None:
"""SEP-2640 Resources: a dynamically generated skill MUST carry the literal string
`"dynamic"` in place of an array - not `null`, not `{}`, not omitted."""
original = Skill(
uri="skill://generated/SKILL.md", frontmatter={"name": "generated", "description": "d"}, resources="dynamic"
)
dumped = original.model_dump(mode="json", by_alias=True)
assert dumped["resources"] == "dynamic"
restored = Skill.model_validate(dumped)
assert restored == original
assert restored.resources == "dynamic"


def test_validate_skill_accepts_a_conformant_skill() -> None:
validate_skill(_skill())

Expand Down Expand Up @@ -170,9 +193,22 @@ def test_validate_skill_rejects_duplicate_resource_uris() -> None:
validate_skill(skill)


def test_validate_skill_rejects_an_invalid_digest_format() -> None:
@pytest.mark.parametrize(
"digest",
[
"not-a-digest", # no sha256: prefix at all
"sha256:" + "A" * 64, # uppercase hex - spec requires lowercase
"sha256:" + "a" * 63, # one hex char short
"sha256:" + "a" * 65, # one hex char long
"sha1:" + "a" * 40, # wrong algorithm prefix
"sha256:" + "g" * 64, # non-hex characters
],
)
def test_validate_skill_rejects_malformed_digest_formats(digest: str) -> None:
"""SEP-2640 Integrity and verification: `sha256:{hex}` where `{hex}` is exactly 64
lowercase hexadecimal characters - each of these near-misses must still be rejected."""
root = "skill://git-workflow/SKILL.md"
bad = SkillResource(uri=root, digest="not-a-digest", size=1)
bad = SkillResource(uri=root, digest=digest, size=1)
skill = Skill(uri=root, frontmatter={"name": "git-workflow", "description": "d"}, resources=[bad])
with pytest.raises(ValueError, match="digest"):
validate_skill(skill)
Expand Down