@red-hat-developer-hub/backstage-plugin-ai-skills-common 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-ai-skills-common
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0a42ebd: Bump minor after the release-2.1 cut so main patch versions do not collide with the maintenance line on npm.
8
+
3
9
  ## 0.4.0
4
10
 
5
11
  ### Minor Changes
@@ -4,6 +4,11 @@ var crypto = require('crypto');
4
4
  var tagConstants = require('./tag-constants.cjs.js');
5
5
  var validation = require('./validation.cjs.js');
6
6
 
7
+ const ANNOTATION_AI_ASSET_CATEGORY = "rhdh.io/ai-asset-category";
8
+ const ANNOTATION_AI_ASSET_VERSION = "rhdh.io/ai-asset-version";
9
+ const ANNOTATION_AI_ASSET_SOURCE = "rhdh.io/ai-asset-source";
10
+ const ANNOTATION_OCI_SKILL_REF = "rhdh.io/oci-skill-ref";
11
+ const ANNOTATION_NPX_SKILL_REF = "rhdh.io/npx-skill-ref";
7
12
  function computeCatalogName(tuple) {
8
13
  const payload = JSON.stringify([tuple.type, tuple.id, tuple.key]);
9
14
  const hash = crypto.createHash("sha256").update(payload, "utf8").digest("hex");
@@ -187,6 +192,11 @@ function parseNpxRef(ref) {
187
192
  return { ref, sourceUri, digest };
188
193
  }
189
194
 
195
+ exports.ANNOTATION_AI_ASSET_CATEGORY = ANNOTATION_AI_ASSET_CATEGORY;
196
+ exports.ANNOTATION_AI_ASSET_SOURCE = ANNOTATION_AI_ASSET_SOURCE;
197
+ exports.ANNOTATION_AI_ASSET_VERSION = ANNOTATION_AI_ASSET_VERSION;
198
+ exports.ANNOTATION_NPX_SKILL_REF = ANNOTATION_NPX_SKILL_REF;
199
+ exports.ANNOTATION_OCI_SKILL_REF = ANNOTATION_OCI_SKILL_REF;
190
200
  exports.buildNpxRef = buildNpxRef;
191
201
  exports.buildOciRef = buildOciRef;
192
202
  exports.computeCatalogName = computeCatalogName;
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-helpers.cjs.js","sources":["../src/catalog-helpers.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Pure identity, tag, version, and source-reference helpers for\n * catalog entity mapping (design D5).\n *\n * These helpers are consumed by the common catalog provider and have\n * no network, database, or Catalog side effects.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'crypto';\n\nimport { CATALOG_TAG_PATTERN, MAX_TAG_LENGTH } from './tag-constants';\nimport type { SkillSourceType } from './types';\nimport { isValidDigest } from './validation';\n\n// ─── Identity ────────────────────────────────────────────────────────\n\n/**\n * The identity tuple used for deterministic catalog entity naming.\n *\n * @public\n */\nexport interface IdentityTuple {\n /** Source type discriminator. */\n type: SkillSourceType;\n /** Configured source ID. */\n id: string;\n /** Stable record key within the source. */\n key: string;\n}\n\n/**\n * Computes the deterministic catalog entity name from an identity tuple.\n *\n * Per design D5, the name is `skill-` followed by the first 56 lowercase\n * hex digits of the SHA-256 hash of the compact JSON UTF-8 encoding of\n * `[source.type, source.id, record.key]`.\n *\n * Names, versions, tags, and content digests do not affect entity\n * identity. A source ID or native key change creates a different name.\n *\n * @remarks\n * The hashing algorithm (SHA-256 of compact JSON identity tuple, truncated\n * to 56 hex digits) is part of the stable public contract. Persisted\n * entities depend on this producing the same output for the same input.\n * Any change to the algorithm constitutes a breaking (major) change.\n *\n * @public\n */\nexport function computeCatalogName(tuple: IdentityTuple): string {\n const payload = JSON.stringify([tuple.type, tuple.id, tuple.key]);\n const hash = createHash('sha256').update(payload, 'utf8').digest('hex');\n return `skill-${hash.substring(0, 56)}`;\n}\n\n// ─── Tag normalization ───────────────────────────────────────────────\n\n/**\n * Result of tag normalization.\n *\n * @public\n */\nexport interface NormalizeTagsResult {\n /** Valid, deduplicated tags in lowercase. */\n tags: string[];\n /**\n * Diagnostic messages for tags that were dropped.\n *\n * @remarks\n * Message text is informational and may change. Consumers must not\n * treat wording as a stable API or use it for programmatic decisions.\n */\n diagnostics: string[];\n}\n\n/**\n * Normalizes tags for catalog use per design D3/D5.\n *\n * Trims whitespace, lowercases, deduplicates, and retains only values\n * accepted by {@link CATALOG_TAG_PATTERN} (per design D3/D5). Invalid or\n * overlength values are omitted with a diagnostic — tags are never\n * truncated and replacement characters are never synthesized.\n *\n * @public\n */\nexport function normalizeTags(tags: string[]): NormalizeTagsResult {\n const seen = new Set<string>();\n const result: string[] = [];\n const diagnostics: string[] = [];\n\n for (const raw of tags) {\n const trimmed = raw.trim().toLocaleLowerCase('en-US');\n\n if (trimmed.length === 0) {\n diagnostics.push(`tag '${raw}': empty after trim, omitted`);\n continue;\n }\n\n if (seen.has(trimmed)) {\n diagnostics.push(`tag '${raw}': duplicate '${trimmed}', omitted`);\n continue;\n }\n\n if (trimmed.length > MAX_TAG_LENGTH) {\n diagnostics.push(\n `tag '${raw}': length ${trimmed.length} exceeds maximum ${MAX_TAG_LENGTH}, omitted`,\n );\n seen.add(trimmed);\n continue;\n }\n\n if (!CATALOG_TAG_PATTERN.test(trimmed)) {\n diagnostics.push(\n `tag '${raw}': '${trimmed}' does not match catalog tag pattern, omitted`,\n );\n seen.add(trimmed);\n continue;\n }\n\n seen.add(trimmed);\n result.push(trimmed);\n }\n\n return { tags: result, diagnostics };\n}\n\n// ─── SemVer fallback ─────────────────────────────────────────────────\n\n/**\n * SemVer validation regex per https://semver.org/#is-there-a-suggested-regular-expression-to-check-a-semver-string\n *\n * Matches: MAJOR.MINOR.PATCH with optional pre-release and build metadata.\n */\nconst SEMVER_RE =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/;\n\n/**\n * Resolves a catalog version from a declared version string and record\n * digest, per design D5.\n *\n * Removes at most one leading `v`, validates the result as SemVer, and\n * falls back to `0.0.0+<first 12 hex digits of record.digest>` when no\n * valid SemVer is present.\n *\n * @param declaredVersion - The raw version string from the record, or undefined.\n * @param digest - The record's content digest in `sha256:<64 hex>` format.\n * @returns A valid SemVer string.\n *\n * @public\n */\nexport function resolveVersion(\n declaredVersion: string | undefined,\n digest: string,\n): string {\n // Validate digest upfront, consistent with buildOciRef and buildNpxRef.\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n\n if (declaredVersion !== undefined && declaredVersion !== '') {\n let candidate = declaredVersion;\n if (candidate.startsWith('v')) {\n candidate = candidate.substring(1);\n }\n if (SEMVER_RE.test(candidate)) {\n return candidate;\n }\n }\n\n // Fallback: 0.0.0+<first 12 hex digits of digest>\n // digest format is sha256:<64 hex>, so hex starts at index 7\n const hex = digest.substring(7, 19);\n return `0.0.0+${hex}`;\n}\n\n// ─── OCI references ──────────────────────────────────────────────────\n\n/**\n * A parsed OCI skill reference.\n *\n * @public\n */\nexport interface OciRef {\n /** Full OCI URI: `oci://<registry>/<repository>@sha256:<hex>`. */\n uri: string;\n /** Registry host (and optional port). */\n registry: string;\n /** Repository path (may contain slashes). */\n repository: string;\n /** Content digest in `sha256:<64 hex>` format. */\n digest: string;\n}\n\n/**\n * OCI URI pattern: `oci://<registry>/<repository>@sha256:<64 hex digits>`.\n */\nconst OCI_URI_RE = /^oci:\\/\\/([^/]+)\\/(.+)@(sha256:[0-9a-f]{64})$/;\n\n/**\n * Constructs a digest-addressed OCI reference URI.\n *\n * The returned URI has the form `oci://<registry>/<repository>@sha256:<hex>`.\n * The provided digest must be a valid SHA-256 digest string.\n *\n * @param registry - Registry host (e.g., `quay.io`).\n * @param repository - Repository path (e.g., `octo/hello-world-skill`).\n * @param digest - Content digest in `sha256:<64 hex>` format.\n * @returns The constructed OCI URI.\n * @throws If the digest is invalid.\n *\n * @public\n */\nexport function buildOciRef(\n registry: string,\n repository: string,\n digest: string,\n): string {\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n if (!registry || !repository) {\n throw new Error(\n `registry and repository must be non-empty: registry=${JSON.stringify(\n registry,\n )}, repository=${JSON.stringify(repository)}`,\n );\n }\n return `oci://${registry}/${repository}@${digest}`;\n}\n\n/**\n * Parses a digest-addressed OCI reference URI.\n *\n * Rejects URIs that do not match the `oci://<registry>/<repository>@sha256:<hex>`\n * pattern. When a `recordDigest` is provided, validates that the URI digest\n * agrees with the record digest.\n *\n * @param uri - The OCI URI to parse.\n * @param recordDigest - Optional record digest to verify agreement.\n * @returns The parsed OCI reference.\n * @throws If the URI is malformed or the digest does not agree.\n *\n * @public\n */\nexport function parseOciRef(uri: string, recordDigest?: string): OciRef {\n const match = OCI_URI_RE.exec(uri);\n if (!match) {\n throw new Error(`malformed OCI URI: ${uri}`);\n }\n\n const [, registry, repository, digest] = match;\n\n if (recordDigest !== undefined && digest !== recordDigest) {\n throw new Error(\n `OCI URI digest '${digest}' does not match record digest '${recordDigest}'`,\n );\n }\n\n return { uri, registry, repository, digest };\n}\n\n// ─── npx references ─────────────────────────────────────────────────\n\n/**\n * A parsed npx skill reference.\n *\n * @public\n */\nexport interface NpxRef {\n /** Serialized reference: `<sourceUri>#<digest>`. */\n ref: string;\n /** The artifact source URL (HTTPS, no credentials or fragments). */\n sourceUri: string;\n /** Content digest in `sha256:<64 hex>` format. */\n digest: string;\n}\n\n/**\n * Query parameter names that indicate credentials or signed access tokens.\n * Individually rejected regardless of context.\n */\nconst SENSITIVE_QUERY_PARAMS: ReadonlySet<string> = new Set([\n 'sig',\n 'signature',\n 'token',\n 'access_token',\n 'api_key',\n 'apikey',\n 'secret',\n 'password',\n 'private_token',\n 'auth_token',\n 'bearer_token',\n 'x-amz-credential',\n 'x-amz-signature',\n 'x-amz-security-token',\n 'x-goog-signature',\n 'client_secret',\n 'refresh_token',\n]);\n\n/**\n * Azure SAS token query parameters. These short, common names (`se`, `sp`,\n * `sv`, etc.) can appear in non-credential contexts, so they are only\n * rejected when {@link AZURE_SAS_CO_OCCURRENCE_THRESHOLD} or more appear\n * together — a strong signal of a signed URL.\n */\nconst AZURE_SAS_PARAMS: ReadonlySet<string> = new Set([\n 'se', // expiry\n 'sp', // permissions\n 'spr', // protocol\n 'sv', // version\n 'ss', // services\n 'srt', // resource types\n 'st', // start time\n]);\n\n/** Minimum number of Azure SAS params that must co-occur to trigger rejection. */\nconst AZURE_SAS_CO_OCCURRENCE_THRESHOLD = 3;\n\n/**\n * Checks whether a URL contains credentials (userinfo), fragments,\n * query-string credentials, or signed access tokens.\n *\n * @param url - The URL to inspect.\n * @param context - Optional context prefix for error messages (e.g., `\"npx reference\"`).\n * @throws If the URL contains sensitive elements.\n */\nfunction rejectSensitiveUrl(url: URL, context?: string): void {\n const prefix = context ? `${context}: ` : '';\n\n if (url.username || url.password) {\n throw new Error(`${prefix}URL contains credentials`);\n }\n\n if (url.hash) {\n throw new Error(`${prefix}URL contains a fragment`);\n }\n\n let azureSasCount = 0;\n for (const [param] of url.searchParams) {\n const lower = param.toLocaleLowerCase('en-US');\n if (SENSITIVE_QUERY_PARAMS.has(lower)) {\n throw new Error(\n `${prefix}URL contains sensitive query parameter '${param}'`,\n );\n }\n if (AZURE_SAS_PARAMS.has(lower)) {\n azureSasCount++;\n }\n }\n\n if (azureSasCount >= AZURE_SAS_CO_OCCURRENCE_THRESHOLD) {\n throw new Error(\n `${prefix}URL contains Azure SAS token parameters (${azureSasCount} of ${AZURE_SAS_PARAMS.size} present)`,\n );\n }\n}\n\n/**\n * Constructs an npx skill reference from a verified artifact URL and digest.\n *\n * The reference has the form `<sourceUri>#<digest>` where `sourceUri` is an\n * absolute HTTPS URL without credentials or fragments, and `digest` is a\n * lowercase SHA-256 digest.\n *\n * Non-sensitive query parameters retain their original order.\n *\n * @param sourceUri - Absolute HTTPS artifact URL.\n * @param digest - Content digest in `sha256:<64 hex>` format.\n * @returns The serialized npx reference string.\n * @throws If the URL contains credentials, fragments, or sensitive params,\n * or if the digest is invalid, or the URL is not HTTPS.\n *\n * @public\n */\nexport function buildNpxRef(sourceUri: string, digest: string): string {\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n\n let url: URL;\n try {\n url = new URL(sourceUri);\n } catch {\n throw new Error('invalid URL');\n }\n\n if (url.protocol !== 'https:') {\n throw new Error('npx reference: URL must use HTTPS');\n }\n\n rejectSensitiveUrl(url, 'npx reference');\n\n // Serialize using URL API to normalize, preserving query order\n return `${url.href}#${digest}`;\n}\n\n/**\n * Parses an npx skill reference.\n *\n * Splits at the single literal `#` separator. Validates that the URL is\n * HTTPS, contains no credentials or fragments, and that the digest is a\n * valid SHA-256 digest.\n *\n * @param ref - The serialized npx reference (`<sourceUri>#<digest>`).\n * @returns The parsed npx reference.\n * @throws If the reference is malformed or the URL is sensitive.\n *\n * @public\n */\nexport function parseNpxRef(ref: string): NpxRef {\n // Split at the last '#' to separate URL from digest\n const hashIndex = ref.lastIndexOf('#');\n if (hashIndex === -1) {\n throw new Error(\"malformed npx reference (no '#' separator)\");\n }\n\n const sourceUri = ref.substring(0, hashIndex);\n const digest = ref.substring(hashIndex + 1);\n\n if (!isValidDigest(digest)) {\n throw new Error('malformed npx reference: invalid digest');\n }\n\n let url: URL;\n try {\n url = new URL(sourceUri);\n } catch {\n throw new Error('malformed npx reference: invalid URL');\n }\n\n if (url.protocol !== 'https:') {\n throw new Error('npx reference: URL must use HTTPS');\n }\n\n rejectSensitiveUrl(url, 'npx reference');\n\n return { ref, sourceUri, digest };\n}\n"],"names":["createHash","MAX_TAG_LENGTH","CATALOG_TAG_PATTERN","isValidDigest"],"mappings":"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"}
1
+ {"version":3,"file":"catalog-helpers.cjs.js","sources":["../src/catalog-helpers.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Pure identity, tag, version, and source-reference helpers for\n * catalog entity mapping (design D5).\n *\n * These helpers are consumed by the common catalog provider and have\n * no network, database, or Catalog side effects.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'crypto';\n\nimport { CATALOG_TAG_PATTERN, MAX_TAG_LENGTH } from './tag-constants';\nimport type { SkillSourceType } from './types';\nimport { isValidDigest } from './validation';\n\n// ─── Annotation key constants ───────────────────────────────────────\n\n/**\n * Annotation key for the AI asset category (e.g., `skill`).\n *\n * @public\n */\nexport const ANNOTATION_AI_ASSET_CATEGORY = 'rhdh.io/ai-asset-category';\n\n/**\n * Annotation key for the resolved AI asset version.\n *\n * @public\n */\nexport const ANNOTATION_AI_ASSET_VERSION = 'rhdh.io/ai-asset-version';\n\n/**\n * Annotation key for the AI asset source (`<sourceType>/<sourceId>`).\n *\n * @public\n */\nexport const ANNOTATION_AI_ASSET_SOURCE = 'rhdh.io/ai-asset-source';\n\n/**\n * Annotation key for the OCI skill image reference.\n *\n * @public\n */\nexport const ANNOTATION_OCI_SKILL_REF = 'rhdh.io/oci-skill-ref';\n\n/**\n * Annotation key for the npx skill package reference with digest.\n *\n * @public\n */\nexport const ANNOTATION_NPX_SKILL_REF = 'rhdh.io/npx-skill-ref';\n\n// ─── Identity ────────────────────────────────────────────────────────\n\n/**\n * The identity tuple used for deterministic catalog entity naming.\n *\n * @public\n */\nexport interface IdentityTuple {\n /** Source type discriminator. */\n type: SkillSourceType;\n /** Configured source ID. */\n id: string;\n /** Stable record key within the source. */\n key: string;\n}\n\n/**\n * Computes the deterministic catalog entity name from an identity tuple.\n *\n * Per design D5, the name is `skill-` followed by the first 56 lowercase\n * hex digits of the SHA-256 hash of the compact JSON UTF-8 encoding of\n * `[source.type, source.id, record.key]`.\n *\n * Names, versions, tags, and content digests do not affect entity\n * identity. A source ID or native key change creates a different name.\n *\n * @remarks\n * The hashing algorithm (SHA-256 of compact JSON identity tuple, truncated\n * to 56 hex digits) is part of the stable public contract. Persisted\n * entities depend on this producing the same output for the same input.\n * Any change to the algorithm constitutes a breaking (major) change.\n *\n * @public\n */\nexport function computeCatalogName(tuple: IdentityTuple): string {\n const payload = JSON.stringify([tuple.type, tuple.id, tuple.key]);\n const hash = createHash('sha256').update(payload, 'utf8').digest('hex');\n return `skill-${hash.substring(0, 56)}`;\n}\n\n// ─── Tag normalization ───────────────────────────────────────────────\n\n/**\n * Result of tag normalization.\n *\n * @public\n */\nexport interface NormalizeTagsResult {\n /** Valid, deduplicated tags in lowercase. */\n tags: string[];\n /**\n * Diagnostic messages for tags that were dropped.\n *\n * @remarks\n * Message text is informational and may change. Consumers must not\n * treat wording as a stable API or use it for programmatic decisions.\n */\n diagnostics: string[];\n}\n\n/**\n * Normalizes tags for catalog use per design D3/D5.\n *\n * Trims whitespace, lowercases, deduplicates, and retains only values\n * accepted by {@link CATALOG_TAG_PATTERN} (per design D3/D5). Invalid or\n * overlength values are omitted with a diagnostic — tags are never\n * truncated and replacement characters are never synthesized.\n *\n * @public\n */\nexport function normalizeTags(tags: string[]): NormalizeTagsResult {\n const seen = new Set<string>();\n const result: string[] = [];\n const diagnostics: string[] = [];\n\n for (const raw of tags) {\n const trimmed = raw.trim().toLocaleLowerCase('en-US');\n\n if (trimmed.length === 0) {\n diagnostics.push(`tag '${raw}': empty after trim, omitted`);\n continue;\n }\n\n if (seen.has(trimmed)) {\n diagnostics.push(`tag '${raw}': duplicate '${trimmed}', omitted`);\n continue;\n }\n\n if (trimmed.length > MAX_TAG_LENGTH) {\n diagnostics.push(\n `tag '${raw}': length ${trimmed.length} exceeds maximum ${MAX_TAG_LENGTH}, omitted`,\n );\n seen.add(trimmed);\n continue;\n }\n\n if (!CATALOG_TAG_PATTERN.test(trimmed)) {\n diagnostics.push(\n `tag '${raw}': '${trimmed}' does not match catalog tag pattern, omitted`,\n );\n seen.add(trimmed);\n continue;\n }\n\n seen.add(trimmed);\n result.push(trimmed);\n }\n\n return { tags: result, diagnostics };\n}\n\n// ─── SemVer fallback ─────────────────────────────────────────────────\n\n/**\n * SemVer validation regex per https://semver.org/#is-there-a-suggested-regular-expression-to-check-a-semver-string\n *\n * Matches: MAJOR.MINOR.PATCH with optional pre-release and build metadata.\n */\nconst SEMVER_RE =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/;\n\n/**\n * Resolves a catalog version from a declared version string and record\n * digest, per design D5.\n *\n * Removes at most one leading `v`, validates the result as SemVer, and\n * falls back to `0.0.0+<first 12 hex digits of record.digest>` when no\n * valid SemVer is present.\n *\n * @param declaredVersion - The raw version string from the record, or undefined.\n * @param digest - The record's content digest in `sha256:<64 hex>` format.\n * @returns A valid SemVer string.\n *\n * @public\n */\nexport function resolveVersion(\n declaredVersion: string | undefined,\n digest: string,\n): string {\n // Validate digest upfront, consistent with buildOciRef and buildNpxRef.\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n\n if (declaredVersion !== undefined && declaredVersion !== '') {\n let candidate = declaredVersion;\n if (candidate.startsWith('v')) {\n candidate = candidate.substring(1);\n }\n if (SEMVER_RE.test(candidate)) {\n return candidate;\n }\n }\n\n // Fallback: 0.0.0+<first 12 hex digits of digest>\n // digest format is sha256:<64 hex>, so hex starts at index 7\n const hex = digest.substring(7, 19);\n return `0.0.0+${hex}`;\n}\n\n// ─── OCI references ──────────────────────────────────────────────────\n\n/**\n * A parsed OCI skill reference.\n *\n * @public\n */\nexport interface OciRef {\n /** Full OCI URI: `oci://<registry>/<repository>@sha256:<hex>`. */\n uri: string;\n /** Registry host (and optional port). */\n registry: string;\n /** Repository path (may contain slashes). */\n repository: string;\n /** Content digest in `sha256:<64 hex>` format. */\n digest: string;\n}\n\n/**\n * OCI URI pattern: `oci://<registry>/<repository>@sha256:<64 hex digits>`.\n */\nconst OCI_URI_RE = /^oci:\\/\\/([^/]+)\\/(.+)@(sha256:[0-9a-f]{64})$/;\n\n/**\n * Constructs a digest-addressed OCI reference URI.\n *\n * The returned URI has the form `oci://<registry>/<repository>@sha256:<hex>`.\n * The provided digest must be a valid SHA-256 digest string.\n *\n * @param registry - Registry host (e.g., `quay.io`).\n * @param repository - Repository path (e.g., `octo/hello-world-skill`).\n * @param digest - Content digest in `sha256:<64 hex>` format.\n * @returns The constructed OCI URI.\n * @throws If the digest is invalid.\n *\n * @public\n */\nexport function buildOciRef(\n registry: string,\n repository: string,\n digest: string,\n): string {\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n if (!registry || !repository) {\n throw new Error(\n `registry and repository must be non-empty: registry=${JSON.stringify(\n registry,\n )}, repository=${JSON.stringify(repository)}`,\n );\n }\n return `oci://${registry}/${repository}@${digest}`;\n}\n\n/**\n * Parses a digest-addressed OCI reference URI.\n *\n * Rejects URIs that do not match the `oci://<registry>/<repository>@sha256:<hex>`\n * pattern. When a `recordDigest` is provided, validates that the URI digest\n * agrees with the record digest.\n *\n * @param uri - The OCI URI to parse.\n * @param recordDigest - Optional record digest to verify agreement.\n * @returns The parsed OCI reference.\n * @throws If the URI is malformed or the digest does not agree.\n *\n * @public\n */\nexport function parseOciRef(uri: string, recordDigest?: string): OciRef {\n const match = OCI_URI_RE.exec(uri);\n if (!match) {\n throw new Error(`malformed OCI URI: ${uri}`);\n }\n\n const [, registry, repository, digest] = match;\n\n if (recordDigest !== undefined && digest !== recordDigest) {\n throw new Error(\n `OCI URI digest '${digest}' does not match record digest '${recordDigest}'`,\n );\n }\n\n return { uri, registry, repository, digest };\n}\n\n// ─── npx references ─────────────────────────────────────────────────\n\n/**\n * A parsed npx skill reference.\n *\n * @public\n */\nexport interface NpxRef {\n /** Serialized reference: `<sourceUri>#<digest>`. */\n ref: string;\n /** The artifact source URL (HTTPS, no credentials or fragments). */\n sourceUri: string;\n /** Content digest in `sha256:<64 hex>` format. */\n digest: string;\n}\n\n/**\n * Query parameter names that indicate credentials or signed access tokens.\n * Individually rejected regardless of context.\n */\nconst SENSITIVE_QUERY_PARAMS: ReadonlySet<string> = new Set([\n 'sig',\n 'signature',\n 'token',\n 'access_token',\n 'api_key',\n 'apikey',\n 'secret',\n 'password',\n 'private_token',\n 'auth_token',\n 'bearer_token',\n 'x-amz-credential',\n 'x-amz-signature',\n 'x-amz-security-token',\n 'x-goog-signature',\n 'client_secret',\n 'refresh_token',\n]);\n\n/**\n * Azure SAS token query parameters. These short, common names (`se`, `sp`,\n * `sv`, etc.) can appear in non-credential contexts, so they are only\n * rejected when {@link AZURE_SAS_CO_OCCURRENCE_THRESHOLD} or more appear\n * together — a strong signal of a signed URL.\n */\nconst AZURE_SAS_PARAMS: ReadonlySet<string> = new Set([\n 'se', // expiry\n 'sp', // permissions\n 'spr', // protocol\n 'sv', // version\n 'ss', // services\n 'srt', // resource types\n 'st', // start time\n]);\n\n/** Minimum number of Azure SAS params that must co-occur to trigger rejection. */\nconst AZURE_SAS_CO_OCCURRENCE_THRESHOLD = 3;\n\n/**\n * Checks whether a URL contains credentials (userinfo), fragments,\n * query-string credentials, or signed access tokens.\n *\n * @param url - The URL to inspect.\n * @param context - Optional context prefix for error messages (e.g., `\"npx reference\"`).\n * @throws If the URL contains sensitive elements.\n */\nfunction rejectSensitiveUrl(url: URL, context?: string): void {\n const prefix = context ? `${context}: ` : '';\n\n if (url.username || url.password) {\n throw new Error(`${prefix}URL contains credentials`);\n }\n\n if (url.hash) {\n throw new Error(`${prefix}URL contains a fragment`);\n }\n\n let azureSasCount = 0;\n for (const [param] of url.searchParams) {\n const lower = param.toLocaleLowerCase('en-US');\n if (SENSITIVE_QUERY_PARAMS.has(lower)) {\n throw new Error(\n `${prefix}URL contains sensitive query parameter '${param}'`,\n );\n }\n if (AZURE_SAS_PARAMS.has(lower)) {\n azureSasCount++;\n }\n }\n\n if (azureSasCount >= AZURE_SAS_CO_OCCURRENCE_THRESHOLD) {\n throw new Error(\n `${prefix}URL contains Azure SAS token parameters (${azureSasCount} of ${AZURE_SAS_PARAMS.size} present)`,\n );\n }\n}\n\n/**\n * Constructs an npx skill reference from a verified artifact URL and digest.\n *\n * The reference has the form `<sourceUri>#<digest>` where `sourceUri` is an\n * absolute HTTPS URL without credentials or fragments, and `digest` is a\n * lowercase SHA-256 digest.\n *\n * Non-sensitive query parameters retain their original order.\n *\n * @param sourceUri - Absolute HTTPS artifact URL.\n * @param digest - Content digest in `sha256:<64 hex>` format.\n * @returns The serialized npx reference string.\n * @throws If the URL contains credentials, fragments, or sensitive params,\n * or if the digest is invalid, or the URL is not HTTPS.\n *\n * @public\n */\nexport function buildNpxRef(sourceUri: string, digest: string): string {\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n\n let url: URL;\n try {\n url = new URL(sourceUri);\n } catch {\n throw new Error('invalid URL');\n }\n\n if (url.protocol !== 'https:') {\n throw new Error('npx reference: URL must use HTTPS');\n }\n\n rejectSensitiveUrl(url, 'npx reference');\n\n // Serialize using URL API to normalize, preserving query order\n return `${url.href}#${digest}`;\n}\n\n/**\n * Parses an npx skill reference.\n *\n * Splits at the single literal `#` separator. Validates that the URL is\n * HTTPS, contains no credentials or fragments, and that the digest is a\n * valid SHA-256 digest.\n *\n * @param ref - The serialized npx reference (`<sourceUri>#<digest>`).\n * @returns The parsed npx reference.\n * @throws If the reference is malformed or the URL is sensitive.\n *\n * @public\n */\nexport function parseNpxRef(ref: string): NpxRef {\n // Split at the last '#' to separate URL from digest\n const hashIndex = ref.lastIndexOf('#');\n if (hashIndex === -1) {\n throw new Error(\"malformed npx reference (no '#' separator)\");\n }\n\n const sourceUri = ref.substring(0, hashIndex);\n const digest = ref.substring(hashIndex + 1);\n\n if (!isValidDigest(digest)) {\n throw new Error('malformed npx reference: invalid digest');\n }\n\n let url: URL;\n try {\n url = new URL(sourceUri);\n } catch {\n throw new Error('malformed npx reference: invalid URL');\n }\n\n if (url.protocol !== 'https:') {\n throw new Error('npx reference: URL must use HTTPS');\n }\n\n rejectSensitiveUrl(url, 'npx reference');\n\n return { ref, sourceUri, digest };\n}\n"],"names":["createHash","MAX_TAG_LENGTH","CATALOG_TAG_PATTERN","isValidDigest"],"mappings":"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"}
@@ -2,6 +2,11 @@ import { createHash } from 'crypto';
2
2
  import { MAX_TAG_LENGTH, CATALOG_TAG_PATTERN } from './tag-constants.esm.js';
3
3
  import { isValidDigest } from './validation.esm.js';
4
4
 
5
+ const ANNOTATION_AI_ASSET_CATEGORY = "rhdh.io/ai-asset-category";
6
+ const ANNOTATION_AI_ASSET_VERSION = "rhdh.io/ai-asset-version";
7
+ const ANNOTATION_AI_ASSET_SOURCE = "rhdh.io/ai-asset-source";
8
+ const ANNOTATION_OCI_SKILL_REF = "rhdh.io/oci-skill-ref";
9
+ const ANNOTATION_NPX_SKILL_REF = "rhdh.io/npx-skill-ref";
5
10
  function computeCatalogName(tuple) {
6
11
  const payload = JSON.stringify([tuple.type, tuple.id, tuple.key]);
7
12
  const hash = createHash("sha256").update(payload, "utf8").digest("hex");
@@ -185,5 +190,5 @@ function parseNpxRef(ref) {
185
190
  return { ref, sourceUri, digest };
186
191
  }
187
192
 
188
- export { buildNpxRef, buildOciRef, computeCatalogName, normalizeTags, parseNpxRef, parseOciRef, resolveVersion };
193
+ export { ANNOTATION_AI_ASSET_CATEGORY, ANNOTATION_AI_ASSET_SOURCE, ANNOTATION_AI_ASSET_VERSION, ANNOTATION_NPX_SKILL_REF, ANNOTATION_OCI_SKILL_REF, buildNpxRef, buildOciRef, computeCatalogName, normalizeTags, parseNpxRef, parseOciRef, resolveVersion };
189
194
  //# sourceMappingURL=catalog-helpers.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-helpers.esm.js","sources":["../src/catalog-helpers.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Pure identity, tag, version, and source-reference helpers for\n * catalog entity mapping (design D5).\n *\n * These helpers are consumed by the common catalog provider and have\n * no network, database, or Catalog side effects.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'crypto';\n\nimport { CATALOG_TAG_PATTERN, MAX_TAG_LENGTH } from './tag-constants';\nimport type { SkillSourceType } from './types';\nimport { isValidDigest } from './validation';\n\n// ─── Identity ────────────────────────────────────────────────────────\n\n/**\n * The identity tuple used for deterministic catalog entity naming.\n *\n * @public\n */\nexport interface IdentityTuple {\n /** Source type discriminator. */\n type: SkillSourceType;\n /** Configured source ID. */\n id: string;\n /** Stable record key within the source. */\n key: string;\n}\n\n/**\n * Computes the deterministic catalog entity name from an identity tuple.\n *\n * Per design D5, the name is `skill-` followed by the first 56 lowercase\n * hex digits of the SHA-256 hash of the compact JSON UTF-8 encoding of\n * `[source.type, source.id, record.key]`.\n *\n * Names, versions, tags, and content digests do not affect entity\n * identity. A source ID or native key change creates a different name.\n *\n * @remarks\n * The hashing algorithm (SHA-256 of compact JSON identity tuple, truncated\n * to 56 hex digits) is part of the stable public contract. Persisted\n * entities depend on this producing the same output for the same input.\n * Any change to the algorithm constitutes a breaking (major) change.\n *\n * @public\n */\nexport function computeCatalogName(tuple: IdentityTuple): string {\n const payload = JSON.stringify([tuple.type, tuple.id, tuple.key]);\n const hash = createHash('sha256').update(payload, 'utf8').digest('hex');\n return `skill-${hash.substring(0, 56)}`;\n}\n\n// ─── Tag normalization ───────────────────────────────────────────────\n\n/**\n * Result of tag normalization.\n *\n * @public\n */\nexport interface NormalizeTagsResult {\n /** Valid, deduplicated tags in lowercase. */\n tags: string[];\n /**\n * Diagnostic messages for tags that were dropped.\n *\n * @remarks\n * Message text is informational and may change. Consumers must not\n * treat wording as a stable API or use it for programmatic decisions.\n */\n diagnostics: string[];\n}\n\n/**\n * Normalizes tags for catalog use per design D3/D5.\n *\n * Trims whitespace, lowercases, deduplicates, and retains only values\n * accepted by {@link CATALOG_TAG_PATTERN} (per design D3/D5). Invalid or\n * overlength values are omitted with a diagnostic — tags are never\n * truncated and replacement characters are never synthesized.\n *\n * @public\n */\nexport function normalizeTags(tags: string[]): NormalizeTagsResult {\n const seen = new Set<string>();\n const result: string[] = [];\n const diagnostics: string[] = [];\n\n for (const raw of tags) {\n const trimmed = raw.trim().toLocaleLowerCase('en-US');\n\n if (trimmed.length === 0) {\n diagnostics.push(`tag '${raw}': empty after trim, omitted`);\n continue;\n }\n\n if (seen.has(trimmed)) {\n diagnostics.push(`tag '${raw}': duplicate '${trimmed}', omitted`);\n continue;\n }\n\n if (trimmed.length > MAX_TAG_LENGTH) {\n diagnostics.push(\n `tag '${raw}': length ${trimmed.length} exceeds maximum ${MAX_TAG_LENGTH}, omitted`,\n );\n seen.add(trimmed);\n continue;\n }\n\n if (!CATALOG_TAG_PATTERN.test(trimmed)) {\n diagnostics.push(\n `tag '${raw}': '${trimmed}' does not match catalog tag pattern, omitted`,\n );\n seen.add(trimmed);\n continue;\n }\n\n seen.add(trimmed);\n result.push(trimmed);\n }\n\n return { tags: result, diagnostics };\n}\n\n// ─── SemVer fallback ─────────────────────────────────────────────────\n\n/**\n * SemVer validation regex per https://semver.org/#is-there-a-suggested-regular-expression-to-check-a-semver-string\n *\n * Matches: MAJOR.MINOR.PATCH with optional pre-release and build metadata.\n */\nconst SEMVER_RE =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/;\n\n/**\n * Resolves a catalog version from a declared version string and record\n * digest, per design D5.\n *\n * Removes at most one leading `v`, validates the result as SemVer, and\n * falls back to `0.0.0+<first 12 hex digits of record.digest>` when no\n * valid SemVer is present.\n *\n * @param declaredVersion - The raw version string from the record, or undefined.\n * @param digest - The record's content digest in `sha256:<64 hex>` format.\n * @returns A valid SemVer string.\n *\n * @public\n */\nexport function resolveVersion(\n declaredVersion: string | undefined,\n digest: string,\n): string {\n // Validate digest upfront, consistent with buildOciRef and buildNpxRef.\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n\n if (declaredVersion !== undefined && declaredVersion !== '') {\n let candidate = declaredVersion;\n if (candidate.startsWith('v')) {\n candidate = candidate.substring(1);\n }\n if (SEMVER_RE.test(candidate)) {\n return candidate;\n }\n }\n\n // Fallback: 0.0.0+<first 12 hex digits of digest>\n // digest format is sha256:<64 hex>, so hex starts at index 7\n const hex = digest.substring(7, 19);\n return `0.0.0+${hex}`;\n}\n\n// ─── OCI references ──────────────────────────────────────────────────\n\n/**\n * A parsed OCI skill reference.\n *\n * @public\n */\nexport interface OciRef {\n /** Full OCI URI: `oci://<registry>/<repository>@sha256:<hex>`. */\n uri: string;\n /** Registry host (and optional port). */\n registry: string;\n /** Repository path (may contain slashes). */\n repository: string;\n /** Content digest in `sha256:<64 hex>` format. */\n digest: string;\n}\n\n/**\n * OCI URI pattern: `oci://<registry>/<repository>@sha256:<64 hex digits>`.\n */\nconst OCI_URI_RE = /^oci:\\/\\/([^/]+)\\/(.+)@(sha256:[0-9a-f]{64})$/;\n\n/**\n * Constructs a digest-addressed OCI reference URI.\n *\n * The returned URI has the form `oci://<registry>/<repository>@sha256:<hex>`.\n * The provided digest must be a valid SHA-256 digest string.\n *\n * @param registry - Registry host (e.g., `quay.io`).\n * @param repository - Repository path (e.g., `octo/hello-world-skill`).\n * @param digest - Content digest in `sha256:<64 hex>` format.\n * @returns The constructed OCI URI.\n * @throws If the digest is invalid.\n *\n * @public\n */\nexport function buildOciRef(\n registry: string,\n repository: string,\n digest: string,\n): string {\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n if (!registry || !repository) {\n throw new Error(\n `registry and repository must be non-empty: registry=${JSON.stringify(\n registry,\n )}, repository=${JSON.stringify(repository)}`,\n );\n }\n return `oci://${registry}/${repository}@${digest}`;\n}\n\n/**\n * Parses a digest-addressed OCI reference URI.\n *\n * Rejects URIs that do not match the `oci://<registry>/<repository>@sha256:<hex>`\n * pattern. When a `recordDigest` is provided, validates that the URI digest\n * agrees with the record digest.\n *\n * @param uri - The OCI URI to parse.\n * @param recordDigest - Optional record digest to verify agreement.\n * @returns The parsed OCI reference.\n * @throws If the URI is malformed or the digest does not agree.\n *\n * @public\n */\nexport function parseOciRef(uri: string, recordDigest?: string): OciRef {\n const match = OCI_URI_RE.exec(uri);\n if (!match) {\n throw new Error(`malformed OCI URI: ${uri}`);\n }\n\n const [, registry, repository, digest] = match;\n\n if (recordDigest !== undefined && digest !== recordDigest) {\n throw new Error(\n `OCI URI digest '${digest}' does not match record digest '${recordDigest}'`,\n );\n }\n\n return { uri, registry, repository, digest };\n}\n\n// ─── npx references ─────────────────────────────────────────────────\n\n/**\n * A parsed npx skill reference.\n *\n * @public\n */\nexport interface NpxRef {\n /** Serialized reference: `<sourceUri>#<digest>`. */\n ref: string;\n /** The artifact source URL (HTTPS, no credentials or fragments). */\n sourceUri: string;\n /** Content digest in `sha256:<64 hex>` format. */\n digest: string;\n}\n\n/**\n * Query parameter names that indicate credentials or signed access tokens.\n * Individually rejected regardless of context.\n */\nconst SENSITIVE_QUERY_PARAMS: ReadonlySet<string> = new Set([\n 'sig',\n 'signature',\n 'token',\n 'access_token',\n 'api_key',\n 'apikey',\n 'secret',\n 'password',\n 'private_token',\n 'auth_token',\n 'bearer_token',\n 'x-amz-credential',\n 'x-amz-signature',\n 'x-amz-security-token',\n 'x-goog-signature',\n 'client_secret',\n 'refresh_token',\n]);\n\n/**\n * Azure SAS token query parameters. These short, common names (`se`, `sp`,\n * `sv`, etc.) can appear in non-credential contexts, so they are only\n * rejected when {@link AZURE_SAS_CO_OCCURRENCE_THRESHOLD} or more appear\n * together — a strong signal of a signed URL.\n */\nconst AZURE_SAS_PARAMS: ReadonlySet<string> = new Set([\n 'se', // expiry\n 'sp', // permissions\n 'spr', // protocol\n 'sv', // version\n 'ss', // services\n 'srt', // resource types\n 'st', // start time\n]);\n\n/** Minimum number of Azure SAS params that must co-occur to trigger rejection. */\nconst AZURE_SAS_CO_OCCURRENCE_THRESHOLD = 3;\n\n/**\n * Checks whether a URL contains credentials (userinfo), fragments,\n * query-string credentials, or signed access tokens.\n *\n * @param url - The URL to inspect.\n * @param context - Optional context prefix for error messages (e.g., `\"npx reference\"`).\n * @throws If the URL contains sensitive elements.\n */\nfunction rejectSensitiveUrl(url: URL, context?: string): void {\n const prefix = context ? `${context}: ` : '';\n\n if (url.username || url.password) {\n throw new Error(`${prefix}URL contains credentials`);\n }\n\n if (url.hash) {\n throw new Error(`${prefix}URL contains a fragment`);\n }\n\n let azureSasCount = 0;\n for (const [param] of url.searchParams) {\n const lower = param.toLocaleLowerCase('en-US');\n if (SENSITIVE_QUERY_PARAMS.has(lower)) {\n throw new Error(\n `${prefix}URL contains sensitive query parameter '${param}'`,\n );\n }\n if (AZURE_SAS_PARAMS.has(lower)) {\n azureSasCount++;\n }\n }\n\n if (azureSasCount >= AZURE_SAS_CO_OCCURRENCE_THRESHOLD) {\n throw new Error(\n `${prefix}URL contains Azure SAS token parameters (${azureSasCount} of ${AZURE_SAS_PARAMS.size} present)`,\n );\n }\n}\n\n/**\n * Constructs an npx skill reference from a verified artifact URL and digest.\n *\n * The reference has the form `<sourceUri>#<digest>` where `sourceUri` is an\n * absolute HTTPS URL without credentials or fragments, and `digest` is a\n * lowercase SHA-256 digest.\n *\n * Non-sensitive query parameters retain their original order.\n *\n * @param sourceUri - Absolute HTTPS artifact URL.\n * @param digest - Content digest in `sha256:<64 hex>` format.\n * @returns The serialized npx reference string.\n * @throws If the URL contains credentials, fragments, or sensitive params,\n * or if the digest is invalid, or the URL is not HTTPS.\n *\n * @public\n */\nexport function buildNpxRef(sourceUri: string, digest: string): string {\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n\n let url: URL;\n try {\n url = new URL(sourceUri);\n } catch {\n throw new Error('invalid URL');\n }\n\n if (url.protocol !== 'https:') {\n throw new Error('npx reference: URL must use HTTPS');\n }\n\n rejectSensitiveUrl(url, 'npx reference');\n\n // Serialize using URL API to normalize, preserving query order\n return `${url.href}#${digest}`;\n}\n\n/**\n * Parses an npx skill reference.\n *\n * Splits at the single literal `#` separator. Validates that the URL is\n * HTTPS, contains no credentials or fragments, and that the digest is a\n * valid SHA-256 digest.\n *\n * @param ref - The serialized npx reference (`<sourceUri>#<digest>`).\n * @returns The parsed npx reference.\n * @throws If the reference is malformed or the URL is sensitive.\n *\n * @public\n */\nexport function parseNpxRef(ref: string): NpxRef {\n // Split at the last '#' to separate URL from digest\n const hashIndex = ref.lastIndexOf('#');\n if (hashIndex === -1) {\n throw new Error(\"malformed npx reference (no '#' separator)\");\n }\n\n const sourceUri = ref.substring(0, hashIndex);\n const digest = ref.substring(hashIndex + 1);\n\n if (!isValidDigest(digest)) {\n throw new Error('malformed npx reference: invalid digest');\n }\n\n let url: URL;\n try {\n url = new URL(sourceUri);\n } catch {\n throw new Error('malformed npx reference: invalid URL');\n }\n\n if (url.protocol !== 'https:') {\n throw new Error('npx reference: URL must use HTTPS');\n }\n\n rejectSensitiveUrl(url, 'npx reference');\n\n return { ref, sourceUri, digest };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"catalog-helpers.esm.js","sources":["../src/catalog-helpers.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Pure identity, tag, version, and source-reference helpers for\n * catalog entity mapping (design D5).\n *\n * These helpers are consumed by the common catalog provider and have\n * no network, database, or Catalog side effects.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'crypto';\n\nimport { CATALOG_TAG_PATTERN, MAX_TAG_LENGTH } from './tag-constants';\nimport type { SkillSourceType } from './types';\nimport { isValidDigest } from './validation';\n\n// ─── Annotation key constants ───────────────────────────────────────\n\n/**\n * Annotation key for the AI asset category (e.g., `skill`).\n *\n * @public\n */\nexport const ANNOTATION_AI_ASSET_CATEGORY = 'rhdh.io/ai-asset-category';\n\n/**\n * Annotation key for the resolved AI asset version.\n *\n * @public\n */\nexport const ANNOTATION_AI_ASSET_VERSION = 'rhdh.io/ai-asset-version';\n\n/**\n * Annotation key for the AI asset source (`<sourceType>/<sourceId>`).\n *\n * @public\n */\nexport const ANNOTATION_AI_ASSET_SOURCE = 'rhdh.io/ai-asset-source';\n\n/**\n * Annotation key for the OCI skill image reference.\n *\n * @public\n */\nexport const ANNOTATION_OCI_SKILL_REF = 'rhdh.io/oci-skill-ref';\n\n/**\n * Annotation key for the npx skill package reference with digest.\n *\n * @public\n */\nexport const ANNOTATION_NPX_SKILL_REF = 'rhdh.io/npx-skill-ref';\n\n// ─── Identity ────────────────────────────────────────────────────────\n\n/**\n * The identity tuple used for deterministic catalog entity naming.\n *\n * @public\n */\nexport interface IdentityTuple {\n /** Source type discriminator. */\n type: SkillSourceType;\n /** Configured source ID. */\n id: string;\n /** Stable record key within the source. */\n key: string;\n}\n\n/**\n * Computes the deterministic catalog entity name from an identity tuple.\n *\n * Per design D5, the name is `skill-` followed by the first 56 lowercase\n * hex digits of the SHA-256 hash of the compact JSON UTF-8 encoding of\n * `[source.type, source.id, record.key]`.\n *\n * Names, versions, tags, and content digests do not affect entity\n * identity. A source ID or native key change creates a different name.\n *\n * @remarks\n * The hashing algorithm (SHA-256 of compact JSON identity tuple, truncated\n * to 56 hex digits) is part of the stable public contract. Persisted\n * entities depend on this producing the same output for the same input.\n * Any change to the algorithm constitutes a breaking (major) change.\n *\n * @public\n */\nexport function computeCatalogName(tuple: IdentityTuple): string {\n const payload = JSON.stringify([tuple.type, tuple.id, tuple.key]);\n const hash = createHash('sha256').update(payload, 'utf8').digest('hex');\n return `skill-${hash.substring(0, 56)}`;\n}\n\n// ─── Tag normalization ───────────────────────────────────────────────\n\n/**\n * Result of tag normalization.\n *\n * @public\n */\nexport interface NormalizeTagsResult {\n /** Valid, deduplicated tags in lowercase. */\n tags: string[];\n /**\n * Diagnostic messages for tags that were dropped.\n *\n * @remarks\n * Message text is informational and may change. Consumers must not\n * treat wording as a stable API or use it for programmatic decisions.\n */\n diagnostics: string[];\n}\n\n/**\n * Normalizes tags for catalog use per design D3/D5.\n *\n * Trims whitespace, lowercases, deduplicates, and retains only values\n * accepted by {@link CATALOG_TAG_PATTERN} (per design D3/D5). Invalid or\n * overlength values are omitted with a diagnostic — tags are never\n * truncated and replacement characters are never synthesized.\n *\n * @public\n */\nexport function normalizeTags(tags: string[]): NormalizeTagsResult {\n const seen = new Set<string>();\n const result: string[] = [];\n const diagnostics: string[] = [];\n\n for (const raw of tags) {\n const trimmed = raw.trim().toLocaleLowerCase('en-US');\n\n if (trimmed.length === 0) {\n diagnostics.push(`tag '${raw}': empty after trim, omitted`);\n continue;\n }\n\n if (seen.has(trimmed)) {\n diagnostics.push(`tag '${raw}': duplicate '${trimmed}', omitted`);\n continue;\n }\n\n if (trimmed.length > MAX_TAG_LENGTH) {\n diagnostics.push(\n `tag '${raw}': length ${trimmed.length} exceeds maximum ${MAX_TAG_LENGTH}, omitted`,\n );\n seen.add(trimmed);\n continue;\n }\n\n if (!CATALOG_TAG_PATTERN.test(trimmed)) {\n diagnostics.push(\n `tag '${raw}': '${trimmed}' does not match catalog tag pattern, omitted`,\n );\n seen.add(trimmed);\n continue;\n }\n\n seen.add(trimmed);\n result.push(trimmed);\n }\n\n return { tags: result, diagnostics };\n}\n\n// ─── SemVer fallback ─────────────────────────────────────────────────\n\n/**\n * SemVer validation regex per https://semver.org/#is-there-a-suggested-regular-expression-to-check-a-semver-string\n *\n * Matches: MAJOR.MINOR.PATCH with optional pre-release and build metadata.\n */\nconst SEMVER_RE =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/;\n\n/**\n * Resolves a catalog version from a declared version string and record\n * digest, per design D5.\n *\n * Removes at most one leading `v`, validates the result as SemVer, and\n * falls back to `0.0.0+<first 12 hex digits of record.digest>` when no\n * valid SemVer is present.\n *\n * @param declaredVersion - The raw version string from the record, or undefined.\n * @param digest - The record's content digest in `sha256:<64 hex>` format.\n * @returns A valid SemVer string.\n *\n * @public\n */\nexport function resolveVersion(\n declaredVersion: string | undefined,\n digest: string,\n): string {\n // Validate digest upfront, consistent with buildOciRef and buildNpxRef.\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n\n if (declaredVersion !== undefined && declaredVersion !== '') {\n let candidate = declaredVersion;\n if (candidate.startsWith('v')) {\n candidate = candidate.substring(1);\n }\n if (SEMVER_RE.test(candidate)) {\n return candidate;\n }\n }\n\n // Fallback: 0.0.0+<first 12 hex digits of digest>\n // digest format is sha256:<64 hex>, so hex starts at index 7\n const hex = digest.substring(7, 19);\n return `0.0.0+${hex}`;\n}\n\n// ─── OCI references ──────────────────────────────────────────────────\n\n/**\n * A parsed OCI skill reference.\n *\n * @public\n */\nexport interface OciRef {\n /** Full OCI URI: `oci://<registry>/<repository>@sha256:<hex>`. */\n uri: string;\n /** Registry host (and optional port). */\n registry: string;\n /** Repository path (may contain slashes). */\n repository: string;\n /** Content digest in `sha256:<64 hex>` format. */\n digest: string;\n}\n\n/**\n * OCI URI pattern: `oci://<registry>/<repository>@sha256:<64 hex digits>`.\n */\nconst OCI_URI_RE = /^oci:\\/\\/([^/]+)\\/(.+)@(sha256:[0-9a-f]{64})$/;\n\n/**\n * Constructs a digest-addressed OCI reference URI.\n *\n * The returned URI has the form `oci://<registry>/<repository>@sha256:<hex>`.\n * The provided digest must be a valid SHA-256 digest string.\n *\n * @param registry - Registry host (e.g., `quay.io`).\n * @param repository - Repository path (e.g., `octo/hello-world-skill`).\n * @param digest - Content digest in `sha256:<64 hex>` format.\n * @returns The constructed OCI URI.\n * @throws If the digest is invalid.\n *\n * @public\n */\nexport function buildOciRef(\n registry: string,\n repository: string,\n digest: string,\n): string {\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n if (!registry || !repository) {\n throw new Error(\n `registry and repository must be non-empty: registry=${JSON.stringify(\n registry,\n )}, repository=${JSON.stringify(repository)}`,\n );\n }\n return `oci://${registry}/${repository}@${digest}`;\n}\n\n/**\n * Parses a digest-addressed OCI reference URI.\n *\n * Rejects URIs that do not match the `oci://<registry>/<repository>@sha256:<hex>`\n * pattern. When a `recordDigest` is provided, validates that the URI digest\n * agrees with the record digest.\n *\n * @param uri - The OCI URI to parse.\n * @param recordDigest - Optional record digest to verify agreement.\n * @returns The parsed OCI reference.\n * @throws If the URI is malformed or the digest does not agree.\n *\n * @public\n */\nexport function parseOciRef(uri: string, recordDigest?: string): OciRef {\n const match = OCI_URI_RE.exec(uri);\n if (!match) {\n throw new Error(`malformed OCI URI: ${uri}`);\n }\n\n const [, registry, repository, digest] = match;\n\n if (recordDigest !== undefined && digest !== recordDigest) {\n throw new Error(\n `OCI URI digest '${digest}' does not match record digest '${recordDigest}'`,\n );\n }\n\n return { uri, registry, repository, digest };\n}\n\n// ─── npx references ─────────────────────────────────────────────────\n\n/**\n * A parsed npx skill reference.\n *\n * @public\n */\nexport interface NpxRef {\n /** Serialized reference: `<sourceUri>#<digest>`. */\n ref: string;\n /** The artifact source URL (HTTPS, no credentials or fragments). */\n sourceUri: string;\n /** Content digest in `sha256:<64 hex>` format. */\n digest: string;\n}\n\n/**\n * Query parameter names that indicate credentials or signed access tokens.\n * Individually rejected regardless of context.\n */\nconst SENSITIVE_QUERY_PARAMS: ReadonlySet<string> = new Set([\n 'sig',\n 'signature',\n 'token',\n 'access_token',\n 'api_key',\n 'apikey',\n 'secret',\n 'password',\n 'private_token',\n 'auth_token',\n 'bearer_token',\n 'x-amz-credential',\n 'x-amz-signature',\n 'x-amz-security-token',\n 'x-goog-signature',\n 'client_secret',\n 'refresh_token',\n]);\n\n/**\n * Azure SAS token query parameters. These short, common names (`se`, `sp`,\n * `sv`, etc.) can appear in non-credential contexts, so they are only\n * rejected when {@link AZURE_SAS_CO_OCCURRENCE_THRESHOLD} or more appear\n * together — a strong signal of a signed URL.\n */\nconst AZURE_SAS_PARAMS: ReadonlySet<string> = new Set([\n 'se', // expiry\n 'sp', // permissions\n 'spr', // protocol\n 'sv', // version\n 'ss', // services\n 'srt', // resource types\n 'st', // start time\n]);\n\n/** Minimum number of Azure SAS params that must co-occur to trigger rejection. */\nconst AZURE_SAS_CO_OCCURRENCE_THRESHOLD = 3;\n\n/**\n * Checks whether a URL contains credentials (userinfo), fragments,\n * query-string credentials, or signed access tokens.\n *\n * @param url - The URL to inspect.\n * @param context - Optional context prefix for error messages (e.g., `\"npx reference\"`).\n * @throws If the URL contains sensitive elements.\n */\nfunction rejectSensitiveUrl(url: URL, context?: string): void {\n const prefix = context ? `${context}: ` : '';\n\n if (url.username || url.password) {\n throw new Error(`${prefix}URL contains credentials`);\n }\n\n if (url.hash) {\n throw new Error(`${prefix}URL contains a fragment`);\n }\n\n let azureSasCount = 0;\n for (const [param] of url.searchParams) {\n const lower = param.toLocaleLowerCase('en-US');\n if (SENSITIVE_QUERY_PARAMS.has(lower)) {\n throw new Error(\n `${prefix}URL contains sensitive query parameter '${param}'`,\n );\n }\n if (AZURE_SAS_PARAMS.has(lower)) {\n azureSasCount++;\n }\n }\n\n if (azureSasCount >= AZURE_SAS_CO_OCCURRENCE_THRESHOLD) {\n throw new Error(\n `${prefix}URL contains Azure SAS token parameters (${azureSasCount} of ${AZURE_SAS_PARAMS.size} present)`,\n );\n }\n}\n\n/**\n * Constructs an npx skill reference from a verified artifact URL and digest.\n *\n * The reference has the form `<sourceUri>#<digest>` where `sourceUri` is an\n * absolute HTTPS URL without credentials or fragments, and `digest` is a\n * lowercase SHA-256 digest.\n *\n * Non-sensitive query parameters retain their original order.\n *\n * @param sourceUri - Absolute HTTPS artifact URL.\n * @param digest - Content digest in `sha256:<64 hex>` format.\n * @returns The serialized npx reference string.\n * @throws If the URL contains credentials, fragments, or sensitive params,\n * or if the digest is invalid, or the URL is not HTTPS.\n *\n * @public\n */\nexport function buildNpxRef(sourceUri: string, digest: string): string {\n if (!isValidDigest(digest)) {\n throw new Error(`invalid digest: ${digest}`);\n }\n\n let url: URL;\n try {\n url = new URL(sourceUri);\n } catch {\n throw new Error('invalid URL');\n }\n\n if (url.protocol !== 'https:') {\n throw new Error('npx reference: URL must use HTTPS');\n }\n\n rejectSensitiveUrl(url, 'npx reference');\n\n // Serialize using URL API to normalize, preserving query order\n return `${url.href}#${digest}`;\n}\n\n/**\n * Parses an npx skill reference.\n *\n * Splits at the single literal `#` separator. Validates that the URL is\n * HTTPS, contains no credentials or fragments, and that the digest is a\n * valid SHA-256 digest.\n *\n * @param ref - The serialized npx reference (`<sourceUri>#<digest>`).\n * @returns The parsed npx reference.\n * @throws If the reference is malformed or the URL is sensitive.\n *\n * @public\n */\nexport function parseNpxRef(ref: string): NpxRef {\n // Split at the last '#' to separate URL from digest\n const hashIndex = ref.lastIndexOf('#');\n if (hashIndex === -1) {\n throw new Error(\"malformed npx reference (no '#' separator)\");\n }\n\n const sourceUri = ref.substring(0, hashIndex);\n const digest = ref.substring(hashIndex + 1);\n\n if (!isValidDigest(digest)) {\n throw new Error('malformed npx reference: invalid digest');\n }\n\n let url: URL;\n try {\n url = new URL(sourceUri);\n } catch {\n throw new Error('malformed npx reference: invalid URL');\n }\n\n if (url.protocol !== 'https:') {\n throw new Error('npx reference: URL must use HTTPS');\n }\n\n rejectSensitiveUrl(url, 'npx reference');\n\n return { ref, sourceUri, digest };\n}\n"],"names":[],"mappings":"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"}
package/dist/index.cjs.js CHANGED
@@ -19,6 +19,11 @@ exports.isValidDigest = validation.isValidDigest;
19
19
  exports.isValidUtcTimestamp = validation.isValidUtcTimestamp;
20
20
  exports.validateSnapshot = validation.validateSnapshot;
21
21
  exports.validateSnapshotSize = validation.validateSnapshotSize;
22
+ exports.ANNOTATION_AI_ASSET_CATEGORY = catalogHelpers.ANNOTATION_AI_ASSET_CATEGORY;
23
+ exports.ANNOTATION_AI_ASSET_SOURCE = catalogHelpers.ANNOTATION_AI_ASSET_SOURCE;
24
+ exports.ANNOTATION_AI_ASSET_VERSION = catalogHelpers.ANNOTATION_AI_ASSET_VERSION;
25
+ exports.ANNOTATION_NPX_SKILL_REF = catalogHelpers.ANNOTATION_NPX_SKILL_REF;
26
+ exports.ANNOTATION_OCI_SKILL_REF = catalogHelpers.ANNOTATION_OCI_SKILL_REF;
22
27
  exports.buildNpxRef = catalogHelpers.buildNpxRef;
23
28
  exports.buildOciRef = catalogHelpers.buildOciRef;
24
29
  exports.computeCatalogName = catalogHelpers.computeCatalogName;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -265,6 +265,36 @@ declare function isNpxSkillRecord(record: SkillRecord, sourceType: SkillSourceTy
265
265
 
266
266
 
267
267
 
268
+ /**
269
+ * Annotation key for the AI asset category (e.g., `skill`).
270
+ *
271
+ * @public
272
+ */
273
+ declare const ANNOTATION_AI_ASSET_CATEGORY = "rhdh.io/ai-asset-category";
274
+ /**
275
+ * Annotation key for the resolved AI asset version.
276
+ *
277
+ * @public
278
+ */
279
+ declare const ANNOTATION_AI_ASSET_VERSION = "rhdh.io/ai-asset-version";
280
+ /**
281
+ * Annotation key for the AI asset source (`<sourceType>/<sourceId>`).
282
+ *
283
+ * @public
284
+ */
285
+ declare const ANNOTATION_AI_ASSET_SOURCE = "rhdh.io/ai-asset-source";
286
+ /**
287
+ * Annotation key for the OCI skill image reference.
288
+ *
289
+ * @public
290
+ */
291
+ declare const ANNOTATION_OCI_SKILL_REF = "rhdh.io/oci-skill-ref";
292
+ /**
293
+ * Annotation key for the npx skill package reference with digest.
294
+ *
295
+ * @public
296
+ */
297
+ declare const ANNOTATION_NPX_SKILL_REF = "rhdh.io/npx-skill-ref";
268
298
  /**
269
299
  * The identity tuple used for deterministic catalog entity naming.
270
300
  *
@@ -1209,5 +1239,5 @@ declare const npxTrustedInput: TrustedSourceInput;
1209
1239
 
1210
1240
 
1211
1241
 
1212
- export { CATALOG_TAG_PATTERN, MAX_RESPONSE_BYTES, MAX_SNAPSHOT_BYTES, MAX_SNAPSHOT_RECORDS, MAX_TAG_LENGTH, SKILLS_ENDPOINT_BASE, SUPPORTED_SCHEMA_VERSION, boundSnapshot, buildNpxRef, buildOciRef, computeCatalogName, createFailedSnapshot, createLoadingSnapshot, invalidFailedWithSkills, invalidLoadingWithObservedAt, invalidLoadingWithSkills, invalidNpxRecordWithOciExtension, invalidOciRecordUnknownExtKey, invalidOciRecordWithNpxExtension, invalidRecordBadDigest, invalidRecordEmptyKey, invalidRecordShortDigest, invalidSnapshotBadVersion, invalidSnapshotDuplicateKeys, invalidSnapshotNonUtcTimestamp, invalidSnapshotOverlappingKeys, invalidSnapshotReadyWithFailedKeys, isNpxSkillRecord, isOciSkillRecord, isValidDigest, isValidUtcTimestamp, normalizeNpxMetadata, normalizeOciMetadata, normalizeTags, npxDiscoveryEntryFull, npxDiscoveryEntryMinimal, npxFrontmatterConflictingVersion, npxFrontmatterFull, npxNativeInputConflictingVersion, npxNativeInputFull, npxNativeInputMinimal, npxSource, npxTrustedInput, ociMarkdownFrontmatterConflictingVersion, ociMarkdownFrontmatterFull, ociNativeInputConflictingVersion, ociNativeInputFull, ociNativeInputMinimal, ociSkillCardConflictingVersion, ociSkillCardFull, ociSkillCardMinimal, ociSource, ociTrustedInput, parseNpxRef, parseOciRef, resolveVersion, sortRecordsByKey, validFailedSnapshot, validLoadingSnapshot, validNpxRecordFull, validNpxRecordMinimal, validNpxRecordWithQuery, validNpxSnapshotReady, validOciRecordEmptyExtensions, validOciRecordFull, validOciRecordMinimal, validOciSnapshotReady, validPartialEmptySnapshot, validPartialSnapshot, validReadyEmptySnapshot, validateSnapshot, validateSnapshotSize };
1242
+ export { ANNOTATION_AI_ASSET_CATEGORY, ANNOTATION_AI_ASSET_SOURCE, ANNOTATION_AI_ASSET_VERSION, ANNOTATION_NPX_SKILL_REF, ANNOTATION_OCI_SKILL_REF, CATALOG_TAG_PATTERN, MAX_RESPONSE_BYTES, MAX_SNAPSHOT_BYTES, MAX_SNAPSHOT_RECORDS, MAX_TAG_LENGTH, SKILLS_ENDPOINT_BASE, SUPPORTED_SCHEMA_VERSION, boundSnapshot, buildNpxRef, buildOciRef, computeCatalogName, createFailedSnapshot, createLoadingSnapshot, invalidFailedWithSkills, invalidLoadingWithObservedAt, invalidLoadingWithSkills, invalidNpxRecordWithOciExtension, invalidOciRecordUnknownExtKey, invalidOciRecordWithNpxExtension, invalidRecordBadDigest, invalidRecordEmptyKey, invalidRecordShortDigest, invalidSnapshotBadVersion, invalidSnapshotDuplicateKeys, invalidSnapshotNonUtcTimestamp, invalidSnapshotOverlappingKeys, invalidSnapshotReadyWithFailedKeys, isNpxSkillRecord, isOciSkillRecord, isValidDigest, isValidUtcTimestamp, normalizeNpxMetadata, normalizeOciMetadata, normalizeTags, npxDiscoveryEntryFull, npxDiscoveryEntryMinimal, npxFrontmatterConflictingVersion, npxFrontmatterFull, npxNativeInputConflictingVersion, npxNativeInputFull, npxNativeInputMinimal, npxSource, npxTrustedInput, ociMarkdownFrontmatterConflictingVersion, ociMarkdownFrontmatterFull, ociNativeInputConflictingVersion, ociNativeInputFull, ociNativeInputMinimal, ociSkillCardConflictingVersion, ociSkillCardFull, ociSkillCardMinimal, ociSource, ociTrustedInput, parseNpxRef, parseOciRef, resolveVersion, sortRecordsByKey, validFailedSnapshot, validLoadingSnapshot, validNpxRecordFull, validNpxRecordMinimal, validNpxRecordWithQuery, validNpxSnapshotReady, validOciRecordEmptyExtensions, validOciRecordFull, validOciRecordMinimal, validOciSnapshotReady, validPartialEmptySnapshot, validPartialSnapshot, validReadyEmptySnapshot, validateSnapshot, validateSnapshotSize };
1213
1243
  export type { BoundSnapshotOptions, FrontmatterMetadata, IdentityTuple, MarkdownFrontmatter, NormalizationDiagnostic, NormalizationResult, NormalizeTagsResult, NpxDiscoveryEntry, NpxExtensions, NpxFrontmatter, NpxNativeInput, NpxRef, NpxSkillRecord, OciExtensions, OciMarkdownFrontmatter, OciNativeInput, OciRef, OciSkillCard, OciSkillCardMetadata, OciSkillCardSpec, OciSkillRecord, SkillAuthor, SkillRecord, SkillSnapshot, SkillSnapshotV1, SkillSourceType, SnapshotSource, SnapshotStatus, TrustedSourceInput, ValidationResult };
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { MAX_SNAPSHOT_BYTES, MAX_SNAPSHOT_RECORDS, SUPPORTED_SCHEMA_VERSION, isNpxSkillRecord, isOciSkillRecord, isValidDigest, isValidUtcTimestamp, validateSnapshot, validateSnapshotSize } from './validation.esm.js';
2
- export { buildNpxRef, buildOciRef, computeCatalogName, normalizeTags, parseNpxRef, parseOciRef, resolveVersion } from './catalog-helpers.esm.js';
2
+ export { ANNOTATION_AI_ASSET_CATEGORY, ANNOTATION_AI_ASSET_SOURCE, ANNOTATION_AI_ASSET_VERSION, ANNOTATION_NPX_SKILL_REF, ANNOTATION_OCI_SKILL_REF, buildNpxRef, buildOciRef, computeCatalogName, normalizeTags, parseNpxRef, parseOciRef, resolveVersion } from './catalog-helpers.esm.js';
3
3
  export { boundSnapshot, createFailedSnapshot, createLoadingSnapshot, sortRecordsByKey } from './snapshot.esm.js';
4
4
  export { MAX_RESPONSE_BYTES, SKILLS_ENDPOINT_BASE } from './rest-contract.esm.js';
5
5
  export { normalizeNpxMetadata, normalizeOciMetadata } from './normalizer.esm.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-ai-skills-common",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared v1 skill record and snapshot contract for RHDH skill connectors and the common catalog provider",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.cjs.js",