Feature: Package Registry Reference Resolver As a developer I want to parse and resolve package references So that I can use the Package Registry Standard v1.0.0 reference formats Background: Given I have imported the registry resolver module # ── Reference parsing through ReferenceResolver.parse ───────────────────── Scenario: Registry reference with version parses all fields Given a package reference string "registry.example.com:acme/search@v1.2.3" When I parse it with ReferenceResolver.parse Then the reference_type should be REGISTRY And the server should be "registry.example.com" And the namespace should be "acme" And the name should be "search" And the version should be "v1.2.3" And the original_reference should be "registry.example.com:acme/search@v1.2.3" Scenario: Registry reference with latest alias Given a package reference string "registry.cleverthis.com:acme/web_search@latest" When I parse it with ReferenceResolver.parse Then the reference_type should be REGISTRY And the server should be "registry.cleverthis.com" And the namespace should be "acme" And the name should be "web_search" And the version should be "latest" Scenario: Registry reference without version defaults to latest Given a package reference string "registry.cleverthis.com:simple/agent" When I parse it with ReferenceResolver.parse Then the version should be "latest" Scenario: ID reference parsing Given a package reference string "ID:pkg_act_0123456789abcdef0123456789abcdef01234567" When I parse it with ReferenceResolver.parse Then the reference_type should be ID And the id_string should be "pkg_act_0123456789abcdef0123456789abcdef01234567" And the parsed server should be null And the parsed namespace should be null Scenario: ID reference with graph type Given a package reference string "ID:pkg_grh_deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" When I parse it with ReferenceResolver.parse Then the reference_type should be ID And the id_string should be "pkg_grh_deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" Scenario: Local reference parsing Given a package reference string "local:./my-actor" When I parse it with ReferenceResolver.parse Then the reference_type should be LOCAL And the name should be "./my-actor" Scenario: Local reference with path Given a package reference string "local:path/to/component" When I parse it with ReferenceResolver.parse Then the reference_type should be LOCAL And the name should be "path/to/component" Scenario: original_reference survives round-trip through parsing Given a package reference string "registry.cleverthis.com:acme/web_search@v1.0.0" When I parse it with ReferenceResolver.parse And I convert the PackageReference to str Then the str result is "registry.cleverthis.com:acme/web_search@v1.0.0" Scenario: Empty reference string raises InvalidPackageReferenceError Given an empty package reference string When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing "must not be empty" Scenario: Reference without colon separator raises error Given a package reference string "nocolonserver" When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing "must be in format" Scenario: Registry reference with empty namespace raises error Given a package reference string "srv:/name" When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing "must include a namespace" Scenario: Registry reference with empty name raises error Given a package reference string "srv:ns/" When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing "must include a name" Scenario: Registry reference with empty server raises error Given a package reference string ":ns/name" When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing "must include a server name" Scenario: Registry reference with empty version after at defaults to latest Given a package reference string "srv:ns/name@" When I parse it with ReferenceResolver.parse Then the version should be "latest" Scenario: Registry reference with @ in namespace raises error Given a package reference string "registry.com:ns/n@me@v1.0.0" When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing "@" Scenario: Registry reference with path traversal in name raises error Given a package reference string "registry.com:foo/../bar@v1.0.0" When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing "must contain only" Scenario: Local reference with directory traversal raises error Given a package reference string "local:../../../etc/passwd" When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing ".." Scenario: Local reference with absolute path raises error Given a package reference string "local:/etc/shadow" When I parse it with ReferenceResolver.parse Then an InvalidPackageReferenceError should be raised with message containing "absolute" # ── Reference resolution ────────────────────────────────────────────────── Scenario: ID reference resolves directly to PackageId Given a package reference string "ID:pkg_act_0123456789abcdef0123456789abcdef01234567" And I create a ReferenceResolver without a client When I resolve the reference Then the resolved PackageId id_string should be "pkg_act_0123456789abcdef0123456789abcdef01234567" Scenario: Registry reference resolution requires a client Given a package reference string "registry.example.com:acme/search@v1.0.0" And I create a ReferenceResolver without a client When I resolve the reference Then an InvalidPackageReferenceError should be raised with message containing "RegistryClient is required" Scenario: Local reference resolution requires a LocalPackageStore Given a package reference string "local:./my-actor" And I create a ReferenceResolver without a client When I resolve the reference Then an InvalidPackageReferenceError should be raised with message containing "no LocalPackageStore configured" Scenario: ReferenceResolver can resolve registry ref with mock client Given a package reference string "registry.example.com:acme/search@v1.0.0" And I create a ReferenceResolver with a mock client that returns "pkg_act_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" When I resolve the reference Then the resolved PackageId id_string should be "pkg_act_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" Scenario: ReferenceResolver wraps client RegistryError in InvalidPackageReferenceError Given a package reference string "registry.example.com:acme/search@v1.0.0" And I create a ReferenceResolver with a mock client that raises PackageNotFoundError When I resolve the reference Then an InvalidPackageReferenceError should be raised with message containing "not found" Scenario: ReferenceResolver close closes client when client is present When I create a ReferenceResolver with a mock close tracking client And I call close on the ReferenceResolver Then the mock client close method should have been called Scenario: ReferenceResolver supports async context manager When I use ReferenceResolver as an async context manager with a mock client Then clean-up should have been performed # ── Version resolution ──────────────────────────────────────────────────── Scenario Outline: Concrete version resolves directly Given I have available versions "v1.0.0, v1.1.0, v2.0.0" When I call resolve_version with "" Then the resolved version is "" Examples: | version | expected | | v1.0.0 | v1.0.0 | | v1.1.0 | v1.1.0 | | v2.0.0 | v2.0.0 | Scenario: latest alias resolves to newest version Given I have available versions "v1.0.0, v1.1.0, v2.0.0, v1.0.1" When I call resolve_version with "latest" Then the resolved version is "v2.0.0" Scenario: vx alias resolves same as latest Given I have available versions "v1.0.0, v1.1.0, v2.0.0" When I call resolve_version with "vx" Then the resolved version is "v2.0.0" Scenario: x alias resolves same as latest Given I have available versions "v1.0.0, v3.0.0, v2.0.0" When I call resolve_version with "x" Then the resolved version is "v3.0.0" Scenario: Major alias vX.x resolves to latest in major series Given I have available versions "v1.0.0, v1.1.0, v1.5.0, v2.0.0" When I call resolve_version with "v1.x" Then the resolved version is "v1.5.0" Scenario: Minor alias vX.Y.x resolves to latest patch in series Given I have available versions "v1.2.0, v1.2.1, v1.2.9, v1.3.0" When I call resolve_version with "v1.2.x" Then the resolved version is "v1.2.9" Scenario: Concrete version not in list raises error Given I have available versions "v1.0.0, v2.0.0" When I call resolve_version with "v3.0.0" Then an InvalidPackageReferenceError should be raised with message containing "not found in available versions" Scenario: Alias with no matching versions raises error Given I have available versions "v2.0.0, v2.1.0" When I call resolve_version with "v1.x" Then an InvalidPackageReferenceError should be raised with message containing "No versions match alias" Scenario: Resolving against empty version list raises error Given I have no available versions When I call resolve_version with "latest" Then an InvalidPackageReferenceError should be raised with message containing "No available versions" Scenario: Unrecognized version format raises error Given I have available versions "v1.0.0" When I call resolve_version with "garbage" Then an InvalidPackageReferenceError should be raised with message containing "Unrecognized version format" # ── Version classification helpers ───────────────────────────────────────── Scenario: is_concrete_version identifies concrete versions Given I check if "v1.2.3" is a concrete version Then the is_concrete_version result should be true Scenario: is_concrete_version rejects aliases Given I check if "latest" is a concrete version Then the is_concrete_version result should be false Scenario: is_concrete_version rejects major alias Given I check if "v2.x" is a concrete version Then the is_concrete_version result should be false Scenario: is_concrete_version rejects minor alias Given I check if "v2.1.x" is a concrete version Then the is_concrete_version result should be false Scenario: is_version_alias identifies latest as alias Given I check if "latest" is a version alias Then the is_version_alias result should be true Scenario: is_version_alias identifies concrete as not alias Given I check if "v1.2.3" is a version alias Then the is_version_alias result should be false Scenario Outline: is_version_alias rejects invalid strings Given I check if "" is a version alias Then the is_version_alias result should be false Examples: | version | | garbage | | 1.2.3 | | v1.2 | | hello | Scenario Outline: is_version_alias recognizes valid aliases Given I check if "" is a version alias Then the is_version_alias result should be true Examples: | version | | latest | | vx | | x | | v2.x | | v2.1.x | # ── ReferenceResolver constructor and properties ────────────────────────── Scenario: ReferenceResolver can be constructed without a client When I create a ReferenceResolver without a client Then the resolver client is null Scenario: ReferenceResolver preserves original_reference through parse Given a package reference string "registry.cleverthis.com:acme/search@v1.2.3" When I parse it with ReferenceResolver.parse Then the original_reference should be "registry.cleverthis.com:acme/search@v1.2.3"