Proposed pkg:service specification #878
VanL
started this conversation in
PURL types
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
PURL
servicetype proposalOverview
Package URLs are widely used as the stable identity for catalog, dependency,
and inventory referents. The standard PURL types cover software package
ecosystems, but one increasingly important class of software is not addressed:
software provided as a service, where there is no distributable artifact and no
authoritative package registry — only a hosted product reached through an API.
This proposal defines a PURL type,
pkg:service, for that class. It is writtento be compatible with ECMA-427 PURL syntax and precise enough to discuss with
the Package URL working group.
Motivation (why now). CycloneDX 2.0 retires the separate
services[]arrayand represents everything as a
component; components carry apurl, where theformer
serviceobject did not. That change creates a concrete consumer for aservice-shaped PURL: a hosted service becomes a component that needs an
identifier, and no existing registry type fits one.
pkg:servicefills that gap.Design principles
Three principles govern every decision below. The per-decision rationale notes
refer back to them.
it is reached. This is why PURL has no URL authority component, and it is the
basis for excluding endpoints, hosts, and regions from identity (see
ECMA-427; purl-spec #127).
independent producers who both know the schema, and who observe the same
thing, shall mint the same PURL — and a holder of the PURL can locate the
thing. Every naming rule is chosen so that convergence does not depend on a
central registry or private knowledge.
distinct from its delivery. "The product" and "the vendor's native delivery
of the product" are therefore the same referent.
Relationship to ECMA-427
ECMA-427 defines the seven-component form:
For this type:
schemeis alwayspkg.typeisservice.namespace,name,version, andqualifierskeep their ECMA-427 roles.namespaceare ownernamespaces, not URL hosts.
subpathis not used.Verbal forms
This document uses the verbal forms of the ISO/IEC/Ecma drafting conventions:
"shall" and "shall not" state requirements, "should" and "should not" state
recommendations, and "may" states permission. "can" and "cannot" state
possibility or capability. The keywords are not capitalized, and "must" is not
used as a normative term.
Scope and type selection
Use a standard ecosystem type when one exists. Types such as
pypi,npm,maven,docker,oci,github, andhuggingfaceare preferred overservicewhenever the referent is actually distributed through thoseecosystems. For an open-weight model, prefer
pkg:huggingface.Use
pkg:serviceonly when the missing concept is the package ecosystem itself:the referent is a hosted product, model API, managed service, commercial
platform, provider-defined resource class, or a specific provider-native
resource instance, and no authoritative package registry should own its identity.
Identity model
The root service/product identity is composed from three things and nothing
else:
More specific identities may narrow that root with
resource_typeandnative_idas described in Identity levels.product — regional hosts, load-balanced hosts, API versions serving the same
product — are the same identity.
through a different vendor or aggregation provider (such as OpenRouter, AWS,
or Azure) is a different referent: different terms, availability, and
frequently different served bits.
Identity levels
pkg:serviceidentifies a referent at three nested levels of specificity. Eachqualifier narrows the referent; each level is a strict subset of the one above.
pkg:service/openai.com/gpt-4opkg:service/aws.amazon.com/ec2?resource_type=instancepkg:service/aws.amazon.com/ec2?native_id=arn:aws:ec2:us-west-2:123456789012:instance%2Fi-0abcdef1234567890&resource_type=instanceThe service/product level is appropriate for catalog, dependency, and component
identity. The instance level is appropriate for inventory, evidence, and
point-in-time system descriptions, and should not appear in public advisories or
catalog records unless disclosure of the native identifier is intended.
Components
typeservice, lowercase.namespace{surface}:{vendor}.nameversionqualifierssubpathNamespace
The namespace identifies the product owner, not a deployment region, API
host, billing account, or tenant. It is DNS-order (e.g.,
openai.com).Implementations should store and compare namespaces in canonical lowercase.
Choosing the canonical namespace
openai.comanthropic.commeta.compinecone.iografana.comaws.amazon.comcloud.google.comA rebrand or acquisition that changes the responsible domain yields a new
PURL; continuity is expressed as a relationship, not as identifier stability
(see Equality and propagation). This matches PURL's
existing behavior for renames in every other type.
Aggregator-qualified namespaces
When a product is reached through a distinct responsible intermediary, the
namespace may be aggregator-qualified. Segments are colon-separated and read
left to right; the rightmost segment is always the originating vendor, and
each segment to its left is an aggregation provider, outermost first. Each
segment is itself a canonical namespace chosen by the rule above.
Grammar (normative):
:is a namespace sub-delimiter. A service-aware parser splits the namespaceon
:; the rightmost segment is the vendor, the rest is the aggregation chain.decodes to a colon. Colon is reserved as the aggregation separator.
a::vendoris invalid).string. It still obtains a valid, distinct identity; only the aggregation
structure is opaque to it.
Name
The
nameis the vendor's stable, canonical product identifier. Where the vendorexposes the product through an API, the name should be the identifier the API
accepts (the on-the-wire model or product id); a canonical lowercase, URL-safe
slug is the fallback when no API identifier exists. Where the vendor's identifier
carries a separable snapshot or date, move that to
@versionand keep the stableproduct identity in
name.Version
The version is optional and opaque. It should be the vendor's own product, model
snapshot, date, release, or API version. It is the temporal axis of a service
whose name is stable while its behavior evolves.
Omitting
versiondenotes the product identity without a pinned version. When aproduct exposes a snapshot or dated build, encode it as
@versionrather thanflattening it into
name.Qualifiers
servicerequires no qualifiers. The following are defined; the Identitycolumn states whether the qualifier is part of component identity, because the
type-definition schema has no field to express this and consumers need to be told.
resource_typenative_idresource_typeshould use the provider's stable API, catalog, or documentationterm for the resource class.
native_id, when present:ID, GCP resource name, Cloudflare locator, SaaS object id, or equivalent).
status, detection logic, or other mutable assessment data.
canonical identifier. These shall not be encoded as separate qualifiers.
per the web percent-encoding standard (RFC 3986 / WHATWG URL).
Region, pricing plan, deployment mode, tenant, customer configuration,
affectedness, and remediation state should not be encoded as qualifiers.
Equality and propagation
PURL equality is defined on the decoded components, so equivalent percent-encodings
of the same value are equal.
Identity narrowing handles the vertical axis: a finding recorded against a
service or class applies, directionally, to the instances below it. No
relationship is needed for this.
Two relationships, carried in the BOM or catalog layer rather than in the PURL,
handle the rest:
form and its aggregator-qualified forms). A product-level finding recorded
against the bare form propagates across these (CycloneDX
pedigree.variants,SPDX
VARIANT_OF).and links two equally-canonical domains for the same owner where the namespace
rule does not fully converge.
Relationship to CycloneDX and SPDX
In CycloneDX 2.0, services are represented as
components, which carrypurl; apkg:serviceidentifier attaches directly to such a component. This proposal isintended to align
type: servicewith the component type CycloneDX 2.0 uses forservice-shaped components. Runtime endpoints are component-level
evidence/occurrence, not identity. A
native_idis identity-bearing onlywhen the component is intentionally described at the native-instance level. In
SPDX, the identifier is an external identifier on the corresponding element,
with deployment relationships expressed via SPDX relationships.
Resolution
There is no single public repository for all products. Resolution is a catalog
lookup:
instance.
resource_typeis present, include the resource class.native_idis present, treat it as the provider-native resourceidentifier; use catalog metadata only for service, product, and class context.
context.
operator parties, lifecycle status, and product relationships.
Package-collection tooling that crawls standard registries should treat
pkg:serviceas valid PURL syntax but not automatically collectable without aproduct-catalog backend.
Candidate type-definition JSON
{ "$schema": "https://packageurl.org/schemas/purl-type-definition.schema-1.0.json", "$id": "https://packageurl.org/types/service-definition.json", "type": "service", "type_name": "Service", "description": "Hosted, managed, or otherwise service-shaped software with no distributable artifact or authoritative package registry: hosted products, model APIs, managed platforms, provider-defined resource classes, and specific provider-native resource instances. Use an existing package-ecosystem type when the referent has a canonical package-registry identity.", "repository": { "use_repository": false, "note": "There is no default repository for services. Resolution uses a product catalog." }, "namespace_definition": { "requirement": "required", "case_sensitive": false, "permitted_characters": "^[a-z0-9.-]+(:[a-z0-9.-]+)*$", "native_name": "vendor namespace", "normalization_rules": [ "The canonical namespace is the domain of the vendor, division, or intermediary responsible for the offering, not the parent conglomerate, in DNS order and lowercase (for example openai.com, aws.amazon.com).", "An aggregator-qualified namespace prefixes the vendor namespace with one or more intermediary namespaces separated by a colon; segments read left to right with the rightmost segment as the originating vendor (for example openrouter.ai:openai.com).", "Colon is reserved as the aggregation separator; namespace segments shall not contain a literal colon or a percent-encoding that decodes to a colon; empty segments are not permitted." ], "note": "The namespace identifies the product owner, not a URL authority." }, "name_definition": { "requirement": "required", "case_sensitive": false, "permitted_characters": "^[a-z0-9][a-z0-9._-]*$", "native_name": "product identifier", "normalization_rules": [ "Use the vendor's API-accepted identifier where one exists; otherwise a lowercase, URL-safe slug of the vendor's stable product name.", "Where the vendor identifier carries a separable snapshot or date, place that in version and keep the stable product identity in name." ] }, "version_definition": { "requirement": "optional", "case_sensitive": true, "native_name": "product, model, snapshot, date, or API version", "note": "An opaque vendor-provided string." }, "qualifiers_definition": [ { "key": "resource_type", "requirement": "optional", "description": "Provider-defined resource class within the named service. Identity-bearing: narrows the referent to a kind of resource." }, { "key": "native_id", "requirement": "optional", "description": "Provider-native canonical identifier for a specific deployed resource (AWS ARN, Azure Resource ID, GCP resource name, or equivalent). Identity-bearing: narrows the referent to a specific instance. Carries region/account/tenant inside the canonical id; percent-encoded as with all qualifier values." } ], "examples": [ "pkg:service/openai.com/gpt-4o", "pkg:service/openai.com/gpt-4o@2024-08-06", "pkg:service/mistral.ai/mistral-small-24b-instruct@2501", "pkg:service/openrouter.ai:openai.com/gpt-4o", "pkg:service/aws.amazon.com:anthropic.com/claude-sonnet-4", "pkg:service/aws.amazon.com/ec2?resource_type=instance", "pkg:service/cloud.google.com/kubernetes-engine?resource_type=cluster", "pkg:service/aws.amazon.com/ec2?native_id=arn:aws:ec2:us-west-2:123456789012:instance%2Fi-0abcdef1234567890&resource_type=instance" ], "note": "Identity is vendor namespace plus canonical product name plus responsible intermediary when distinct; the delivery URL is never part of identity." }Summary of key decisions
/namespace cannot; a distinct surface is a distinct referent.resource_typeandnative_idare identity-bearing.@version(#257).native_id, not as qualifiers.References
https://ecma-tc54.github.io/ECMA-427/https://ecma-international.org/publications-and-standards/standards/ecma-427/https://github.com/package-url/purl-spec/tree/main/typeshttps://raw.githubusercontent.com/package-url/purl-spec/main/schemas/purl-type-definition.schema-1.0.jsonhttps://github.com/package-url/purl-spec/issues/127https://github.com/package-url/purl-spec/issues/257Beta Was this translation helpful? Give feedback.
All reactions