Skip to content

feat: Add Hugging Face Model Import via Replication with Preview and File Filters - #1398

Draft
Vad1mo wants to merge 15 commits into
mainfrom
feat/hf-model-import-option-b
Draft

Vad1mo wants to merge 15 commits into
mainfrom
feat/hf-model-import-option-b

Conversation

@Vad1mo

@Vad1mo Vad1mo commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Draft, for preview images and testing. Harbor-next build of Option B from the model import comparison (goharbor/community#297, upstream draft goharbor/harbor#24319).

What it contains

  1. Port of feat(replication): Hugging Face registry adapter for replication and proxy cache (WIP) goharbor/harbor#24008: huggingface registry adapter for pull replication and proxy cache (models become CNAI model-spec artifacts, deterministic digests)
  2. Generic replication extensions: file filter with include/exclude, ArtifactResolver copy preview, optional sha-<12> revision tag, source_only in registry info
  3. Option B UI: POST /registries/{id}/preview; replication rule form driven by source capabilities (Model / Revision / Files fields, preview panel, revision tag checkbox, source-only registries hidden as push destinations); token-only Hugging Face endpoint; Model panel on CNAI artifacts

Verified

HuggingFaceTB/SmolLM2-135M @ main, include *.json, revision tag on: sha256:2dc8fb68cff37ff1…, tags main and sha-93efa2f097d5; rerun skipped. Unfiltered copy sha256:2b6f786b… (same digest as #24008). Go tests on touched packages, golangci-lint clean, touched karma specs pass.

Known limitations

  • Quay's access key becomes read-only in the endpoint form (Quay declares the fixed access-key credential pattern); needs a decision before merge
  • replication task rows don't show the revision tag yet (only in the task log)
  • no file filter or preview for proxy cache projects
  • non-English lang files carry the English strings
  • includes two docs commits (docs/model-import-foundation.md, docs/model-sync-option-b.md) that are prototype notes

Screenshots

B1: Hugging Face endpoint, token only

B1: Hugging Face endpoint, token only

B2: Rule form with Hugging Face source: Model, Revision, Files

B2: Rule form with Hugging Face source: Model, Revision, Files

B3: Preview inside the rule form

B3: Preview inside the rule form

B4: Rule form with Docker Hub source, unchanged

B4: Rule form with Docker Hub source, unchanged

B5: Executions succeeded

B5: Executions succeeded

B6: Model artifact with the Model panel

B6: Model artifact with the Model panel

Release Notes

Import AI models from Hugging Face into Harbor with pull replication or proxy cache. Replication rules for model sources can preview the files before import, filter files with include/exclude patterns, and add an immutable sha-<commit> tag next to the branch tag. Model artifacts show license, source and source revision.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

A preview chart for this PR is available, packaged from 99f1cd6:

helm install harbor-next \
  oci://8gears.container-registry.com/8gcr-pr/charts/harbor \
  --version 2.1.1-pr.1398

Digest: sha256:105c3eebdc8412baa35b535d24f72dd9caa61e77fa43868ecc5165346fecbfb6. The chart installs Harbor v2.15.14 as committed. This PR's pr-1398 images, when published, live in 8gcr-pr and are not selected by this command: point the chart at them with per-component image.registry and image.repository values.

Preview images for this PR are published too: #1398 (comment)

Verify the preview chart:

cosign verify \
  --certificate-identity-regexp="https://github.com/container-registry/harbor-next/.github/workflows/pr-chart.yml@.*" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  8gears.container-registry.com/8gcr-pr/charts/harbor@sha256:105c3eebdc8412baa35b535d24f72dd9caa61e77fa43868ecc5165346fecbfb6

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

🔒 Vulnerability Check Results

⚠️ Fixable vulnerabilities detected: 4 of 7 found

Vulnerability ID Package Found In Fixed In Description Example Trace Details
GO-2026-4942 github.com/distribution/distribution v2.8.2+incompatible 3.1.0 Stale blob descriptor cache invalidation in github.com/distribution/distribution github.com/distribution/distribution View
GO-2026-5094 github.com/distribution/distribution v2.8.2+incompatible 3.1.0 Distribution affected by pull-through cache credential exfiltration via www-authenticate bearer realm in github.com/distribution/distribution github.com/distribution/distribution View
GO-2026-5185 github.com/distribution/distribution v2.8.2+incompatible 3.1.1 Distribution's tag deletion bypasses storage.delete.enabled configuration in github.com/distribution/distribution github.com/distribution/distribution View
GO-2026-5932 golang.org/x/crypto v0.58.0 0.58.1-0.20261009181718-2f1f834330b2 The golang.org/x/crypto/openpgp package is unmaintained, unsafe by design, and has known security issues golang.org/x/crypto View

Showing 4 of 7 total findings.

govulncheck exit code: 0

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

23 issues found across 103 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts">

<violation number="1" location="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts:141">
P2: Custom agent: **Enforce Strict Maintainability Standards**

This change takes the shared rule component from 929 to 1,227 lines, with the preview/file-filter state and workflow added inline. Extract that cohesive UI and state into a focused component or module to keep the shared form below the rule's 1,000-line threshold.</violation>

<violation number="2" location="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts:630">
P1: Editing a rule with a file filter drops that filter on save. The new serialization updates `copyRuleForm`, but the update branch still submits raw `this.ruleForm.value`, whose FILE control is intentionally empty; submit the transformed payload for updates as well.</violation>

<violation number="3" location="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts:1208">
P2: An in-flight preview can repopulate the form after the source or rule changes, showing results for the wrong registry. Cancel preview requests or ignore responses whose source/form generation no longer matches the request.</violation>
</file>

<file name="src/controller/replication/flow/copy_source_test.go">

<violation number="1" location="src/controller/replication/flow/copy_source_test.go:67">
P2: This test cannot detect configuring the wrong adapter or configuring after `FetchArtifacts`, because both registry sides share `adp` and the fetch expectation does not inspect `got`. Use distinct source and destination adapters, and have the source adapter’s `FetchArtifacts` callback assert that `ConfigureSource` already ran.</violation>
</file>

<file name="src/portal/src/app/base/project/repository/artifact/artifact-additions/files/files.component.ts">

<violation number="1" location="src/portal/src/app/base/project/repository/artifact/artifact-additions/files/files.component.ts:61">
P2: This checks optional `children` instead of the required `type`, so empty directories are rendered with a file-type icon because the API omits empty `children`. Use `file.type === 'directory'` to classify folders.</violation>
</file>

<file name="src/pkg/reg/adapter/huggingface/replication_test.go">

<violation number="1" location="src/pkg/reg/adapter/huggingface/replication_test.go:207">
P2: This restart check can pass without proving deterministic reconstruction: the transfer skips an existing tag when its freshly resolved digest differs and `Override` is false, leaving the old destination digest unchanged. Reset the destination before the post-cache-clear run, or compare the freshly pulled source digest directly before asserting it matches the first run.</violation>
</file>

<file name="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.html">

<violation number="1" location="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.html:174">
P2: This removes the existing GCR fallback when `/replication/adapterinfos` is unavailable: `isFixedAccessKey()` becomes false and the `_json_key` field is editable. Preserve the explicit GCR check alongside the metadata-driven check.</violation>
</file>

<file name="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.spec.ts">

<violation number="1" location="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.spec.ts:664">
P2: Use the production `huggingface` adapter type in this fixture and when assigning `comp.target.type`; `hf` is not the registered adapter key, so regressions in the real Hugging Face metadata entry could pass this test.</violation>
</file>

<file name="docs/model-sync-option-b.md">

<violation number="1" location="docs/model-sync-option-b.md:72">
P3: These test commands are not copy-pastable from the repository root: the Go and Angular workspaces live under `src`, and `<touched specs>` is shell redirection syntax. Prefix the commands with their required working directories and use concrete spec paths, or run the full portal suite.</violation>

<violation number="2" location="docs/model-sync-option-b.md:160">
P2: The preview endpoint performs source downloads synchronously in the core request. Large repositories or repeated previews can exhaust core request workers and outbound connections; move resolution to a bounded worker or enforce strict timeout, size, and rate limits before relying on this endpoint in production.</violation>
</file>

<file name="src/pkg/reg/adapter/huggingface/pull_test.go">

<violation number="1" location="src/pkg/reg/adapter/huggingface/pull_test.go:379">
P3: This test never exercises vanished-head handling because the target digest belongs to `main`, which is scanned before the vanished `v1` ref. Make the first scanned head invalid and place the target digest on a later ref so the test fails if not-found heads are not skipped.</violation>
</file>

<file name="src/pkg/reg/adapter/huggingface/hub/errors.go">

<violation number="1" location="src/pkg/reg/adapter/huggingface/hub/errors.go:57">
P2: Map HTTP 400 to `errors.BadRequestCode`; otherwise a Hub bad-request response is surfaced as `UNKNOWN` and can become a Harbor 500/internal-error response instead of a client error.</violation>
</file>

<file name="docs/model-import-foundation.md">

<violation number="1" location="docs/model-import-foundation.md:7">
P2: This says no public Resolve API is designed, but this revision publishes `POST /registries/{id}/preview` and `supported_preview` in the Swagger contract. Qualify the statement or document the generic preview endpoint here so UI consumers do not miss it.</violation>

<violation number="2" location="docs/model-import-foundation.md:103">
P2: The Resolve example ignores `ok` and calls the resolver for unsupported sources, which panics instead of returning the documented unsupported-preview error. Check `ok` before calling `Resolve`.</violation>
</file>

<file name="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts">

<violation number="1" location="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts:291">
P2: Selecting Hugging Face before `/replication/adapterinfos` finishes leaves the fixed `token` key blank: `adapterChange()` clears it while this helper is false, then the helper becomes true and the field becomes read-only. Initialize the selected adapter after metadata loads or defer adapter selection until both requests complete.</violation>
</file>

<file name="src/pkg/reg/adapter/huggingface/naming.go">

<violation number="1" location="src/pkg/reg/adapter/huggingface/naming.go:48">
P2: A Hugging Face ref named `sha-<12 hex>` can shadow the generated revision tag with the same name. Pulls then follow the mutable source ref instead of the intended commit, and moving that ref can make an allegedly immutable tag resolve to a different artifact; reserve this namespace or detect and handle the collision consistently when indexing and generating tags.</violation>
</file>

<file name="src/controller/replication/policy.go">

<violation number="1" location="src/controller/replication/policy.go:221">
P2: `registryInfo` is not side-effect-free: it creates the adapter, and some factories perform network discovery during `Create`. Creating or updating a policy can therefore block on registry timeouts for a source-only check that ultimately accepts errors; use static provider metadata or cached capabilities for destination validation.</violation>
</file>

<file name="src/pkg/reg/adapter/huggingface/resolve.go">

<violation number="1" location="src/pkg/reg/adapter/huggingface/resolve.go:58">
P2: Commit-pinned previews still require the Hub `/refs` endpoint, despite the documented 40-hex path skipping that call. Guard the refs lookup with `!hub.IsCommit(reference)` so pinned revisions do not spend a rate-limited request or fail solely because refs is unavailable.</violation>
</file>

<file name="src/controller/replication/model/model.go">

<violation number="1" location="src/controller/replication/model/model.go:217">
P2: This full-row mapping clears `revision_tag` whenever an older client omits the new optional field in an update. Preserve the stored value when the field is absent, or update this column only when the request explicitly supplies it.</violation>
</file>

<file name="src/pkg/reg/adapter/huggingface/hub/hubtest/hubtest.go">

<violation number="1" location="src/pkg/reg/adapter/huggingface/hub/hubtest/hubtest.go:366">
P3: The fake reports tag refs as branch refs, so any test that inspects the raw `ref` field can pass against incorrect Hub metadata. Make `refs` distinguish branch and tag collections and emit `refs/tags/` for tags.</violation>
</file>

<file name="src/portal/src/app/shared/units/file-filter.ts">

<violation number="1" location="src/portal/src/app/shared/units/file-filter.ts:61">
P2: This local matcher accepts malformed patterns that the replication API rejects. Validate file/tag patterns with the same doublestar rules before rematching, or mark the preview invalid instead of displaying results for an unsaveable rule.</violation>
</file>

<file name="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.html">

<violation number="1" location="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.html:232">
P2: Switching the source leaves the standalone file include/exclude values intact. Returning to another file-capable source can submit stale patterns from the previous source; reset the file-filter state whenever the source capabilities are replaced.</violation>
</file>

<file name="src/portal/src/app/base/project/repository/artifact/artifact-model-metadata/artifact-model-metadata.component.ts">

<violation number="1" location="src/portal/src/app/base/project/repository/artifact/artifact-model-metadata/artifact-model-metadata.component.ts:59">
P2: The Parameters row reads `config.paramSize`, but CNAI model metadata uses `config.parameterSize`; imported models therefore never display their parameter count. Read `c.parameterSize` instead.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}
});
}
copyRuleForm.filters.forEach(item => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Editing a rule with a file filter drops that filter on save. The new serialization updates copyRuleForm, but the update branch still submits raw this.ruleForm.value, whose FILE control is intentionally empty; submit the transformed payload for updates as well.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts, line 630:

<comment>Editing a rule with a file filter drops that filter on save. The new serialization updates `copyRuleForm`, but the update branch still submits raw `this.ruleForm.value`, whose FILE control is intentionally empty; submit the transformed payload for updates as well.</comment>

<file context>
@@ -553,6 +627,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
                 }
             });
         }
