@red-hat-developer-hub/backstage-plugin-catalog-backend-module-ai-resource-extensions 0.4.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 ADDED
@@ -0,0 +1,39 @@
1
+ # @red-hat-developer-hub/backstage-plugin-catalog-backend-module-ai-resource-extensions
2
+
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 238650c: Rename package from the accidentally doubled
8
+ `catalog-backend-module-catalog-backend-module-ai-resource-extensions` path/name
9
+ to `catalog-backend-module-ai-resource-extensions`.
10
+
11
+ ## 0.3.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 55088f9: Aggregate multiple AIResource extension errors into a single response.
16
+ Extract OCI validation into a shared `collectOciErrors` function called by
17
+ `AIResourceExtensionsProcessor` so that scope and OCI constraint violations
18
+ are reported together instead of stopping at the first failure.
19
+ - 8317e6e: Consolidate OCI location validation into `AIResourceExtensionsProcessor`.
20
+ The standalone `AIResourceOciProcessor` class and its public export have been
21
+ removed; OCI format checks now run via the internal `collectOciErrors` helper
22
+ called by `AIResourceExtensionsProcessor`.
23
+ - 93847ae: Migrate OCI asset location validation from `spec.location` to the standard
24
+ `backstage.io/source-location` annotation using the Backstage location-ref
25
+ form `url:oci://...`. The annotation is parsed with upstream `parseLocationRef`
26
+ for correct whitespace handling. Bare `oci://...` annotations (without the
27
+ `url:` prefix) are rejected with an actionable error.
28
+
29
+ ### Patch Changes
30
+
31
+ - e168046: Add AIResource example YAML files and extend README with schema field
32
+ reference, registration guidance, and OCI validation-only documentation.
33
+
34
+ ## 0.2.0
35
+
36
+ ### Minor Changes
37
+
38
+ - 6af0114: Add AIResource extension validation via AIResourceExtensionsProcessor, starting with spec.scope
39
+ - 67be6ce: Scaffold new AIResource catalog extension and add upstream module to workspace
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # @red-hat-developer-hub/backstage-plugin-catalog-backend-module-ai-resource-extensions
2
+
3
+ A Backstage catalog backend module that validates RHDH-specific extension
4
+ fields on AIResource entities.
5
+
6
+ ## Validation
7
+
8
+ The module registers a single catalog processor —
9
+ `AIResourceExtensionsProcessor` — for `AIResource` entities.
10
+ Non-AIResource entities pass through unchanged.
11
+
12
+ ### `AIResourceExtensionsProcessor`
13
+
14
+ Validates RHDH extension fields on `AIResource` entities. All constraint
15
+ violations are collected and reported in a single error rather than
16
+ stopping at the first failure.
17
+
18
+ #### `spec.scope`
19
+
20
+ When present, `spec.scope` must be one of:
21
+
22
+ - `organization`
23
+ - `product`
24
+ - `team`
25
+
26
+ Omitting `spec.scope` is valid. Invalid values produce an actionable error
27
+ naming the field, the received value, and the accepted values.
28
+
29
+ #### `backstage.io/source-location` (OCI)
30
+
31
+ When `backstage.io/source-location` uses the location-ref form
32
+ `url:oci://…`, validates that the OCI target is a well-formed
33
+ `oci://registry/repository[:tag|@digest]` URI. Validation is format-only —
34
+ the processor makes no outbound registry or HTTP calls.
35
+
36
+ Bare `oci://…` annotations (without the `url:` prefix) are rejected with
37
+ an actionable error explaining the required `url:oci://…` format. The
38
+ `url:` prefix is required because bare `oci://…` parses as location type
39
+ `oci` with a non-URL target, which would break future UrlReader integration.
40
+
41
+ Non-OCI source-locations (e.g. `url:https://…`) are not subject to OCI
42
+ format rules and pass through without validation.
43
+
44
+ ## Schema Fields
45
+
46
+ An `AIResource` entity uses `apiVersion: backstage.io/v1beta1` and
47
+ `kind: AIResource`. The following fields are relevant:
48
+
49
+ | Field | Enforced by this module | Description |
50
+ | ------------------------------ | ----------------------- | ------------------------------------------------------------------------------------------------ |
51
+ | `spec.type` | no¹ | The type of AI asset (e.g. `model`) |
52
+ | `spec.lifecycle` | no¹ | Lifecycle stage (e.g. `experimental`, `production`) |
53
+ | `spec.owner` | no¹ | Owning entity reference (e.g. `team-data-science`) |
54
+ | `spec.scope` | optional, validated | RHDH extension. Accepted values: `organization`, `product`, `team`. Omitting the field is valid. |
55
+ | `backstage.io/source-location` | when target is `oci://` | Standard Backstage annotation. For OCI assets use `url:oci://…` (see below). |
56
+
57
+ ¹ These fields are typical for Backstage entities but are **not** validated or
58
+ required by the processors in this module. Entities missing these fields will
59
+ pass through without error.
60
+
61
+ ### `spec.scope` Values
62
+
63
+ | Value | Description |
64
+ | -------------- | ----------------------------------------------------- |
65
+ | `organization` | The AI asset is shared across the entire organization |
66
+ | `product` | The AI asset is scoped to a specific product |
67
+ | `team` | The AI asset is scoped to a specific team |
68
+
69
+ ## Registering an AIResource Entity
70
+
71
+ AIResource entities are registered through the standard RHDH catalog
72
+ interfaces — no AIResource-specific registration flow is required.
73
+
74
+ ### Git-backed entities
75
+
76
+ 1. Create a `catalog-info.yaml` with `kind: AIResource`.
77
+ 2. Set `backstage.io/source-location` to the repository URL using the
78
+ standard location-ref form (e.g. `url:https://github.com/my-org/my-repo`)
79
+ so consumers can locate the source repository.
80
+ 3. Register the entity YAML via the RHDH catalog URL registration UI or
81
+ include it in a catalog location for auto-discovery.
82
+
83
+ See [`examples/ai-resource-git.yaml`](../../examples/ai-resource-git.yaml)
84
+ for a complete example.
85
+
86
+ ### OCI-backed entities
87
+
88
+ 1. Create a `catalog-info.yaml` with `kind: AIResource`.
89
+ 2. Set `backstage.io/source-location` to an OCI reference using the
90
+ location-ref form `url:oci://…`
91
+ (e.g. `url:oci://quay.io/my-org/my-model:latest`).
92
+
93
+ **Important:** use the `url:oci://…` form — not bare `oci://…`. The
94
+ `url:` prefix is required by the Backstage location-ref convention.
95
+
96
+ 3. Register the file via the RHDH catalog URL registration UI or include
97
+ it in a catalog location for auto-discovery.
98
+
99
+ See [`examples/ai-resource-oci.yaml`](../../examples/ai-resource-oci.yaml)
100
+ for a complete example.
101
+
102
+ ## OCI Validation-Only Behavior
103
+
104
+ The `AIResourceExtensionsProcessor` validates the `backstage.io/source-location`
105
+ annotation when the location-ref target uses the `oci://` scheme. It checks:
106
+
107
+ - The annotation uses the `url:oci://…` location-ref form (not bare `oci://…`).
108
+ - The OCI target is a non-empty string with no leading or trailing whitespace.
109
+ - The URI contains at least a registry and a repository path
110
+ (e.g. `oci://quay.io/org/model:tag`).
111
+
112
+ **The processor makes zero outbound network calls.** It does not contact
113
+ OCI registries, pull manifests, or verify that the referenced artifact
114
+ exists. The catalog indexes the OCI reference as metadata only.
115
+
116
+ This design is intentional for air-gap safety: RHDH deployments in
117
+ disconnected environments can register OCI-backed AI assets without
118
+ requiring network access to the artifact registry at ingestion time.
119
+ The OCI URI serves as a stable reference that downstream tooling can
120
+ resolve when registry access is available.
121
+
122
+ ## Public API
123
+
124
+ | Export | Description |
125
+ | ------------------------------- | ------------------------------------------------------- |
126
+ | `AIResourceExtensionsProcessor` | `CatalogProcessor` for RHDH AIResource extension fields |
127
+ | `VALID_AI_RESOURCE_SCOPES` | Readonly tuple of accepted scope values |
128
+ | `AIResourceScope` | Type union of accepted scope values |
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ var collectOciErrors = require('./collectOciErrors.cjs.js');
4
+
5
+ const VALID_AI_RESOURCE_SCOPES = [
6
+ "organization",
7
+ "product",
8
+ "team"
9
+ ];
10
+ class AIResourceExtensionsProcessor {
11
+ getProcessorName() {
12
+ return "AIResourceExtensionsProcessor";
13
+ }
14
+ async preProcessEntity(entity, _location, _emit) {
15
+ if (entity.kind !== "AIResource") {
16
+ return entity;
17
+ }
18
+ const errors = [];
19
+ const scope = entity.spec?.scope;
20
+ if (scope !== void 0 && !VALID_AI_RESOURCE_SCOPES.includes(scope)) {
21
+ const accepted = VALID_AI_RESOURCE_SCOPES.map((v) => `'${v}'`).join(", ");
22
+ const sanitized = Array.from(String(scope)).filter((c) => c.charCodeAt(0) > 31).join("").slice(0, 200);
23
+ errors.push(
24
+ `spec.scope has invalid value '${sanitized}'; accepted values are ${accepted}`
25
+ );
26
+ }
27
+ errors.push(...collectOciErrors.collectOciErrors(entity));
28
+ if (errors.length > 0) {
29
+ throw new Error(
30
+ `Validation failed for AIResource entity: ${errors.join("; ")}`
31
+ );
32
+ }
33
+ return entity;
34
+ }
35
+ }
36
+
37
+ exports.AIResourceExtensionsProcessor = AIResourceExtensionsProcessor;
38
+ exports.VALID_AI_RESOURCE_SCOPES = VALID_AI_RESOURCE_SCOPES;
39
+ //# sourceMappingURL=AIResourceExtensionsProcessor.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AIResourceExtensionsProcessor.cjs.js","sources":["../src/AIResourceExtensionsProcessor.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\nimport {\n CatalogProcessor,\n CatalogProcessorEmit,\n} from '@backstage/plugin-catalog-node';\nimport { Entity } from '@backstage/catalog-model';\nimport { LocationSpec } from '@backstage/plugin-catalog-common';\nimport { collectOciErrors } from './collectOciErrors';\n\n/**\n * Valid values for spec.scope on AIResource entities.\n *\n * @public\n */\nexport const VALID_AI_RESOURCE_SCOPES = [\n 'organization',\n 'product',\n 'team',\n] as const;\n\n/**\n * Type for valid AIResource scope values.\n *\n * @public\n */\nexport type AIResourceScope = (typeof VALID_AI_RESOURCE_SCOPES)[number];\n\n/**\n * A CatalogProcessor that validates RHDH-specific extension\n * fields on AIResource entities.\n *\n * Validates:\n * - `spec.scope`: optional field restricted to 'organization',\n * 'product', or 'team'\n * - `backstage.io/source-location`: OCI URI format when the\n * location-ref target uses the `oci://` scheme\n *\n * All constraint violations are collected and reported in a single\n * error rather than stopping at the first failure.\n *\n * @public\n */\nexport class AIResourceExtensionsProcessor implements CatalogProcessor {\n getProcessorName(): string {\n return 'AIResourceExtensionsProcessor';\n }\n\n async preProcessEntity(\n entity: Entity,\n _location: LocationSpec,\n _emit: CatalogProcessorEmit,\n ): Promise<Entity> {\n if (entity.kind !== 'AIResource') {\n return entity;\n }\n\n const errors: string[] = [];\n\n const scope = entity.spec?.scope;\n if (\n scope !== undefined &&\n !VALID_AI_RESOURCE_SCOPES.includes(scope as AIResourceScope)\n ) {\n const accepted = VALID_AI_RESOURCE_SCOPES.map(v => `'${v}'`).join(', ');\n const sanitized = Array.from(String(scope))\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n errors.push(\n `spec.scope has invalid value '${sanitized}'; accepted values are ${accepted}`,\n );\n }\n\n errors.push(...collectOciErrors(entity));\n\n if (errors.length > 0) {\n throw new Error(\n `Validation failed for AIResource entity: ${errors.join('; ')}`,\n );\n }\n\n return entity;\n }\n}\n"],"names":["collectOciErrors"],"mappings":";;;;AA6BO,MAAM,wBAAA,GAA2B;AAAA,EACtC,cAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF;AAwBO,MAAM,6BAAA,CAA0D;AAAA,EACrE,gBAAA,GAA2B;AACzB,IAAA,OAAO,+BAAA;AAAA,EACT;AAAA,EAEA,MAAM,gBAAA,CACJ,MAAA,EACA,SAAA,EACA,KAAA,EACiB;AACjB,IAAA,IAAI,MAAA,CAAO,SAAS,YAAA,EAAc;AAChC,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,SAAmB,EAAC;AAE1B,IAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,EAAM,KAAA;AAC3B,IAAA,IACE,UAAU,MAAA,IACV,CAAC,wBAAA,CAAyB,QAAA,CAAS,KAAwB,CAAA,EAC3D;AACA,MAAA,MAAM,QAAA,GAAW,yBAAyB,GAAA,CAAI,CAAA,CAAA,KAAK,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AACtE,MAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA,CACvC,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,MAAA,MAAA,CAAO,IAAA;AAAA,QACL,CAAA,8BAAA,EAAiC,SAAS,CAAA,uBAAA,EAA0B,QAAQ,CAAA;AAAA,OAC9E;AAAA,IACF;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,GAAGA,iCAAA,CAAiB,MAAM,CAAC,CAAA;AAEvC,IAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,yCAAA,EAA4C,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,OAC/D;AAAA,IACF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;;"}
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ var catalogModel = require('@backstage/catalog-model');
4
+
5
+ function collectOciErrors(entity) {
6
+ const annotation = entity.metadata?.annotations?.[catalogModel.ANNOTATION_SOURCE_LOCATION];
7
+ if (annotation === void 0 || annotation === null) {
8
+ return [];
9
+ }
10
+ let type;
11
+ let target;
12
+ try {
13
+ ({ type, target } = catalogModel.parseLocationRef(annotation));
14
+ } catch {
15
+ return [];
16
+ }
17
+ if (type === "oci") {
18
+ const sanitized = Array.from(annotation.trim()).filter((c) => c.charCodeAt(0) > 31).join("").slice(0, 200);
19
+ return [
20
+ `${catalogModel.ANNOTATION_SOURCE_LOCATION} '${sanitized}' uses a bare oci:// URI; the Backstage location-ref form url:oci://\u2026 is required (e.g. url:oci://quay.io/org/model:tag)`
21
+ ];
22
+ }
23
+ if (type !== "url" || !target.startsWith("oci://")) {
24
+ return [];
25
+ }
26
+ const ociPath = target.slice("oci://".length);
27
+ if (ociPath === "") {
28
+ return [
29
+ `${catalogModel.ANNOTATION_SOURCE_LOCATION} target 'oci://' is not a valid OCI reference; expected format url:oci://registry/repository[:tag|@digest]`
30
+ ];
31
+ }
32
+ const parts = ociPath.split("/");
33
+ if (parts.length < 2 || parts.some((part) => part === "" || /\s/.test(part))) {
34
+ const sanitized = Array.from(target).filter((c) => c.charCodeAt(0) > 31).join("").slice(0, 200);
35
+ return [
36
+ `${catalogModel.ANNOTATION_SOURCE_LOCATION} target '${sanitized}' is not a valid OCI reference; expected format url:oci://registry/repository[:tag|@digest]`
37
+ ];
38
+ }
39
+ return [];
40
+ }
41
+
42
+ exports.collectOciErrors = collectOciErrors;
43
+ //# sourceMappingURL=collectOciErrors.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collectOciErrors.cjs.js","sources":["../src/collectOciErrors.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\nimport {\n ANNOTATION_SOURCE_LOCATION,\n Entity,\n parseLocationRef,\n} from '@backstage/catalog-model';\n\n/**\n * Collects OCI-related validation errors for an AIResource entity\n * without throwing. Returns an array of error messages (empty if valid).\n *\n * Validates the `backstage.io/source-location` annotation when its\n * location-ref target uses the `oci://` scheme. The annotation must\n * use the Backstage location-ref form `url:oci://…`.\n *\n * Uses upstream `parseLocationRef` for parsing, which normalizes\n * leading/trailing whitespace around type and target.\n *\n * @internal\n */\nexport function collectOciErrors(entity: Entity): string[] {\n const annotation = entity.metadata?.annotations?.[ANNOTATION_SOURCE_LOCATION];\n\n if (annotation === undefined || annotation === null) {\n return [];\n }\n\n let type: string;\n let target: string;\n try {\n ({ type, target } = parseLocationRef(annotation));\n } catch {\n // Not a valid location-ref (no colon, empty type/target, etc.)\n // Leave it for other processors to handle.\n return [];\n }\n\n // Detect bare `oci://…` without the required `url:` prefix.\n // parseLocationRef parses `oci://host/path` as type `oci` with a\n // non-URL target, which breaks future UrlReader integration.\n if (type === 'oci') {\n const sanitized = Array.from(annotation.trim())\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n return [\n `${ANNOTATION_SOURCE_LOCATION} '${sanitized}' uses a bare oci:// URI; ` +\n `the Backstage location-ref form url:oci://… is required ` +\n `(e.g. url:oci://quay.io/org/model:tag)`,\n ];\n }\n\n // Only apply OCI validation when the location-ref type is `url` and\n // the target starts with `oci://`.\n if (type !== 'url' || !target.startsWith('oci://')) {\n return [];\n }\n\n const ociPath = target.slice('oci://'.length);\n\n if (ociPath === '') {\n return [\n `${ANNOTATION_SOURCE_LOCATION} target 'oci://' is not a valid OCI ` +\n `reference; expected format url:oci://registry/repository[:tag|@digest]`,\n ];\n }\n\n const parts = ociPath.split('/');\n\n // Reject missing registry/repo, empty segments (e.g. trailing slash),\n // and whitespace inside path segments (e.g. oci:// quay.io/...).\n if (parts.length < 2 || parts.some(part => part === '' || /\\s/.test(part))) {\n const sanitized = Array.from(target)\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n return [\n `${ANNOTATION_SOURCE_LOCATION} target '${sanitized}' is not a valid ` +\n `OCI reference; expected format ` +\n `url:oci://registry/repository[:tag|@digest]`,\n ];\n }\n\n return [];\n}\n"],"names":["ANNOTATION_SOURCE_LOCATION","parseLocationRef"],"mappings":";;;;AAmCO,SAAS,iBAAiB,MAAA,EAA0B;AACzD,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,QAAA,EAAU,WAAA,GAAcA,uCAA0B,CAAA;AAE5E,EAAA,IAAI,UAAA,KAAe,MAAA,IAAa,UAAA,KAAe,IAAA,EAAM;AACnD,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,CAAC,EAAE,IAAA,EAAM,MAAA,EAAO,GAAIC,8BAAiB,UAAU,CAAA;AAAA,EACjD,CAAA,CAAA,MAAQ;AAGN,IAAA,OAAO,EAAC;AAAA,EACV;AAKA,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA,CAC3C,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,IAAA,OAAO;AAAA,MACL,CAAA,EAAGD,uCAA0B,CAAA,EAAA,EAAK,SAAS,CAAA,6HAAA;AAAA,KAG7C;AAAA,EACF;AAIA,EAAA,IAAI,SAAS,KAAA,IAAS,CAAC,MAAA,CAAO,UAAA,CAAW,QAAQ,CAAA,EAAG;AAClD,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AAE5C,EAAA,IAAI,YAAY,EAAA,EAAI;AAClB,IAAA,OAAO;AAAA,MACL,GAAGA,uCAA0B,CAAA,0GAAA;AAAA,KAE/B;AAAA,EACF;AAEA,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAI/B,EAAA,IAAI,KAAA,CAAM,MAAA,GAAS,CAAA,IAAK,KAAA,CAAM,IAAA,CAAK,CAAA,IAAA,KAAQ,IAAA,KAAS,EAAA,IAAM,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,EAAG;AAC1E,IAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA,CAChC,MAAA,CAAO,OAAK,CAAA,CAAE,UAAA,CAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,IAAA,OAAO;AAAA,MACL,CAAA,EAAGA,uCAA0B,CAAA,SAAA,EAAY,SAAS,CAAA,2FAAA;AAAA,KAGpD;AAAA,EACF;AAEA,EAAA,OAAO,EAAC;AACV;;;;"}
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var module$1 = require('./module.cjs.js');
6
+ var AIResourceExtensionsProcessor = require('./AIResourceExtensionsProcessor.cjs.js');
7
+
8
+
9
+
10
+ exports.default = module$1.catalogModuleCatalogBackendModuleAiResourceExtensions;
11
+ exports.AIResourceExtensionsProcessor = AIResourceExtensionsProcessor.AIResourceExtensionsProcessor;
12
+ exports.VALID_AI_RESOURCE_SCOPES = AIResourceExtensionsProcessor.VALID_AI_RESOURCE_SCOPES;
13
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
@@ -0,0 +1,57 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
+ import { CatalogProcessor, CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
3
+ import { Entity } from '@backstage/catalog-model';
4
+ import { LocationSpec } from '@backstage/plugin-catalog-common';
5
+
6
+ /**
7
+ * catalogModuleCatalogBackendModuleAiResourceExtensions is the catalog-backend-module-ai-resource-extensions backend module for the catalog plugin.
8
+ *
9
+ * Registers {@link AIResourceExtensionsProcessor}, which validates all
10
+ * RHDH extension constraints (spec.scope and OCI source-location) and
11
+ * aggregates errors into a single response.
12
+ *
13
+ * @public
14
+ */
15
+ declare const catalogModuleCatalogBackendModuleAiResourceExtensions: _backstage_backend_plugin_api.BackendFeature;
16
+
17
+ /**
18
+ * Valid values for spec.scope on AIResource entities.
19
+ *
20
+ * @public
21
+ */
22
+ declare const VALID_AI_RESOURCE_SCOPES: readonly ["organization", "product", "team"];
23
+ /**
24
+ * Type for valid AIResource scope values.
25
+ *
26
+ * @public
27
+ */
28
+ type AIResourceScope = (typeof VALID_AI_RESOURCE_SCOPES)[number];
29
+ /**
30
+ * A CatalogProcessor that validates RHDH-specific extension
31
+ * fields on AIResource entities.
32
+ *
33
+ * Validates:
34
+ * - `spec.scope`: optional field restricted to 'organization',
35
+ * 'product', or 'team'
36
+ * - `backstage.io/source-location`: OCI URI format when the
37
+ * location-ref target uses the `oci://` scheme
38
+ *
39
+ * All constraint violations are collected and reported in a single
40
+ * error rather than stopping at the first failure.
41
+ *
42
+ * @public
43
+ */
44
+ declare class AIResourceExtensionsProcessor implements CatalogProcessor {
45
+ getProcessorName(): string;
46
+ preProcessEntity(entity: Entity, _location: LocationSpec, _emit: CatalogProcessorEmit): Promise<Entity>;
47
+ }
48
+
49
+ /**
50
+ * The catalog-backend-module-ai-resource-extensions backend module for the catalog plugin.
51
+ *
52
+ * @packageDocumentation
53
+ */
54
+
55
+
56
+ export { AIResourceExtensionsProcessor, VALID_AI_RESOURCE_SCOPES, catalogModuleCatalogBackendModuleAiResourceExtensions as default };
57
+ export type { AIResourceScope };
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var pluginCatalogNode = require('@backstage/plugin-catalog-node');
5
+ var AIResourceExtensionsProcessor = require('./AIResourceExtensionsProcessor.cjs.js');
6
+
7
+ const catalogModuleCatalogBackendModuleAiResourceExtensions = backendPluginApi.createBackendModule({
8
+ pluginId: "catalog",
9
+ moduleId: "catalog-backend-module-ai-resource-extensions",
10
+ register(reg) {
11
+ reg.registerInit({
12
+ deps: {
13
+ catalog: pluginCatalogNode.catalogProcessingExtensionPoint
14
+ },
15
+ async init({ catalog }) {
16
+ catalog.addProcessor(new AIResourceExtensionsProcessor.AIResourceExtensionsProcessor());
17
+ }
18
+ });
19
+ }
20
+ });
21
+
22
+ exports.catalogModuleCatalogBackendModuleAiResourceExtensions = catalogModuleCatalogBackendModuleAiResourceExtensions;
23
+ //# sourceMappingURL=module.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.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 */\nimport { createBackendModule } from '@backstage/backend-plugin-api';\nimport { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';\n\nimport { AIResourceExtensionsProcessor } from './AIResourceExtensionsProcessor';\n\n/**\n * catalogModuleCatalogBackendModuleAiResourceExtensions is the catalog-backend-module-ai-resource-extensions backend module for the catalog plugin.\n *\n * Registers {@link AIResourceExtensionsProcessor}, which validates all\n * RHDH extension constraints (spec.scope and OCI source-location) and\n * aggregates errors into a single response.\n *\n * @public\n */\nexport const catalogModuleCatalogBackendModuleAiResourceExtensions =\n createBackendModule({\n pluginId: 'catalog',\n moduleId: 'catalog-backend-module-ai-resource-extensions',\n register(reg) {\n reg.registerInit({\n deps: {\n catalog: catalogProcessingExtensionPoint,\n },\n async init({ catalog }) {\n catalog.addProcessor(new AIResourceExtensionsProcessor());\n },\n });\n },\n });\n"],"names":["createBackendModule","catalogProcessingExtensionPoint","AIResourceExtensionsProcessor"],"mappings":";;;;;;AA6BO,MAAM,wDACXA,oCAAA,CAAoB;AAAA,EAClB,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,+CAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,OAAA,EAASC;AAAA,OACX;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,OAAA,EAAQ,EAAG;AACtB,QAAA,OAAA,CAAQ,YAAA,CAAa,IAAIC,2DAAA,EAA+B,CAAA;AAAA,MAC1D;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@red-hat-developer-hub/backstage-plugin-catalog-backend-module-ai-resource-extensions",
3
+ "version": "0.4.0",
4
+ "license": "Apache-2.0",
5
+ "description": "The catalog-backend-module-ai-resource-extensions backend module for the catalog plugin.",
6
+ "main": "dist/index.cjs.js",
7
+ "types": "dist/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "main": "dist/index.cjs.js",
11
+ "types": "dist/index.d.ts"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/redhat-developer/rhdh-plugins",
16
+ "directory": "workspaces/ai-integrations/plugins/catalog-backend-module-ai-resource-extensions"
17
+ },
18
+ "backstage": {
19
+ "role": "backend-plugin-module",
20
+ "pluginId": "catalog",
21
+ "pluginPackage": "@backstage/plugin-catalog-backend",
22
+ "features": {
23
+ ".": "@backstage/BackendFeature"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "start": "backstage-cli package start",
28
+ "build": "backstage-cli package build",
29
+ "lint": "backstage-cli package lint",
30
+ "test": "backstage-cli package test",
31
+ "clean": "backstage-cli package clean",
32
+ "prepack": "backstage-cli package prepack",
33
+ "postpack": "backstage-cli package postpack"
34
+ },
35
+ "dependencies": {
36
+ "@backstage/backend-plugin-api": "^1.9.2",
37
+ "@backstage/catalog-model": "^1.9.0",
38
+ "@backstage/plugin-catalog-common": "^1.1.10",
39
+ "@backstage/plugin-catalog-node": "^2.2.2"
40
+ },
41
+ "devDependencies": {
42
+ "@backstage/backend-test-utils": "^1.11.4",
43
+ "@backstage/cli": "^0.36.3"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ],
48
+ "typesVersions": {
49
+ "*": {
50
+ "package.json": [
51
+ "package.json"
52
+ ]
53
+ }
54
+ }
55
+ }