@a5c-ai/kradle 5.0.1-staging.3abdf9534c25
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/Dockerfile +31 -0
- package/README.md +187 -0
- package/bin/kradle-demo.mjs +23 -0
- package/bin/kradle-server.mjs +14 -0
- package/dist/kradle-controller-ui.json +3482 -0
- package/dist/kradle-lifecycle.json +201 -0
- package/dist/kradle-runtime-snapshot.json +3125 -0
- package/dist/kradle-summary.json +724 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/gaps-agent-mux-to-kradle-crds.md +298 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/architecture-v2.md +2759 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/crd-behaviors-and-relationships.md +3926 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/integration-and-design-decisions.md +1530 -0
- package/docs/kradle-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/openapi.yaml +1291 -0
- package/docs/product-requirements.md +62 -0
- package/docs/requirements-v2.md +235 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/sdk-api-reference.md +1108 -0
- package/docs/system-requirements.md +90 -0
- package/docs/system-spec-v2.md +1230 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/todos.md +4 -0
- package/docs/user-stories.md +78 -0
- package/docs/web-console-spec.md +533 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +66 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +95 -0
- package/scripts/validate-ui.mjs +305 -0
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +170 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +549 -0
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-identity-migration.js +115 -0
- package/src/agent-memory-controller.js +357 -0
- package/src/agent-memory-import.js +327 -0
- package/src/agent-memory-query.js +292 -0
- package/src/agent-memory-repository-source-controller.js +255 -0
- package/src/agent-mux-client.js +589 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-persona-controller.js +135 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-prompt-composition.js +55 -0
- package/src/agent-provider-config-controller.js +151 -0
- package/src/agent-secret-config-grant-controller.js +282 -0
- package/src/agent-session-transcript-controller.js +189 -0
- package/src/agent-stack-controller.js +421 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +387 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +621 -0
- package/src/argocd-gitops.js +43 -0
- package/src/artifact-registry-controller.js +542 -0
- package/src/assistant-runtime.js +284 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +310 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +112 -0
- package/src/controller-ui.js +620 -0
- package/src/data-plane.js +179 -0
- package/src/event-bus.js +397 -0
- package/src/external/conflict-controller.js +225 -0
- package/src/external/github/auth.js +96 -0
- package/src/external/github/cicd.js +180 -0
- package/src/external/github/git-forge.js +240 -0
- package/src/external/github/index.js +144 -0
- package/src/external/github/issue-tracking.js +163 -0
- package/src/external/provider-adapter.js +161 -0
- package/src/external/provider-resource-factory.js +221 -0
- package/src/external/sync-controller.js +235 -0
- package/src/external/webhook-controller.js +144 -0
- package/src/external/write-controller.js +283 -0
- package/src/gitea-backend.js +131 -0
- package/src/gitea-service.js +173 -0
- package/src/handoff.js +98 -0
- package/src/health-probes.js +134 -0
- package/src/hooks-events.js +63 -0
- package/src/hooks-lifecycle.js +117 -0
- package/src/http-server.js +409 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +71 -0
- package/src/jitsi-agent-bridge.js +141 -0
- package/src/jitsi-meeting-controller.js +291 -0
- package/src/jitsi-sync-controller.js +198 -0
- package/src/kradle-inference-service-controller.js +246 -0
- package/src/kubernetes-controller-async.js +531 -0
- package/src/kubernetes-controller.js +904 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/model-route-controller.js +364 -0
- package/src/notification-controller.js +178 -0
- package/src/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +282 -0
- package/src/runner-controller.js +272 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/snapshot-cache.js +157 -0
- package/src/virtual-model-controller.js +538 -0
- package/src/virtual-model-hook-bridge.js +200 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +679 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-identity-migration.test.js +87 -0
- package/tests/agent-memory-controller.test.js +461 -0
- package/tests/agent-memory-import-snapshot.test.js +477 -0
- package/tests/agent-memory-query.test.js +404 -0
- package/tests/agent-memory-repository-source.test.js +514 -0
- package/tests/agent-mux-client.test.js +389 -0
- package/tests/agent-mux-integration.test.js +971 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-persona-controller.test.js +127 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-prompt-composition.test.js +76 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +303 -0
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-stack-controller.test.js +283 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-controller.test.js +271 -0
- package/tests/agent-trigger-routes.test.js +190 -0
- package/tests/agent-trigger-sources.test.js +245 -0
- package/tests/agent-workspace-controller.test.js +181 -0
- package/tests/agent-writeback.test.js +292 -0
- package/tests/approval-persistence.test.js +171 -0
- package/tests/artifact-registry.test.js +511 -0
- package/tests/assistant-runtime.test.js +506 -0
- package/tests/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/codespace-controller.test.js +318 -0
- package/tests/controller-client.test.js +133 -0
- package/tests/deployment.test.js +527 -0
- package/tests/e2e/lifecycle.test.js +120 -0
- package/tests/event-bus-integration.test.js +355 -0
- package/tests/external-github-forge.test.js +560 -0
- package/tests/external-github-issues-cicd.test.js +520 -0
- package/tests/external-integration.test.js +470 -0
- package/tests/external-persistence.test.js +415 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +223 -0
- package/tests/external-webhook-sync.test.js +287 -0
- package/tests/external-write-conflict.test.js +353 -0
- package/tests/gitea-service.test.js +253 -0
- package/tests/health-check-real.test.js +165 -0
- package/tests/health-probes.test.js +90 -0
- package/tests/hooks-lifecycle.test.js +364 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/jitsi-agent-bridge.test.js +119 -0
- package/tests/jitsi-helm-integration.test.js +77 -0
- package/tests/jitsi-meeting-controller.test.js +170 -0
- package/tests/jitsi-resource-model.test.js +73 -0
- package/tests/jitsi-sync-controller.test.js +112 -0
- package/tests/kradle-inference-service.test.js +689 -0
- package/tests/kradle.test.js +779 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/model-route-controller.test.js +733 -0
- package/tests/notification-controller.test.js +196 -0
- package/tests/notification-integration.test.js +179 -0
- package/tests/org-scoping.test.js +687 -0
- package/tests/runner-controller.test.js +327 -0
- package/tests/runner-integration.test.js +231 -0
- package/tests/session-cookie-hmac.test.js +151 -0
- package/tests/snapshot-performance.test.js +315 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/virtual-model-controller.test.js +877 -0
- package/tests/virtual-model-hook-bridge.test.js +384 -0
- package/tests/webhook-trigger.test.js +198 -0
- package/tests/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createArtifactRegistryController } from '../src/artifact-registry-controller.js';
|
|
4
|
+
import { validateResource, createResource, RESOURCE_DEFINITIONS, CONFIG_KINDS, AGGREGATED_KINDS, ALL_KINDS } from '../src/resource-model.js';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
function makeRegistry(overrides = {}) {
|
|
11
|
+
return {
|
|
12
|
+
metadata: { name: 'my-npm-registry' },
|
|
13
|
+
spec: {
|
|
14
|
+
organizationRef: 'acme',
|
|
15
|
+
registryType: 'npm',
|
|
16
|
+
storageBackend: 'internal',
|
|
17
|
+
...overrides,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeFeed(overrides = {}) {
|
|
23
|
+
return {
|
|
24
|
+
metadata: { name: 'my-feed' },
|
|
25
|
+
spec: {
|
|
26
|
+
organizationRef: 'acme',
|
|
27
|
+
registryRef: 'my-npm-registry',
|
|
28
|
+
feedName: '@acme',
|
|
29
|
+
...overrides,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeAccessPolicy(overrides = {}) {
|
|
35
|
+
return {
|
|
36
|
+
metadata: { name: 'my-policy' },
|
|
37
|
+
spec: {
|
|
38
|
+
organizationRef: 'acme',
|
|
39
|
+
feedRef: 'my-feed',
|
|
40
|
+
subjects: ['user:alice', 'team:platform'],
|
|
41
|
+
permissions: ['read', 'write'],
|
|
42
|
+
...overrides,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Resource model integration
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
test('resource model includes ArtifactRegistry in CONFIG_KINDS', () => {
|
|
52
|
+
assert.ok(CONFIG_KINDS.has('ArtifactRegistry'));
|
|
53
|
+
assert.ok(CONFIG_KINDS.has('ArtifactFeed'));
|
|
54
|
+
assert.ok(CONFIG_KINDS.has('ArtifactAccessPolicy'));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('resource model includes ArtifactVersion and ArtifactDownload in AGGREGATED_KINDS', () => {
|
|
58
|
+
assert.ok(AGGREGATED_KINDS.has('ArtifactVersion'));
|
|
59
|
+
assert.ok(AGGREGATED_KINDS.has('ArtifactDownload'));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('ALL_KINDS includes all 5 artifact kinds', () => {
|
|
63
|
+
for (const kind of ['ArtifactRegistry', 'ArtifactFeed', 'ArtifactAccessPolicy', 'ArtifactVersion', 'ArtifactDownload']) {
|
|
64
|
+
assert.ok(ALL_KINDS.has(kind), `ALL_KINDS should include ${kind}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('RESOURCE_DEFINITIONS has correct definitions for artifact kinds', () => {
|
|
69
|
+
assert.equal(RESOURCE_DEFINITIONS.ArtifactRegistry.storage, 'etcd');
|
|
70
|
+
assert.equal(RESOURCE_DEFINITIONS.ArtifactRegistry.context, 'artifacts');
|
|
71
|
+
assert.equal(RESOURCE_DEFINITIONS.ArtifactRegistry.plural, 'artifactregistries');
|
|
72
|
+
assert.equal(RESOURCE_DEFINITIONS.ArtifactVersion.storage, 'postgres');
|
|
73
|
+
assert.equal(RESOURCE_DEFINITIONS.ArtifactDownload.storage, 'postgres');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('createResource works for ArtifactRegistry', () => {
|
|
77
|
+
const resource = createResource('ArtifactRegistry', { name: 'test-reg' }, { organizationRef: 'acme', registryType: 'npm', storageBackend: 'internal' });
|
|
78
|
+
assert.equal(resource.kind, 'ArtifactRegistry');
|
|
79
|
+
assert.equal(resource.spec.registryType, 'npm');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('validateResource enforces required spec for ArtifactRegistry', () => {
|
|
83
|
+
const resource = createResource('ArtifactRegistry', { name: 'test-reg' }, { organizationRef: 'acme', registryType: 'npm', storageBackend: 's3' });
|
|
84
|
+
assert.doesNotThrow(() => validateResource(resource));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('validateResource rejects ArtifactRegistry with missing required fields', () => {
|
|
88
|
+
const resource = createResource('ArtifactRegistry', { name: 'test-reg' }, { organizationRef: 'acme' });
|
|
89
|
+
assert.throws(() => validateResource(resource), /registryType/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Registry validation
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
test('validateRegistry accepts valid npm registry config', () => {
|
|
97
|
+
const controller = createArtifactRegistryController();
|
|
98
|
+
const result = controller.validateRegistry(makeRegistry({ registryType: 'npm' }));
|
|
99
|
+
assert.ok(result.valid);
|
|
100
|
+
assert.equal(result.errors.length, 0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('validateRegistry accepts valid docker registry config', () => {
|
|
104
|
+
const controller = createArtifactRegistryController();
|
|
105
|
+
const result = controller.validateRegistry(makeRegistry({ registryType: 'docker', storageBackend: 's3' }));
|
|
106
|
+
assert.ok(result.valid);
|
|
107
|
+
assert.equal(result.errors.length, 0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('validateRegistry accepts valid pip registry config', () => {
|
|
111
|
+
const controller = createArtifactRegistryController();
|
|
112
|
+
const result = controller.validateRegistry(makeRegistry({ registryType: 'pip', storageBackend: 'gcs' }));
|
|
113
|
+
assert.ok(result.valid);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('validateRegistry accepts generic type', () => {
|
|
117
|
+
const controller = createArtifactRegistryController();
|
|
118
|
+
const result = controller.validateRegistry(makeRegistry({ registryType: 'generic', storageBackend: 'azure-blob' }));
|
|
119
|
+
assert.ok(result.valid);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('validateRegistry rejects unknown type', () => {
|
|
123
|
+
const controller = createArtifactRegistryController();
|
|
124
|
+
const result = controller.validateRegistry(makeRegistry({ registryType: 'maven' }));
|
|
125
|
+
assert.ok(!result.valid);
|
|
126
|
+
assert.ok(result.errors.some((e) => e.includes('maven')));
|
|
127
|
+
assert.ok(result.errors.some((e) => e.includes('not supported')));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('validateRegistry rejects missing storageBackend', () => {
|
|
131
|
+
const controller = createArtifactRegistryController();
|
|
132
|
+
const result = controller.validateRegistry({ metadata: { name: 'r' }, spec: { organizationRef: 'acme', registryType: 'npm' } });
|
|
133
|
+
assert.ok(!result.valid);
|
|
134
|
+
assert.ok(result.errors.some((e) => e.includes('storageBackend')));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('validateRegistry rejects null resource', () => {
|
|
138
|
+
const controller = createArtifactRegistryController();
|
|
139
|
+
const result = controller.validateRegistry(null);
|
|
140
|
+
assert.ok(!result.valid);
|
|
141
|
+
assert.ok(result.errors.some((e) => e.includes('null')));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Feed validation
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
test('validateFeed accepts valid feed with registryRef', () => {
|
|
149
|
+
const controller = createArtifactRegistryController();
|
|
150
|
+
const result = controller.validateFeed(makeFeed());
|
|
151
|
+
assert.ok(result.valid);
|
|
152
|
+
assert.equal(result.errors.length, 0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('validateFeed rejects missing feedName', () => {
|
|
156
|
+
const controller = createArtifactRegistryController();
|
|
157
|
+
const result = controller.validateFeed(makeFeed({ feedName: '' }));
|
|
158
|
+
assert.ok(!result.valid);
|
|
159
|
+
assert.ok(result.errors.some((e) => e.includes('feedName')));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('validateFeed rejects missing registryRef', () => {
|
|
163
|
+
const controller = createArtifactRegistryController();
|
|
164
|
+
const result = controller.validateFeed({ metadata: { name: 'f' }, spec: { organizationRef: 'acme', feedName: 'test' } });
|
|
165
|
+
assert.ok(!result.valid);
|
|
166
|
+
assert.ok(result.errors.some((e) => e.includes('registryRef')));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Access policy validation
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
test('validateAccessPolicy accepts valid policy with subjects and permissions', () => {
|
|
174
|
+
const controller = createArtifactRegistryController();
|
|
175
|
+
const result = controller.validateAccessPolicy(makeAccessPolicy());
|
|
176
|
+
assert.ok(result.valid);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('validateAccessPolicy rejects empty subjects', () => {
|
|
180
|
+
const controller = createArtifactRegistryController();
|
|
181
|
+
const result = controller.validateAccessPolicy(makeAccessPolicy({ subjects: [] }));
|
|
182
|
+
assert.ok(!result.valid);
|
|
183
|
+
assert.ok(result.errors.some((e) => e.includes('subjects')));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('validateAccessPolicy rejects unknown permissions', () => {
|
|
187
|
+
const controller = createArtifactRegistryController();
|
|
188
|
+
const result = controller.validateAccessPolicy(makeAccessPolicy({ permissions: ['read', 'delete'] }));
|
|
189
|
+
assert.ok(!result.valid);
|
|
190
|
+
assert.ok(result.errors.some((e) => e.includes('delete')));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Registry endpoint
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
test('getRegistryEndpoint returns internal URL for internal backend', () => {
|
|
198
|
+
const controller = createArtifactRegistryController();
|
|
199
|
+
const registry = makeRegistry();
|
|
200
|
+
const url = controller.getRegistryEndpoint(registry);
|
|
201
|
+
assert.ok(url.includes('/api/v1/registry/npm/acme'));
|
|
202
|
+
assert.ok(url.startsWith('https://kradle.example.com'));
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('getRegistryEndpoint returns external URL for npm.pkg.github.com', () => {
|
|
206
|
+
const controller = createArtifactRegistryController();
|
|
207
|
+
const registry = makeRegistry({
|
|
208
|
+
registryType: 'npm',
|
|
209
|
+
storageBackend: 's3',
|
|
210
|
+
externalBackendRef: 'github-packages',
|
|
211
|
+
externalUrl: 'https://npm.pkg.github.com/@acme',
|
|
212
|
+
});
|
|
213
|
+
const url = controller.getRegistryEndpoint(registry);
|
|
214
|
+
assert.equal(url, 'https://npm.pkg.github.com/@acme');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('getRegistryEndpoint returns external URL for ghcr.io', () => {
|
|
218
|
+
const controller = createArtifactRegistryController();
|
|
219
|
+
const registry = makeRegistry({
|
|
220
|
+
registryType: 'docker',
|
|
221
|
+
storageBackend: 's3',
|
|
222
|
+
externalBackendRef: 'github-packages',
|
|
223
|
+
externalUrl: 'https://ghcr.io/acme',
|
|
224
|
+
});
|
|
225
|
+
const url = controller.getRegistryEndpoint(registry);
|
|
226
|
+
assert.equal(url, 'https://ghcr.io/acme');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('getRegistryEndpoint generates default external npm URL when no externalUrl', () => {
|
|
230
|
+
const controller = createArtifactRegistryController();
|
|
231
|
+
const registry = makeRegistry({
|
|
232
|
+
registryType: 'npm',
|
|
233
|
+
storageBackend: 's3',
|
|
234
|
+
externalBackendRef: 'github-packages',
|
|
235
|
+
});
|
|
236
|
+
const url = controller.getRegistryEndpoint(registry);
|
|
237
|
+
assert.equal(url, 'https://npm.pkg.github.com/@acme');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Artifact publish / list / get / delete
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
test('publishArtifact creates ArtifactVersion with digest', async () => {
|
|
245
|
+
const controller = createArtifactRegistryController();
|
|
246
|
+
const feed = makeFeed();
|
|
247
|
+
const result = await controller.publishArtifact(feed, {
|
|
248
|
+
name: '@acme/utils',
|
|
249
|
+
version: '1.0.0',
|
|
250
|
+
size: 12345,
|
|
251
|
+
metadata: { license: 'MIT' },
|
|
252
|
+
});
|
|
253
|
+
assert.ok(result.version);
|
|
254
|
+
assert.equal(result.version.kind, 'ArtifactVersion');
|
|
255
|
+
assert.equal(result.version.spec.name, '@acme/utils');
|
|
256
|
+
assert.equal(result.version.spec.version, '1.0.0');
|
|
257
|
+
assert.ok(result.digest);
|
|
258
|
+
assert.ok(result.publishedAt);
|
|
259
|
+
assert.equal(result.version.status.phase, 'Published');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('listVersions returns paginated versions', async () => {
|
|
263
|
+
const controller = createArtifactRegistryController();
|
|
264
|
+
const feed = makeFeed();
|
|
265
|
+
await controller.publishArtifact(feed, { name: 'pkg-a', version: '1.0.0', digest: 'aaa' });
|
|
266
|
+
await controller.publishArtifact(feed, { name: 'pkg-a', version: '2.0.0', digest: 'bbb' });
|
|
267
|
+
await controller.publishArtifact(feed, { name: 'pkg-b', version: '1.0.0', digest: 'ccc' });
|
|
268
|
+
|
|
269
|
+
const all = await controller.listVersions(feed);
|
|
270
|
+
assert.equal(all.total, 3);
|
|
271
|
+
assert.equal(all.items.length, 3);
|
|
272
|
+
|
|
273
|
+
const filtered = await controller.listVersions(feed, { name: 'pkg-a' });
|
|
274
|
+
assert.equal(filtered.total, 2);
|
|
275
|
+
|
|
276
|
+
const paginated = await controller.listVersions(feed, { limit: 1, offset: 1 });
|
|
277
|
+
assert.equal(paginated.items.length, 1);
|
|
278
|
+
assert.equal(paginated.total, 3);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('getVersion returns specific version', async () => {
|
|
282
|
+
const controller = createArtifactRegistryController();
|
|
283
|
+
const feed = makeFeed();
|
|
284
|
+
await controller.publishArtifact(feed, { name: 'my-lib', version: '3.2.1', digest: 'xyz' });
|
|
285
|
+
const version = await controller.getVersion(feed, 'my-lib', '3.2.1');
|
|
286
|
+
assert.ok(version);
|
|
287
|
+
assert.equal(version.spec.name, 'my-lib');
|
|
288
|
+
assert.equal(version.spec.version, '3.2.1');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('getVersion returns null for non-existent version', async () => {
|
|
292
|
+
const controller = createArtifactRegistryController();
|
|
293
|
+
const feed = makeFeed();
|
|
294
|
+
const result = await controller.getVersion(feed, 'nope', '0.0.0');
|
|
295
|
+
assert.equal(result, null);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('deleteVersion marks as deleted', async () => {
|
|
299
|
+
const controller = createArtifactRegistryController();
|
|
300
|
+
const feed = makeFeed();
|
|
301
|
+
await controller.publishArtifact(feed, { name: 'doomed', version: '1.0.0', digest: 'ddd' });
|
|
302
|
+
const result = await controller.deleteVersion(feed, 'doomed', '1.0.0');
|
|
303
|
+
assert.ok(result.deleted);
|
|
304
|
+
assert.ok(result.deletedAt);
|
|
305
|
+
|
|
306
|
+
// getVersion should not return soft-deleted versions
|
|
307
|
+
const version = await controller.getVersion(feed, 'doomed', '1.0.0');
|
|
308
|
+
assert.equal(version, null);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('deleteVersion returns deleted: false for non-existent version', async () => {
|
|
312
|
+
const controller = createArtifactRegistryController();
|
|
313
|
+
const feed = makeFeed();
|
|
314
|
+
const result = await controller.deleteVersion(feed, 'nope', '0.0.0');
|
|
315
|
+
assert.ok(!result.deleted);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// Docker-specific
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
test('Docker: listDockerTags returns tags', async () => {
|
|
323
|
+
const controller = createArtifactRegistryController();
|
|
324
|
+
const feed = makeFeed({ feedName: 'docker-feed' });
|
|
325
|
+
await controller.publishArtifact(feed, { name: 'myapp', version: 'v1.0', digest: 'd1' });
|
|
326
|
+
await controller.publishArtifact(feed, { name: 'myapp', version: 'v1.1', digest: 'd2' });
|
|
327
|
+
await controller.publishArtifact(feed, { name: 'other', version: 'latest', digest: 'd3' });
|
|
328
|
+
|
|
329
|
+
const result = await controller.listDockerTags(feed, 'myapp');
|
|
330
|
+
assert.equal(result.tags.length, 2);
|
|
331
|
+
assert.ok(result.tags.includes('v1.0'));
|
|
332
|
+
assert.ok(result.tags.includes('v1.1'));
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('Docker: getDockerManifest returns manifest by tag', async () => {
|
|
336
|
+
const controller = createArtifactRegistryController();
|
|
337
|
+
const feed = makeFeed();
|
|
338
|
+
await controller.publishArtifact(feed, { name: 'myapp', version: 'latest', digest: 'sha256:abc123', size: 50000 });
|
|
339
|
+
const manifest = await controller.getDockerManifest(feed, 'myapp', 'latest');
|
|
340
|
+
assert.ok(manifest);
|
|
341
|
+
assert.equal(manifest.repository, 'myapp');
|
|
342
|
+
assert.equal(manifest.tag, 'latest');
|
|
343
|
+
assert.equal(manifest.digest, 'sha256:abc123');
|
|
344
|
+
assert.equal(manifest.size, 50000);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// npm-specific
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
test('npm: getNpmPackageInfo returns package metadata', async () => {
|
|
352
|
+
const controller = createArtifactRegistryController();
|
|
353
|
+
const feed = makeFeed();
|
|
354
|
+
await controller.publishArtifact(feed, { name: '@acme/sdk', version: '1.0.0', digest: 'n1', size: 100 });
|
|
355
|
+
await controller.publishArtifact(feed, { name: '@acme/sdk', version: '2.0.0', digest: 'n2', size: 200 });
|
|
356
|
+
|
|
357
|
+
const info = await controller.getNpmPackageInfo(feed, '@acme/sdk');
|
|
358
|
+
assert.ok(info);
|
|
359
|
+
assert.equal(info.name, '@acme/sdk');
|
|
360
|
+
assert.equal(info.latestVersion, '2.0.0');
|
|
361
|
+
assert.ok(info.versions['1.0.0']);
|
|
362
|
+
assert.ok(info.versions['2.0.0']);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('npm: getNpmPackageInfo returns null for unknown package', async () => {
|
|
366
|
+
const controller = createArtifactRegistryController();
|
|
367
|
+
const feed = makeFeed();
|
|
368
|
+
const info = await controller.getNpmPackageInfo(feed, '@acme/unknown');
|
|
369
|
+
assert.equal(info, null);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test('npm: listNpmVersions returns versions', async () => {
|
|
373
|
+
const controller = createArtifactRegistryController();
|
|
374
|
+
const feed = makeFeed();
|
|
375
|
+
await controller.publishArtifact(feed, { name: '@acme/lib', version: '0.1.0', digest: 'v1' });
|
|
376
|
+
await controller.publishArtifact(feed, { name: '@acme/lib', version: '0.2.0', digest: 'v2' });
|
|
377
|
+
|
|
378
|
+
const result = await controller.listNpmVersions(feed, '@acme/lib');
|
|
379
|
+
assert.equal(result.versions.length, 2);
|
|
380
|
+
assert.ok(result.versions.includes('0.1.0'));
|
|
381
|
+
assert.ok(result.versions.includes('0.2.0'));
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
// pip-specific
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
test('pip: listPipPackages returns packages', async () => {
|
|
389
|
+
const controller = createArtifactRegistryController();
|
|
390
|
+
const feed = makeFeed();
|
|
391
|
+
await controller.publishArtifact(feed, { name: 'my-package', version: '1.0', digest: 'p1' });
|
|
392
|
+
await controller.publishArtifact(feed, { name: 'another-package', version: '2.0', digest: 'p2' });
|
|
393
|
+
|
|
394
|
+
const result = await controller.listPipPackages(feed);
|
|
395
|
+
assert.equal(result.packages.length, 2);
|
|
396
|
+
assert.ok(result.packages.includes('my-package'));
|
|
397
|
+
assert.ok(result.packages.includes('another-package'));
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('pip: getPipPackageInfo returns package info', async () => {
|
|
401
|
+
const controller = createArtifactRegistryController();
|
|
402
|
+
const feed = makeFeed();
|
|
403
|
+
await controller.publishArtifact(feed, { name: 'flask-ext', version: '0.1.0', digest: 'pp1' });
|
|
404
|
+
const info = await controller.getPipPackageInfo(feed, 'flask-ext');
|
|
405
|
+
assert.ok(info);
|
|
406
|
+
assert.equal(info.name, 'flask-ext');
|
|
407
|
+
assert.equal(info.versions.length, 1);
|
|
408
|
+
assert.equal(info.versions[0].version, '0.1.0');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
// Generic/ad-hoc
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
|
|
415
|
+
test('Generic: uploadArtifact creates version from file', async () => {
|
|
416
|
+
const controller = createArtifactRegistryController();
|
|
417
|
+
const feed = makeFeed();
|
|
418
|
+
const result = await controller.uploadArtifact(feed, { name: 'build.tar.gz', size: 100000 }, { version: '20240101' });
|
|
419
|
+
assert.ok(result.version);
|
|
420
|
+
assert.equal(result.version.spec.name, 'build.tar.gz');
|
|
421
|
+
assert.ok(result.digest);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test('Generic: downloadArtifact returns artifact and records audit', async () => {
|
|
425
|
+
const controller = createArtifactRegistryController();
|
|
426
|
+
const feed = makeFeed();
|
|
427
|
+
await controller.publishArtifact(feed, { name: 'release.zip', version: '1.0', digest: 'gen1', size: 50000 });
|
|
428
|
+
const result = await controller.downloadArtifact(feed, 'release.zip', '1.0', 'user:bob');
|
|
429
|
+
assert.ok(result.artifact);
|
|
430
|
+
assert.ok(result.download);
|
|
431
|
+
assert.equal(result.download.kind, 'ArtifactDownload');
|
|
432
|
+
assert.equal(result.download.spec.requestedBy, 'user:bob');
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test('Generic: downloadArtifact returns null for non-existent artifact', async () => {
|
|
436
|
+
const controller = createArtifactRegistryController();
|
|
437
|
+
const feed = makeFeed();
|
|
438
|
+
const result = await controller.downloadArtifact(feed, 'nope', '0.0', 'user:bob');
|
|
439
|
+
assert.equal(result.artifact, null);
|
|
440
|
+
assert.equal(result.download, null);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
// Access policy enforcement
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
|
|
447
|
+
test('Access policy enforces read permissions', () => {
|
|
448
|
+
const controller = createArtifactRegistryController();
|
|
449
|
+
const policy = makeAccessPolicy({ subjects: ['user:alice', 'user:bob'], permissions: ['read'] });
|
|
450
|
+
assert.ok(controller.checkAccess(policy, 'user:alice', 'read'));
|
|
451
|
+
assert.ok(!controller.checkAccess(policy, 'user:alice', 'write'));
|
|
452
|
+
assert.ok(!controller.checkAccess(policy, 'user:alice', 'admin'));
|
|
453
|
+
assert.ok(!controller.checkAccess(policy, 'user:charlie', 'read'));
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test('Access policy enforces write permissions (implies read)', () => {
|
|
457
|
+
const controller = createArtifactRegistryController();
|
|
458
|
+
const policy = makeAccessPolicy({ subjects: ['user:alice'], permissions: ['write'] });
|
|
459
|
+
assert.ok(controller.checkAccess(policy, 'user:alice', 'read'));
|
|
460
|
+
assert.ok(controller.checkAccess(policy, 'user:alice', 'write'));
|
|
461
|
+
assert.ok(!controller.checkAccess(policy, 'user:alice', 'admin'));
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test('Access policy enforces admin permissions (implies read + write)', () => {
|
|
465
|
+
const controller = createArtifactRegistryController();
|
|
466
|
+
const policy = makeAccessPolicy({ subjects: ['team:platform'], permissions: ['admin'] });
|
|
467
|
+
assert.ok(controller.checkAccess(policy, 'team:platform', 'read'));
|
|
468
|
+
assert.ok(controller.checkAccess(policy, 'team:platform', 'write'));
|
|
469
|
+
assert.ok(controller.checkAccess(policy, 'team:platform', 'admin'));
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
// External registry integration
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
|
|
476
|
+
test('resolveExternalRegistryCapability returns capability for enabled binding', () => {
|
|
477
|
+
const controller = createArtifactRegistryController();
|
|
478
|
+
const binding = {
|
|
479
|
+
spec: { interfaces: { artifactRegistry: { enabled: true, mode: 'read-write' } } },
|
|
480
|
+
};
|
|
481
|
+
const result = controller.resolveExternalRegistryCapability(binding);
|
|
482
|
+
assert.ok(result);
|
|
483
|
+
assert.ok(result.enabled);
|
|
484
|
+
assert.equal(result.mode, 'read-write');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test('resolveExternalRegistryCapability returns null for disabled binding', () => {
|
|
488
|
+
const controller = createArtifactRegistryController();
|
|
489
|
+
const binding = {
|
|
490
|
+
spec: { interfaces: { artifactRegistry: { enabled: false } } },
|
|
491
|
+
};
|
|
492
|
+
const result = controller.resolveExternalRegistryCapability(binding);
|
|
493
|
+
assert.equal(result, null);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test('resolveExternalRegistryCapability returns null for missing interfaces', () => {
|
|
497
|
+
const controller = createArtifactRegistryController();
|
|
498
|
+
const binding = { spec: {} };
|
|
499
|
+
const result = controller.resolveExternalRegistryCapability(binding);
|
|
500
|
+
assert.equal(result, null);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test('resolveExternalRegistryCapability defaults mode to read-only', () => {
|
|
504
|
+
const controller = createArtifactRegistryController();
|
|
505
|
+
const binding = {
|
|
506
|
+
spec: { interfaces: { artifactRegistry: { enabled: true } } },
|
|
507
|
+
};
|
|
508
|
+
const result = controller.resolveExternalRegistryCapability(binding);
|
|
509
|
+
assert.ok(result);
|
|
510
|
+
assert.equal(result.mode, 'read-only');
|
|
511
|
+
});
|