+        copyRuleForm.filters.forEach(item => {
+            if (item.type === FilterType.FILE) {
+                const f = this.fileFilter;
</file context>

copyStringForLabelFilter: string = '';
// capabilities of the selected source; every model-specific control below
// is driven by them, never by the registry type
sourceInfo: RegistryInfo;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: Enforce Strict Maintainability Standards

This change takes the shared rule component from 929 to 1,227 lines, with the preview/file-filter state and workflow added inline. Extract that cohesive UI and state into a focused component or module to keep the shared form below the rule's 1,000-line threshold.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts, line 141:

<comment>This change takes the shared rule component from 929 to 1,227 lines, with the preview/file-filter state and workflow added inline. Extract that cohesive UI and state into a focused component or module to keep the shared form below the rule's 1,000-line threshold.</comment>

<file context>
@@ -121,6 +136,24 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
     copyStringForLabelFilter: string = '';
+    // capabilities of the selected source; every model-specific control below
+    // is driven by them, never by the registry type
+    sourceInfo: RegistryInfo;
+    sourceOnlyTypes = new Set<string>();
+    fileInclude: string = '';
</file context>

adp := &configurableAdapter{mockAdapter: &mockAdapter{}}
factory := &mockFactory{}
factory.On("AdapterPattern").Return(nil)
factory.On("Create", mock.Anything).Return(adp, nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test cannot detect configuring the wrong adapter or configuring after FetchArtifacts, because both registry sides share adp and the fetch expectation does not inspect got. Use distinct source and destination adapters, and have the source adapter’s FetchArtifacts callback assert that ConfigureSource already ran.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/controller/replication/flow/copy_source_test.go, line 67:

<comment>This test cannot detect configuring the wrong adapter or configuring after `FetchArtifacts`, because both registry sides share `adp` and the fetch expectation does not inspect `got`. Use distinct source and destination adapters, and have the source adapter’s `FetchArtifacts` callback assert that `ConfigureSource` already ran.</comment>

<file context>
@@ -0,0 +1,95 @@
+	adp := &configurableAdapter{mockAdapter: &mockAdapter{}}
+	factory := &mockFactory{}
+	factory.On("AdapterPattern").Return(nil)
+	factory.On("Create", mock.Anything).Return(adp, nil)
+	require.NoError(t, adapter.RegisterFactory("TEST_COPY_FLOW_SOURCE_OPTIONS", factory))
+	adp.On("Info").Return(&model.RegistryInfo{SupportedResourceTypes: []string{model.ResourceTypeArtifact}}, nil)
</file context>

}

iconFor(file: FilesItem & { expanded?: boolean }): string {
if (file.children) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This checks optional children instead of the required type, so empty directories are rendered with a file-type icon because the API omits empty children. Use file.type === 'directory' to classify folders.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/project/repository/artifact/artifact-additions/files/files.component.ts, line 61:

<comment>This checks optional `children` instead of the required `type`, so empty directories are rendered with a file-type icon because the API omits empty `children`. Use `file.type === 'directory'` to classify folders.</comment>

<file context>
@@ -56,6 +57,13 @@ export class ArtifactFilesComponent implements OnInit {
     }
 
+    iconFor(file: FilesItem & { expanded?: boolean }): string {
+        if (file.children) {
+            return file.expanded ? 'folder-open' : 'folder';
+        }
</file context>
Suggested change
if (file.children) {
if (file.type === 'directory') {

.pipe(finalize(() => (this.previewing = false)))
.subscribe({
next: res => {
this.preview = res;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: An in-flight preview can repopulate the form after the source or rule changes, showing results for the wrong registry. Cancel preview requests or ignore responses whose source/form generation no longer matches the request.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts, line 1208:

<comment>An in-flight preview can repopulate the form after the source or rule changes, showing results for the wrong registry. Cancel preview requests or ignore responses whose source/form generation no longer matches the request.</comment>

<file context>
@@ -926,4 +1016,212 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
+            .pipe(finalize(() => (this.previewing = false)))
+            .subscribe({
+                next: res => {
+                    this.preview = res;
+                    this.previewFor = request;
+                    this.rematchPreview();
</file context>

Comment thread docs/model-sync-option-b.md Outdated
Comment on lines +72 to +74
go test ./pkg/reg/... ./controller/replication/... ./controller/registry/ ./server/v2.0/handler/ # all ok
golangci-lint run ./controller/registry/... ./server/v2.0/handler/ ./pkg/reg/adapter/huggingface/... ./pkg/reg/model/... # 0 issues
npx ng test --watch=false --include=<touched specs> # Executed 47 of 47 SUCCESS

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: These test commands are not copy-pastable from the repository root: the Go and Angular workspaces live under src, and <touched specs> is shell redirection syntax. Prefix the commands with their required working directories and use concrete spec paths, or run the full portal suite.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/model-sync-option-b.md, line 72:

<comment>These test commands are not copy-pastable from the repository root: the Go and Angular workspaces live under `src`, and `<touched specs>` is shell redirection syntax. Prefix the commands with their required working directories and use concrete spec paths, or run the full portal suite.</comment>

<file context>
@@ -0,0 +1,161 @@
+## Tests
+
+```
+go test ./pkg/reg/... ./controller/replication/... ./controller/registry/ ./server/v2.0/handler/   # all ok
+golangci-lint run ./controller/registry/... ./server/v2.0/handler/ ./pkg/reg/adapter/huggingface/... ./pkg/reg/model/...  # 0 issues
+npx ng test --watch=false --include=<touched specs>   # Executed 47 of 47 SUCCESS
</file context>
Suggested change
go test ./pkg/reg/... ./controller/replication/... ./controller/registry/ ./server/v2.0/handler/ # all ok
golangci-lint run ./controller/registry/... ./server/v2.0/handler/ ./pkg/reg/adapter/huggingface/... ./pkg/reg/model/... # 0 issues
npx ng test --watch=false --include=<touched specs> # Executed 47 of 47 SUCCESS
(cd src && go test ./pkg/reg/... ./controller/replication/... ./controller/registry/ ./server/v2.0/handler/)
(cd src && golangci-lint run ./controller/registry/... ./server/v2.0/handler/ ./pkg/reg/adapter/huggingface/... ./pkg/reg/model/...)
(cd src/portal && npx ng test --watch=false)

_, want := manifestOf(t, newTestAdapter(t, hubRegistry(h, 1, ""), newMemoryCache()), repoTiny, "main")

// v1 points at a commit the Hub no longer has; the main head is still found.
h.SetTag(model.ID, "v1", "dddddddddddddddddddddddddddddddddddddddd")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This test never exercises vanished-head handling because the target digest belongs to main, which is scanned before the vanished v1 ref. Make the first scanned head invalid and place the target digest on a later ref so the test fails if not-found heads are not skipped.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/pkg/reg/adapter/huggingface/pull_test.go, line 379:

<comment>This test never exercises vanished-head handling because the target digest belongs to `main`, which is scanned before the vanished `v1` ref. Make the first scanned head invalid and place the target digest on a later ref so the test fails if not-found heads are not skipped.</comment>

<file context>
@@ -0,0 +1,392 @@
+	_, want := manifestOf(t, newTestAdapter(t, hubRegistry(h, 1, ""), newMemoryCache()), repoTiny, "main")
+
+	// v1 points at a commit the Hub no longer has; the main head is still found.
+	h.SetTag(model.ID, "v1", "dddddddddddddddddddddddddddddddddddddddd")
+	a := newTestAdapter(t, hubRegistry(h, 1, ""), newMemoryCache())
+	exist, _, err := a.ManifestExist(repoTiny, want.String())
</file context>

}
sort.Strings(keys)
for _, k := range keys {
out = append(out, map[string]string{"name": k, "ref": "refs/heads/" + k, "targetCommit": names[k]})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The fake reports tag refs as branch refs, so any test that inspects the raw ref field can pass against incorrect Hub metadata. Make refs distinguish branch and tag collections and emit refs/tags/ for tags.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/pkg/reg/adapter/huggingface/hub/hubtest/hubtest.go, line 366:

<comment>The fake reports tag refs as branch refs, so any test that inspects the raw `ref` field can pass against incorrect Hub metadata. Make `refs` distinguish branch and tag collections and emit `refs/tags/` for tags.</comment>

<file context>
@@ -0,0 +1,396 @@
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		out = append(out, map[string]string{"name": k, "ref": "refs/heads/" + k, "targetCommit": names[k]})
+	}
+	return out
</file context>

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Outdated: the preview image build for 99f1cd6 did not succeed (failure). That build may already have repointed the preview reference, so trust the digest below rather than the tag. What follows is from an earlier revision of this PR.

Preview images for this PR are available in 8gears.container-registry.com/8gcr-pr with tag pr-1398, built from 71feb9d:

  • 8gears.container-registry.com/8gcr-pr/harbor-core:pr-1398
  • 8gears.container-registry.com/8gcr-pr/harbor-jobservice:pr-1398
  • 8gears.container-registry.com/8gcr-pr/harbor-registryctl:pr-1398
  • 8gears.container-registry.com/8gcr-pr/harbor-exporter:pr-1398
  • 8gears.container-registry.com/8gcr-pr/harbor-portal:pr-1398
  • 8gears.container-registry.com/8gcr-pr/harbor-registry:pr-1398
  • 8gears.container-registry.com/8gcr-pr/trivy-adapter:pr-1398

A preview chart for this PR is published too, and its comment carries the install command: #1398 (comment)

Verify a preview image:

cosign verify \
  --certificate-identity-regexp="https://github.com/container-registry/harbor-next/.github/workflows/pr-ci.yml@.*" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  8gears.container-registry.com/8gcr-pr/harbor-core:pr-1398

Verify the SBOM attestation:

cosign verify-attestation \
  --certificate-identity-regexp="https://github.com/container-registry/harbor-next/.github/workflows/pr-ci.yml@.*" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  --type spdxjson \
  8gears.container-registry.com/8gcr-pr/harbor-core:pr-1398

Signed-off-by: Vad1mo <vadim@8gears.com>
@Vad1mo
Vad1mo force-pushed the feat/hf-model-import-option-b branch from 71feb9d to 9d7dd69 Compare November 5, 2026 15:33
Vad1mo and others added 13 commits November 5, 2026 16:34
Add a `huggingface` registry type that lets pull replication and proxy
cache copy Hugging Face Hub models into Harbor as ModelPack model-spec
artifacts, without a dedicated sync framework.

The adapter synthesizes a byte-deterministic OCI manifest from the Hub
API for a pinned commit: one raw layer per file, LFS sha256 reused as the
layer digest, non-LFS files hashed and checked against their git blob id,
file classification frozen from modctl. The same commit always yields the
same digest, so replication's digest-equality skip and proxy cache's
local/remote comparison work unchanged.

Digest-to-file lookups go through lib/cache scoped per registry (Redis in
core, in-process memory in jobservice) with a stateless rebuild fallback,
because proxy cache creates a new adapter per request.

The type is source-only: replication policies with a Hugging Face
destination are rejected.

Refs: goharbor/community#297
Signed-off-by: Vadim Bauer <vb@container-registry.com>
Signed-off-by: Vad1mo <vadim@8gears.com>
…s, propagate lookup errors

Signed-off-by: Vadim Bauer <vadim@8gears.com>
Signed-off-by: Vad1mo <vadim@8gears.com>
Add a generic "file" replication filter that limits which files a source
adapter packs into an artifact. The policy's filter becomes
model.SourceOptions, which the copy flow hands to the source adapter through
the optional adapter.SourceConfigurer interface and serializes with the source
resource, so the transfer configures its own adapter the same way. Adapters
that do not implement the interface fail instead of ignoring the options.

The Hugging Face adapter selects files with the filter before hashing and
synthesis. The digest depends on the commit and the selected files only, so
the same filter always yields the same digest and a filter that drops files
yields a different one.

Signed-off-by: Vad1mo <vadim@8gears.com>
Add the optional adapter.ArtifactResolver interface. Resolve reports what a
copy of one source reference would produce without copying: the revision,
the moving tags pointing at it, license, task, the file list with class,
size, sha256 and whether the file filter selects it, matched totals, and the
manifest digest of the copy.

The Hugging Face adapter implements it with the same snapshot, hashing and
synthesis code as the copy, so the previewed digest is the copied digest. The
Hub's pipeline_tag is read into the snapshot as the task. A live test behind
the hubintegration build tag resolves against the public Hub.

Signed-off-by: Vad1mo <vadim@8gears.com>
Add a revision_tag flag to replication policies. With it, every copy also
gets the immutable tag sha-<first 12 hex of the source revision> next to its
moving tags, so a scheduled pull of main keeps every imported revision
addressable. The flag travels with the other source options to the source
adapter; policy validation accepts it only for sources whose registry info
declares supported_revision_tag.

The Hugging Face adapter appends the revision tag after the tag filter and
resolves a revision tag back to the ref head it names. The column is added
idempotently in harbor_next.sql.

Signed-off-by: Vad1mo <vadim@8gears.com>
Add source_only to the registry info. Policy validation now rejects any
registry that declares it as a replication destination instead of checking
for the Hugging Face type, and the registry info API returns the flag so a
UI can hide such registries from destination pickers. The local Harbor is
never probed, and a registry whose capabilities cannot be read is accepted as
before.

Signed-off-by: Vad1mo <vadim@8gears.com>
Document the Go interfaces, REST fields and database column that the model
import UI prototypes build on, how the file filter and revision tag travel
from the policy to the source adapter, their semantics, and the end-to-end
verification on the dev stack.

Signed-off-by: Vad1mo <vadim@8gears.com>
…y Info

POST /registries/{id}/preview resolves what a copy of a repository reference
with an optional file filter would produce, for any adapter that implements
adapter.ArtifactResolver; others answer 400. RegistryInfo.supported_preview is
derived from that interface, so adapters declare nothing new.

FilterStyle gains optional label, placeholder and description so a source can
name its filters in its own terms, and the adapter pattern gains source_only so
clients can leave such registries out of destination pickers without creating
an adapter per registry. The Hugging Face adapter uses both and fixes its access
key, so the endpoint form only asks for the token.

Signed-off-by: Vad1mo <vadim@8gears.com>
Signed-off-by: Vad1mo <vadim@8gears.com>
The form reads everything model-specific from the source's registry info, never
from the registry type: the file filter gets include/exclude inputs when the
source lists the file filter, the revision tag option appears when the source
declares supported_revision_tag, a preview panel appears when it declares
supported_preview, and filters show the adapter's own label, placeholder and
help. Registries whose type is source_only are left out of the destination
picker. Sources without these declarations render exactly as before.

Signed-off-by: Vad1mo <vadim@8gears.com>
…attern

A fixed access key (AccessKeyTypeFix) is prefilled and read-only, and the secret
is a text area only for AccessSecretTypeFile, instead of checking for the GCR
type. Hugging Face endpoints thus only ask for a token. Source-only providers
say so under the provider select.

Signed-off-by: Vad1mo <vadim@8gears.com>
A Model section lists licenses, source URL, source revision and the model-spec
config (architecture, format, parameters, precision, quantization) instead of
the raw descriptor/config/modelfs JSON in the overview. The Files tab picks
icons by file class, the artifact list marks models with a badge, and the
quantization label no longer reads the misspelled config.puantization key.

Signed-off-by: Vad1mo <vadim@8gears.com>
Signed-off-by: Vad1mo <vadim@8gears.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 33 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/portal/src/app/shared/units/file-filter.ts">

<violation number="1" location="src/portal/src/app/shared/units/file-filter.ts:62">
P2: The new safe matcher still disagrees with the backend for valid escaped doublestar patterns such as `a\*`: the local preview does not mark files that the replication backend will import. Make the glob conversion escape-aware, or use matching semantics shared with the backend.</violation>

<violation number="2" location="src/portal/src/app/shared/units/file-filter.ts:68">
P2: The client validator omits the backend limit of 64 combined include/exclude patterns. With 65 valid patterns, preview and save remain enabled but the API rejects the request; enforce the aggregate limit client-side.</violation>
</file>

<file name="src/portal/src/i18n/lang/de-de-lang.json">

<violation number="1" location="src/portal/src/i18n/lang/de-de-lang.json:703">
P3: The German locale leaves this new validation message in English. Translate it so German users see a localized error, for example `Ungültiges Dateimuster: {{patterns}}`.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

const base = path.split('/').pop();
return patterns.some(p => {
const re = safeGlobToRegExp(p);
return !!re && re.test(p.includes('/') ? path : base);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new safe matcher still disagrees with the backend for valid escaped doublestar patterns such as a\*: the local preview does not mark files that the replication backend will import. Make the glob conversion escape-aware, or use matching semantics shared with the backend.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/shared/units/file-filter.ts, line 62:

<comment>The new safe matcher still disagrees with the backend for valid escaped doublestar patterns such as `a\*`: the local preview does not mark files that the replication backend will import. Make the glob conversion escape-aware, or use matching semantics shared with the backend.</comment>

<file context>
@@ -57,11 +57,67 @@ export function matchFileFilter(f: FileFilterValue, path: string): boolean {
-        globToRegExp(p).test(p.includes('/') ? path : base)
+    return patterns.some(p => {
+        const re = safeGlobToRegExp(p);
+        return !!re && re.test(p.includes('/') ? path : base);
+    });
+}
</file context>

Comment on lines +68 to +72
export function invalidFilePatterns(f: FileFilterValue): string[] {
return [...(f?.include || []), ...(f?.exclude || [])].filter(
p => p.startsWith('/') || !isValidGlob(p) || !safeGlobToRegExp(p)
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The client validator omits the backend limit of 64 combined include/exclude patterns. With 65 valid patterns, preview and save remain enabled but the API rejects the request; enforce the aggregate limit client-side.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/shared/units/file-filter.ts, line 68:

<comment>The client validator omits the backend limit of 64 combined include/exclude patterns. With 65 valid patterns, preview and save remain enabled but the API rejects the request; enforce the aggregate limit client-side.</comment>

<file context>
@@ -57,11 +57,67 @@ export function matchFileFilter(f: FileFilterValue, path: string): boolean {
+
+// Mirrors pkg/reg/model.FileFilter.Validate: the patterns the replication API
+// rejects, so a preview is never re-matched with a filter that cannot be saved.
+export function invalidFilePatterns(f: FileFilterValue): string[] {
+    return [...(f?.include || []), ...(f?.exclude || [])].filter(
+        p => p.startsWith('/') || !isValidGlob(p) || !safeGlobToRegExp(p)
</file context>
Suggested change
export function invalidFilePatterns(f: FileFilterValue): string[] {
return [...(f?.include || []), ...(f?.exclude || [])].filter(
p => p.startsWith('/') || !isValidGlob(p) || !safeGlobToRegExp(p)
);
}
export function invalidFilePatterns(f: FileFilterValue): string[] {
const patterns = [...(f?.include || []), ...(f?.exclude || [])];
return patterns.length > 64
? patterns
: patterns.filter(
p => p.startsWith('/') || !isValidGlob(p) || !safeGlobToRegExp(p)
);
}

"PREVIEW_FILTER_CHANGED": "File filter changed, preview again to compute the digest",
"PREVIEW_ALL_FILES": "Show all files",
"PREVIEW_MATCHED_ONLY": "Show matched files only",
"FILE_FILTER_INVALID": "Invalid file pattern: {{patterns}}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The German locale leaves this new validation message in English. Translate it so German users see a localized error, for example Ungültiges Dateimuster: {{patterns}}.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/i18n/lang/de-de-lang.json, line 703:

<comment>The German locale leaves this new validation message in English. Translate it so German users see a localized error, for example `Ungültiges Dateimuster: {{patterns}}`.</comment>

<file context>
@@ -700,6 +700,7 @@
         "PREVIEW_FILTER_CHANGED": "File filter changed, preview again to compute the digest",
         "PREVIEW_ALL_FILES": "Show all files",
         "PREVIEW_MATCHED_ONLY": "Show matched files only",
+        "FILE_FILTER_INVALID": "Invalid file pattern: {{patterns}}",
         "REVISION_TAG": "Also tag each copy with its source revision (sha-…)",
         "REVISION_TAG_TIP": "Adds the immutable tag sha-<first 12 hex characters of the source revision> next to the moving tags, so every copied revision stays addressable after the branch moves on.",
</file context>
Suggested change
"FILE_FILTER_INVALID": "Invalid file pattern: {{patterns}}",
"FILE_FILTER_INVALID": "Ungültiges Dateimuster: {{patterns}}",

…ture

Register the huggingface registry type as the optional feature
huggingface_model_import (config key enable_huggingface_model_import,
env HARBOR_ENABLE_HUGGINGFACE_MODEL_IMPORT), off by default.

While it is off, core hides the type from the adapter lists and rejects
creating, updating, pinging, previewing and reading info of such a
registry, proxy cache projects and replication policies that use one,
and starting their replications. Proxy cache projects of the type serve
only cached content. Disabling is refused while registries of the type
exist.

Signed-off-by: Vad1mo <vadim@8gears.com>
@Vad1mo
Vad1mo force-pushed the feat/hf-model-import-option-b branch from 9d7dd69 to 99f1cd6 Compare November 5, 2026 15:42

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 issues found across 37 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/portal/src/app/base/left-side-nav/config/features/features.component.html">

<violation number="1" location="src/portal/src/app/base/left-side-nav/config/features/features.component.html:3">
P2: The info tooltip trigger sits inside the checkbox label. Clicking the info icon also activates the associated toggle, changing the feature when the user only intended to read its description; move the tooltip outside the label.</violation>
</file>

<file name="src/portal/src/i18n/lang/de-de-lang.json">

<violation number="1" location="src/portal/src/i18n/lang/de-de-lang.json:864">
P2: These new entries leave the entire feature-settings block in English when users select German. Translate the labels, warning, locked-by text, and Hugging Face description so the new settings follow the existing German locale behavior.

(Based on your team's feedback about German locale translations.) .</violation>
</file>

<file name="src/portal/src/app/base/left-side-nav/config/features/features.component.scss">

<violation number="1" location="src/portal/src/app/base/left-side-nav/config/features/features.component.scss:31">
P2: The beta warning becomes difficult to read in dark mode: `#aa4500` has only about 2.5:1 contrast against the dark theme background. Use a theme-aware warning color or add a dark-theme override that meets the contrast requirement.</violation>
</file>

<file name="docs/model-sync-option-b.md">

<violation number="1" location="docs/model-sync-option-b.md:169">
P2: This bullet documents behavior that the implementation does not provide: disabling the feature does not stop existing Hugging Face proxy-cache requests from contacting the upstream registry. Add a feature check to the proxy request path before constructing or using the remote helper, or remove this guarantee from the documentation.</violation>
</file>

<file name="src/controller/config/controller.go">

<violation number="1" location="src/controller/config/controller.go:120">
P1: This rejects valid bootstrap configuration when `HARBOR_ENABLE_HUGGINGFACE_MODEL_IMPORT` and `CONFIG_OVERWRITE_JSON` both contain the feature key, causing core to fail startup. Skip environment-controlled feature keys during the overwrite bootstrap (or use a separate bootstrap validation path) so the environment value wins without rejecting the initial settings.</violation>
</file>

<file name="src/portal/src/app/base/left-side-nav/config/features/features.component.ts">

<violation number="1" location="src/portal/src/app/base/left-side-nav/config/features/features.component.ts:83">
P2: The page becomes interactive before the post-save configuration refresh completes. A second edit can race that refresh and be overwritten when `updateConfig()` replaces the current configuration; include `config.getLoadingConfigStatus()` in the page's `inProgress` state, as the other configuration pages do.</violation>
</file>

<file name="src/pkg/feature/feature.go">

<violation number="1" location="src/pkg/feature/feature.go:163">
P2: The guard’s count check is not atomic with registry creation. A concurrent create can pass `CheckTypeEnabled` after the count returns zero and commit after this update, leaving a persisted endpoint while the feature is disabled; serialize these operations or recheck under a shared transaction or lock.</violation>
</file>

<file name="src/server/middleware/repoproxy/proxy.go">

<violation number="1" location="src/server/middleware/repoproxy/proxy.go:418">
P2: The feature gate does not cover the referrer proxy path: `ProxyReferrerMiddleware` bypasses `canProxy` and can still fetch referrers from a healthy disabled-type registry. Apply the same registry-type check before creating the remote helper or in a shared proxy guard.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

func (c *controller) validateCfg(ctx context.Context, cfgs map[string]any) error {
mgr := config.GetCfgManager(ctx)

if err := feature.ValidateConfigUpdate(ctx, cfgs); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This rejects valid bootstrap configuration when HARBOR_ENABLE_HUGGINGFACE_MODEL_IMPORT and CONFIG_OVERWRITE_JSON both contain the feature key, causing core to fail startup. Skip environment-controlled feature keys during the overwrite bootstrap (or use a separate bootstrap validation path) so the environment value wins without rejecting the initial settings.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/controller/config/controller.go, line 120:

<comment>This rejects valid bootstrap configuration when `HARBOR_ENABLE_HUGGINGFACE_MODEL_IMPORT` and `CONFIG_OVERWRITE_JSON` both contain the feature key, causing core to fail startup. Skip environment-controlled feature keys during the overwrite bootstrap (or use a separate bootstrap validation path) so the environment value wins without rejecting the initial settings.</comment>

<file context>
@@ -116,6 +117,9 @@ func (c *controller) updateLogEndpoint(ctx context.Context, cfgs map[string]any)
 func (c *controller) validateCfg(ctx context.Context, cfgs map[string]any) error {
 	mgr := config.GetCfgManager(ctx)
 
+	if err := feature.ValidateConfigUpdate(ctx, cfgs); err != nil {
+		return errors.BadRequestError(err)
+	}
</file context>

@@ -0,0 +1,86 @@
<ng-template #toggleList let-features>
<clr-toggle-container *ngFor="let feature of features">
<label [for]="feature.configKey">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The info tooltip trigger sits inside the checkbox label. Clicking the info icon also activates the associated toggle, changing the feature when the user only intended to read its description; move the tooltip outside the label.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/config/features/features.component.html, line 3:

<comment>The info tooltip trigger sits inside the checkbox label. Clicking the info icon also activates the associated toggle, changing the feature when the user only intended to read its description; move the tooltip outside the label.</comment>

<file context>
@@ -0,0 +1,86 @@
+<ng-template #toggleList let-features>
+    <clr-toggle-container *ngFor="let feature of features">
+        <label [for]="feature.configKey">
+            {{ feature.name | translate }}
+            <clr-tooltip *ngIf="feature.beta">
</file context>

Comment on lines +864 to +870
"FEATURES": "Features",
"OPTIONAL_FEATURES": "Optional Features",
"FEATURE_BETA": "Beta",
"FEATURE_BETA_WARNING": "Beta feature: behavior and stored data formats may still change between releases.",
"FEATURE_LOCKED_BY": "Set by {{source}}",
"FEATURE_HUGGINGFACE_MODEL_IMPORT": "Hugging Face Model Import",
"FEATURE_HUGGINGFACE_MODEL_IMPORT_INFO": "Adds the Hugging Face registry type, so replication rules and proxy cache projects can import Hugging Face models as OCI artifacts. It cannot be turned off while Hugging Face registry endpoints exist.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: These new entries leave the entire feature-settings block in English when users select German. Translate the labels, warning, locked-by text, and Hugging Face description so the new settings follow the existing German locale behavior.

(Based on your team's feedback about German locale translations.) .

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/i18n/lang/de-de-lang.json, line 864:

<comment>These new entries leave the entire feature-settings block in English when users select German. Translate the labels, warning, locked-by text, and Hugging Face description so the new settings follow the existing German locale behavior.

(Based on your team's feedback about German locale translations.) .</comment>

<file context>
@@ -861,6 +861,13 @@
         "ROOT_CERT_LINK": "Download",
         "REGISTRY_CERTIFICATE": "Registry Zertifikat",
         "NO_CHANGE": "Speichern abgebrochen, da nichts verändert wurde",
+        "FEATURES": "Features",
+        "OPTIONAL_FEATURES": "Optional Features",
+        "FEATURE_BETA": "Beta",
</file context>
Suggested change
"FEATURES": "Features",
"OPTIONAL_FEATURES": "Optional Features",
"FEATURE_BETA": "Beta",
"FEATURE_BETA_WARNING": "Beta feature: behavior and stored data formats may still change between releases.",
"FEATURE_LOCKED_BY": "Set by {{source}}",
"FEATURE_HUGGINGFACE_MODEL_IMPORT": "Hugging Face Model Import",
"FEATURE_HUGGINGFACE_MODEL_IMPORT_INFO": "Adds the Hugging Face registry type, so replication rules and proxy cache projects can import Hugging Face models as OCI artifacts. It cannot be turned off while Hugging Face registry endpoints exist.",
"FEATURES": "Funktionen",
"OPTIONAL_FEATURES": "Optionale Funktionen",
"FEATURE_BETA": "Beta",
"FEATURE_BETA_WARNING": "Beta-Funktion: Verhalten und gespeicherte Datenformate können sich zwischen Versionen noch ändern.",
"FEATURE_LOCKED_BY": "Festgelegt durch {{source}}",
"FEATURE_HUGGINGFACE_MODEL_IMPORT": "Hugging-Face-Modellimport",
"FEATURE_HUGGINGFACE_MODEL_IMPORT_INFO": "Fügt den Registry-Typ Hugging Face hinzu, damit Replikationsregeln und Proxy-Cache-Projekte Hugging-Face-Modelle als OCI-Artefakte importieren können. Die Funktion kann nicht deaktiviert werden, solange Hugging-Face-Registry-Endpunkte vorhanden sind.",

}

.beta-warning {
color: #aa4500;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The beta warning becomes difficult to read in dark mode: #aa4500 has only about 2.5:1 contrast against the dark theme background. Use a theme-aware warning color or add a dark-theme override that meets the contrast requirement.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/config/features/features.component.scss, line 31:

<comment>The beta warning becomes difficult to read in dark mode: `#aa4500` has only about 2.5:1 contrast against the dark theme background. Use a theme-aware warning color or add a dark-theme override that meets the contrast requirement.</comment>

<file context>
@@ -0,0 +1,36 @@
+}
+
+.beta-warning {
+    color: #aa4500;
+}
+
</file context>

for one, to creating a proxy cache project on one and to creating or updating a replication
policy that uses one
- refuses to start a replication of such a policy, manual or scheduled
- serves a `huggingface` proxy cache project from its local cache only, without upstream calls

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This bullet documents behavior that the implementation does not provide: disabling the feature does not stop existing Hugging Face proxy-cache requests from contacting the upstream registry. Add a feature check to the proxy request path before constructing or using the remote helper, or remove this guarantee from the documentation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/model-sync-option-b.md, line 169:

<comment>This bullet documents behavior that the implementation does not provide: disabling the feature does not stop existing Hugging Face proxy-cache requests from contacting the upstream registry. Add a feature check to the proxy request path before constructing or using the remote helper, or remove this guarantee from the documentation.</comment>

<file context>
@@ -153,6 +153,28 @@ Browser login over plain HTTP needed no change: this base no longer uses gorilla
+  for one, to creating a proxy cache project on one and to creating or updating a replication
+  policy that uses one
+- refuses to start a replication of such a policy, manual or scheduled
+- serves a `huggingface` proxy cache project from its local cache only, without upstream calls
+
+Switching it off is refused while `huggingface` registries exist. Policies and proxy cache
</file context>

this.configService.saveConfiguration(changes).subscribe({
next: () => {
this.inProgress = false;
this.config.updateConfig();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The page becomes interactive before the post-save configuration refresh completes. A second edit can race that refresh and be overwritten when updateConfig() replaces the current configuration; include config.getLoadingConfigStatus() in the page's inProgress state, as the other configuration pages do.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/config/features/features.component.ts, line 83:

<comment>The page becomes interactive before the post-save configuration refresh completes. A second edit can race that refresh and be overwritten when `updateConfig()` replaces the current configuration; include `config.getLoadingConfigStatus()` in the page's `inProgress` state, as the other configuration pages do.</comment>

<file context>
@@ -0,0 +1,113 @@
+        this.configService.saveConfiguration(changes).subscribe({
+            next: () => {
+                this.inProgress = false;
+                this.config.updateConfig();
+                this.messageHandler.showSuccess('CONFIG.SAVE_SUCCESS');
+            },
</file context>

mu.RLock()
guard := guards[definition.Feature]
mu.RUnlock()
if guard != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The guard’s count check is not atomic with registry creation. A concurrent create can pass CheckTypeEnabled after the count returns zero and commit after this update, leaving a persisted endpoint while the feature is disabled; serialize these operations or recheck under a shared transaction or lock.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/pkg/feature/feature.go, line 163:

<comment>The guard’s count check is not atomic with registry creation. A concurrent create can pass `CheckTypeEnabled` after the count returns zero and commit after this update, leaving a persisted endpoint while the feature is disabled; serialize these operations or recheck under a shared transaction or lock.</comment>

<file context>
@@ -0,0 +1,181 @@
+		mu.RLock()
+		guard := guards[definition.Feature]
+		mu.RUnlock()
+		if guard != nil {
+			if err := guard(ctx); err != nil {
+				return err
</file context>

log.Errorf("failed to get registry, error:%v", err)
return false
}
if !feature.RegistryTypeEnabled(ctx, reg.Type) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The feature gate does not cover the referrer proxy path: ProxyReferrerMiddleware bypasses canProxy and can still fetch referrers from a healthy disabled-type registry. Apply the same registry-type check before creating the remote helper or in a shared proxy guard.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/server/middleware/repoproxy/proxy.go, line 418:

<comment>The feature gate does not cover the referrer proxy path: `ProxyReferrerMiddleware` bypasses `canProxy` and can still fetch referrers from a healthy disabled-type registry. Apply the same registry-type check before creating the remote helper or in a shared proxy guard.</comment>

<file context>
@@ -414,6 +415,10 @@ func canProxy(ctx context.Context, p *proModels.Project) bool {
 		log.Errorf("failed to get registry, error:%v", err)
 		return false
 	}
+	if !feature.RegistryTypeEnabled(ctx, reg.Type) {
+		log.Debugf("the registry type %s is disabled, serve project %s from the local cache only", reg.Type, p.Name)
+		return false
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants