Skip to content

Commit 776f148

Browse files
authored
feat(helm): publish the chart to GHCR as a signed OCI artifact (#7563)
* feat(helm): publish the chart to GHCR as a signed OCI artifact The chart has only ever existed inside the repo, so self-hosters whose security process requires an officially published artifact could not use it at all — a chart pulled from a git checkout is not something they can pin, mirror, or admit. Publishes helm/sim to oci://ghcr.io/simstudioai/charts/sim on push to main, signed with Sigstore keyless signing and carrying a SLSA build provenance attestation, both stored in the registry so they survive a mirror into an internal registry. The publish job lives in helm.yml rather than its own publish-*.yml so it can gate on the existing jobs: nothing ships unless the chart linted, unit-tested, rendered clean under kubeconform, and installed on kind. Publishing is idempotent — a version already in the registry is skipped rather than overwritten, since a published chart version is immutable. Also pushes helm/artifacthub-repo.yml to the reserved artifacthub.io tag for the Artifact Hub listing, and switches the chart README and the Kubernetes docs page off the clone-and-install path. * fix(helm): fail closed on registry lookup errors and pin documented installs The publish guard treated every non-zero `helm show chart` exit as proof the version was unpublished, so a transient 5xx, an expired token, or a DNS blip would have enabled a push that moves an already-published version tag. Runs where the version already exists are routine — the path filter also fires on package.json and workflow edits — so this was reachable. Now only an explicit `: not found` counts as absent; anything else stops the job. Verified against the pinned Helm v3.16.4 that an absent version and an absent repository both report `: not found`, so a first publish still proceeds, while denied/unauthorized/dial-tcp failures abort instead. Also pins the two documented install paths that were still reproducible only by accident: the cloud-specific command paired a pinned chart with values fetched from the moving main branch, and the README quick start omitted --version while the same README warns against exactly that. * chore(helm): harden the chart workflow against zizmor and document cosign v3 Ran the two standard workflow linters over helm.yml. actionlint reported one shellcheck style issue; zizmor reported 22 findings, including two High. Both High findings predate this PR: the version gate expanded github.base_ref straight into a run block. The three Medium findings were checkouts leaving the token in .git/config. Fixed all of them here rather than leaving known findings in a file this PR already rewrites -- the gate only reads history and fetches a public branch, so it never needed the credential. The 16 informational findings were mine: every ${{ }} in a run block is template expansion before the shell sees it, so the documented fix is to pass values through env and reference them as shell variables. Done for the whole publish job, which also removes the shellcheck nit. zizmor now reports zero findings on the file in pedantic mode, with no suppressions, and actionlint is clean. Separately, cosign v3 writes the Sigstore protobuf bundle format by default and cosign v2 cannot read it, so the verification instructions now state the required version. The in-workflow verify uses the same binary it signs with, so it could never have caught this. Re-verified the existence guard's four branches after the env refactor, and confirmed it also fails closed when the registry login has not happened. * fix(helm): push Artifact Hub metadata from helm/ so the layer title matches Dry-ran the publish sequence against a local OCI registry, which surfaced one divergence from Artifact Hub's documented command: passing a path-qualified argument to oras records the layer's title annotation as `helm/artifacthub-repo.yml` rather than the bare `artifacthub-repo.yml` the docs produce. Artifact Hub selects the layer by media type, so this was probably harmless, but there is no reason to diverge from the documented form. Running the step from helm/ reproduces it exactly. Everything else in the sequence behaved as intended against a real registry: helm push derives charts/sim:1.9.1 from the chart name and version, the digest grep extracts it, the metadata lands with both documented media types under the artifacthub.io tag, the chart and metadata tags coexist without disturbing each other, and helm pull round-trips to an identical sha256. Also confirmed the overwrite hazard behind the fail-closed guard is real rather than theoretical: re-pushing modified content under an existing version moved the tag, so a pinned consumer would have silently received different bytes. * fix(helm): categorise the chart for Artifact Hub The chart carried `category: developer-tools`, which is a different convention entirely -- Artifact Hub reads `artifacthub.io/category` and accepts only one value from a fixed list that has no developer-tools entry. The listing this PR sets up would therefore have published uncategorised. Adds `artifacthub.io/category: ai-machine-learning`, the closest fit in that list, and keeps the bare annotation for whatever else consumes it. Bumps to 1.9.2 per the chart's own gate and moves the documented versions with it. Deliberately not adding `artifacthub.io/images`: Artifact Hub already extracts images from a dry-run render, and a hand-listed copy would be a second source of truth next to the generated images.yaml. * feat(helm): also publish the chart to a classic HTTP repo I claimed earlier that OCI-only was defensible in 2026. That was wrong, and checking rather than reasoning settled it: Bitnami, cert-manager, ingress-nginx, prometheus-community, Grafana, Argo and external-secrets all still serve a live index.yaml. Dual-publish is the actual convention, and external-secrets -- the workflow this one is modelled on -- does both. Adds a chart-releaser job that maintains index.yaml on a pages branch and attaches each packaged chart to a GitHub release. It is a separate job from the OCI publish because it needs contents: write to cut that release, and that permission has no business sitting in the job that holds the signing identity. Three details worth stating: - mark_as_latest is false. A chart release must never take the "Latest" badge from the application release it packages. - Releases are named helm-chart-<version> so they stay distinguishable from the vX.Y.Z app releases in the same list. - skip_existing mirrors the OCI job: re-running on a published version is a no-op rather than an overwrite. The job no-ops with a warning until a gh-pages branch exists, because creating that branch and enabling Pages are one-time manual steps no workflow can perform for itself, and main's CI must not fail on their absence. No documentation yet. index.yaml is only reachable once the branch, Pages, and DNS are in place, and pointing users at a hostname that does not resolve is the exact failure this whole PR exists to remove. * docs(helm): document the Helm repository now that charts.sim.ai serves Held these back until the hostname actually resolved, because shipping an install command that points at a dead domain is the exact failure this work exists to remove. It resolves now: DNS is in place, GitHub Pages serves the branch, and Let's Encrypt has issued the certificate. Verified end to end rather than assumed: helm repo add sim https://charts.sim.ai helm repo update # Successfully got an update Presents OCI as the preferred path in both places and the repository as the compatibility path, and says plainly that the signature and provenance are attached to the OCI artifact only -- `helm repo add` has no equivalent, and leaving that implicit would let someone believe they had verified something they had not. Bumps to 1.9.3 per the chart's own gate, since this touches helm/sim/. * docs(self-hosting): install from the published chart, not a checkout The Terraform page still told readers the chart "is not published to a Helm repository or an OCI registry, so there is no repository to point at", and had them vendor the repo as a submodule or git clone. That is now false, and it is the same claim that makes a self-hoster conclude Sim is not packaged for them, so it is the most important line in the docs to correct. It now points repository at the OCI registry with a pinned version, notes the classic repository as the alternative for tooling that does not speak OCI, and replaces the "because the chart is local, version does nothing" paragraph -- pinning the chart version is now both possible and the thing that stops an unplanned apply pulling new migrations. Syntax checked against the provider docs; the page's existing set_sensitive list form is already v3. Also switches the two upgrade commands and the env render check off ./helm/sim, which no longer exists for anyone following the install path. Deliberately unchanged, because these are not install references: - The chart README links on the Kubernetes page. The README lives in the repo; charts.sim.ai serves index.yaml, not documentation. - The cosign --certificate-identity-regexp. That is a Sigstore OIDC identity that happens to look like a URL, and rewriting it would break verification. - The releases page and issue tracker links, which point where they should. - The git clone in the quick start, which is the Docker Compose path and genuinely does need the repo. * docs(helm): audit the chart documentation against the chart Swept the remaining checkout-era references and audited the Helm docs by checking every values path they mention against values.yaml rather than reading them. That found a real error: the chart has no `copilot.env`. The map is `copilot.server.env`, so anyone following the env-validation note would have set a key that silently does nothing. Fixed. It also flagged `app.envDefaults.FREE_TABLES_LIMIT` and `app.secrets.existingSecret.keys`, which are NOT bugs -- both appear only in upgrade notes describing keys older releases shipped and this one removed. Left exactly as they are; "fixing" them would delete the history an upgrader needs. The chart README was the bigger problem. It led with registry install after the earlier commits, then used `./helm/sim` in eleven subsequent commands -- upgrade, dry-run, examples, parameters, secrets, troubleshooting. Someone following the documented install had no such path. They now use the OCI reference with a pinned version, with one note establishing that convention so the checkout form does not have to be repeated everywhere. Two related corrections while in there: the example values files are not part of the packaged chart, so the examples section now fetches one at a release tag instead of assuming it locally; and values.schema.json cannot be `cat`ed without a checkout, so it links to the repo or `helm pull --untar`. Left pointing at GitHub on purpose: the chart README deep links (that is where the README lives, and an extra hop through the repository root would be worse), the releases page, the issue tracker, the cosign identity regexp, and the Docker Compose quick start's git clone. Bumps to 1.9.4 per the chart's own gate. * fix(helm): track the app release in appVersion, and enforce it appVersion sat at v0.8.18 while the app shipped through v0.8.24 -- six releases. Because the image tags default to Chart.AppVersion, a default `helm install` deployed the older Sim, and regenerating images.yaml here moved eleven lines, so the mirror inventory an air-gapped operator works from named the wrong images too. Publishing is what makes this serious. Cloning main got you whatever was there; a published chart version is immutable, so every stale appVersion would be frozen and installable forever. It was bumped by hand and nothing checked it, which is why it drifted. The publish job now refuses to publish when appVersion does not match the latest GitHub release, comparing against the release API rather than a hardcoded value so the check cannot go stale itself. Confirmed it fires on exactly the drift that existed (v0.8.18 vs v0.8.24) and passes now that it is fixed. Bumps appVersion to v0.8.24, regenerates images.yaml, and takes the chart to 1.9.5. * fix(helm): fail the appVersion check only when it lags, not on any mismatch The check compared appVersion to /releases/latest for equality, which races the release it is meant to protect. Version tags are cut by the main-branch merge commit that releases them, so on that commit appVersion legitimately names a release that does not exist yet while ci.yml is still building it. Equality would have rejected the bump and blocked the very publish it was for. helm/sim/ci/kind-values.yaml already documents this circularity, and records it as the reason appVersion went unbumped from chart 1.2.0 to 1.6.3. Lagging is the failure; being ahead is a normal transient. The comparison is `sort -V`, so it orders versions rather than strings -- v0.9.9 against v0.10.0 is exactly where a string compare silently inverts. Verified all five cases: behind fails, equal passes, ahead passes with a notice, and both multi-digit orderings resolve correctly.
1 parent 5730e62 commit 776f148

9 files changed

Lines changed: 529 additions & 49 deletions

File tree

.github/workflows/helm.yml

Lines changed: 303 additions & 2 deletions
Large diffs are not rendered by default.

apps/docs/content/docs/platform/self-hosting/environment-variables.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ Setting the variable to an empty string does **not** remove it: the chart reads
311311
Null the variable in every layer that sets it. If it appears in both `app.env` and `app.envDefaults`, nulling only the `app.env` entry lets the `envDefaults` value apply again and the limit stays in force. With External Secrets, also drop the key from `externalSecrets.remoteRefs.app`, which keeps syncing it independently. Confirm what the pod will actually receive before rolling out:
312312

313313
```bash
314-
helm template sim ./helm/sim -f values.yaml | grep -A1 FREE_TABLE # expect no output
314+
helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 -f values.yaml | grep -A1 FREE_TABLE # expect no output
315315
```
316316

317317
`null` deletion has no effect under `helm upgrade --reuse-values` — pass your full values with `-f`, or use `--reset-then-reuse-values` (Helm 3.14+). If you deploy with Argo CD, put the `null` in `valueFiles` or the `values` string rather than `valuesObject`, which strips nulls. On Docker Compose, delete the line from your `.env` file.

apps/docs/content/docs/platform/self-hosting/kubernetes.mdx

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@ import { FAQ } from '@/components/ui/faq'
2121

2222
## Installation
2323

24-
```bash
25-
# Clone repo
26-
git clone https://github.com/simstudioai/sim.git && cd sim
24+
The chart is published to GitHub Container Registry as an OCI artifact at
25+
`oci://ghcr.io/simstudioai/charts/sim`. Install it directly — no clone required.
26+
27+
It is also available from a classic Helm repository at `https://charts.sim.ai`
28+
for tooling that expects one; see [Helm repository](#helm-repository) below.
29+
Prefer OCI where you can, since the signature and provenance are attached to the
30+
OCI artifact.
2731

32+
```bash
2833
# Generate secrets
2934
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
3035
ENCRYPTION_KEY=$(openssl rand -hex 32)
@@ -34,7 +39,8 @@ CRON_SECRET=$(openssl rand -hex 32)
3439
POSTGRES_PASSWORD=$(openssl rand -hex 24)
3540

3641
# Install
37-
helm install sim ./helm/sim \
42+
helm install sim oci://ghcr.io/simstudioai/charts/sim \
43+
--version 1.9.5 \
3844
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
3945
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
4046
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
@@ -56,13 +62,58 @@ helm install sim ./helm/sim \
5662
This installs the chart's default image tag. For production, **pin `app`, `realtime`, and `migrations` to the same explicit release tag** — see [Upgrades](/platform/self-hosting/upgrades).
5763
</Callout>
5864

65+
## Helm repository
66+
67+
For clusters or GitOps configs that consume `helm repo add` rather than OCI:
68+
69+
```bash
70+
helm repo add sim https://charts.sim.ai
71+
helm repo update
72+
73+
helm install sim sim/sim --version 1.9.5 --namespace simstudio --create-namespace \
74+
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
75+
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
76+
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
77+
--set app.env.API_ENCRYPTION_KEY="$API_ENCRYPTION_KEY" \
78+
--set app.env.CRON_SECRET="$CRON_SECRET" \
79+
--set postgresql.auth.password="$POSTGRES_PASSWORD"
80+
```
81+
82+
It serves the same chart as the OCI registry. The signature and provenance below
83+
apply to the OCI artifact only.
84+
85+
## Verifying the chart
86+
87+
Every published version is signed with Sigstore keyless signing and carries a SLSA build-provenance attestation. Both live in the registry alongside the chart, so they survive a mirror into an internal registry.
88+
89+
```bash
90+
cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.5 \
91+
--certificate-identity-regexp '^https://github.com/simstudioai/sim/' \
92+
--certificate-oidc-issuer https://token.actions.githubusercontent.com
93+
94+
gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.5 --repo simstudioai/sim
95+
```
96+
97+
Signing is Sigstore-only — there is no GPG `.prov` file, so `helm install --verify` does not apply.
98+
99+
<Callout type="warn">
100+
Verification requires **cosign v3.0 or newer**. Signatures use the Sigstore protobuf bundle format, which cosign v3 writes by default and cosign v2 cannot read. cosign v3.1+ auto-detects both formats.
101+
</Callout>
102+
59103
## Cloud-Specific Values
60104

61105
These are cloud-tuned **alternatives** to the generic install above — pick one path, don't run both. The commands reuse the `$BETTER_AUTH_SECRET`, `$ENCRYPTION_KEY`, `$INTERNAL_API_SECRET`, `$API_ENCRYPTION_KEY`, `$CRON_SECRET`, and `$POSTGRES_PASSWORD` variables generated in [Installation](#installation) above, so run that block's `openssl` lines first in the same shell. They use `helm upgrade --install`, so they work whether or not a release exists yet. Two caveats when converting an existing generic install rather than starting fresh: (1) **reuse the original secret values** — recover them with `helm get values sim -n simstudio` if your shell no longer has them; supplying a newly generated `ENCRYPTION_KEY` makes every previously encrypted value (workspace environment variables, stored provider keys, MCP OAuth credentials) undecryptable. (2) The cloud values rename the bundled PostgreSQL database to `simstudio`, but Postgres only applies that setting on first initialization — add `--set postgresql.auth.database=sim` to keep your existing database. If you'd rather start clean, `helm uninstall sim -n simstudio`, delete its PVCs, and run the cloud command fresh.
62106

63107
```bash
64-
helm upgrade --install sim ./helm/sim \
65-
--values ./helm/sim/examples/values-aws.yaml \
108+
# The example values files are not part of the packaged chart, so fetch the one
109+
# you want at a release tag — pinning the chart but reading values off a moving
110+
# branch would still make this command produce different deployments over time.
111+
SIM_RELEASE=v0.8.24
112+
curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-aws.yaml"
113+
114+
helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \
115+
--version 1.9.5 \
116+
--values values-aws.yaml \
66117
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
67118
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
68119
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
@@ -80,7 +131,7 @@ helm upgrade --install sim ./helm/sim \
80131

81132
Every one of those overrides is required. The cloud values files hardcode a placeholder domain in all six places, and overriding only `NEXT_PUBLIC_APP_URL` leaves sign-in pointed at the placeholder, realtime rejecting every socket upgrade, and the Ingress serving the wrong host.
82133

83-
Swap the `--values` file for your cloud: `values-aws.yaml` (EKS), `values-azure.yaml` (AKS), or `values-gcp.yaml` (GKE). Everything else is identical.
134+
Swap the `--values` file for your cloud: `values-aws.yaml` (EKS), `values-azure.yaml` (AKS), or `values-gcp.yaml` (GKE). Everything else is identical. Keep the downloaded file in your own config repo — the `--set` overrides above cover the six placeholder domains, but anything else you tune belongs in the file.
84135

85136
## Key Configuration
86137

@@ -181,8 +232,8 @@ kubectl port-forward deployment/sim-app 3000:3000 -n simstudio
181232
# View logs
182233
kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
183234
184-
# Upgrade
185-
helm upgrade sim ./helm/sim --namespace simstudio
235+
# Upgrade (always pin the target chart version)
236+
helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 --namespace simstudio
186237
187238
# Uninstall
188239
helm uninstall sim --namespace simstudio

apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,23 @@ See [Security](/platform/self-hosting/security) for the full secret inventory, w
139139

140140
## Calling the chart from Terraform
141141

142-
If you already run Terraform, the chart is the resource to wrap — not something to reimplement. It is not published to a Helm repository or an OCI registry, so there is no `repository` to point at: vendor this repo as a submodule, a release tarball, or a `git clone` in your pipeline, and give `chart` the local path.
142+
If you already run Terraform, the chart is the resource to wrap — not something to reimplement. Point `repository` at the OCI registry and pin `version`; there is no need to vendor the repo.
143143

144144
```hcl
145145
resource "helm_release" "sim" {
146146
name = "sim"
147147
namespace = "sim"
148148
create_namespace = true
149149
150-
# Local path, not a repository. Pin the git ref you vendor from.
151-
chart = "${path.module}/sim/helm/sim"
150+
# Always pin `version`. Without it Terraform resolves the newest published
151+
# chart at apply time, which is how an unplanned apply moves Sim to a new
152+
# release with new migrations.
153+
repository = "oci://ghcr.io/simstudioai/charts"
154+
chart = "sim"
155+
version = "1.9.5"
156+
157+
# Or the classic repository, if your tooling does not speak OCI:
158+
# repository = "https://charts.sim.ai"
152159
153160
# Your own values file. The examples under helm/sim/examples/ carry
154161
# placeholder secrets and are starting points, not deployable as-is.
@@ -172,6 +179,6 @@ resource "helm_release" "sim" {
172179
The example values files ship literal placeholders such as `your-secure-production-auth-secret-here`. That includes `postgresql.auth.password`. The chart only rejects empty values and its own `CHANGE-ME` strings, so a deployment that inherits those placeholders installs cleanly with a publicly known session-signing secret and database password. Override every secret, or use External Secrets and set none of them inline.
173180
</Callout>
174181

175-
Because the chart is local, `version` does nothing — what pins it is the git ref you vendor from, and `helm/sim/Chart.yaml` tells you which chart release that ref carries. Pin that ref, and pin the image tags separately, or an unplanned `terraform apply` can move Sim to a new release with new migrations. See [Upgrades](/platform/self-hosting/upgrades).
182+
Pin `version` above, and pin the image tags separately — the chart version and the application version move independently, so pinning one does not pin the other. See [Upgrades](/platform/self-hosting/upgrades).
176183

177184
Once the infrastructure exists, follow [Kubernetes](/platform/self-hosting/kubernetes) for the install itself, then the [pre-launch checklist](/platform/self-hosting/security) and the [verification checklist](/platform/self-hosting/verify).

apps/docs/content/docs/platform/self-hosting/upgrades.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,16 @@ Migration surprises are usually data-shaped rather than schema-shaped, so a stag
156156
<Tab value="Kubernetes (Helm)">
157157

158158
```bash
159-
helm upgrade sim ./helm/sim \
159+
helm upgrade sim oci://ghcr.io/simstudioai/charts/sim \
160+
--version 1.9.5 \
160161
--namespace simstudio \
161162
--values my-values.yaml
162163
```
163164

164165
Preview first if the chart version changed:
165166

166167
```bash
167-
helm diff upgrade sim ./helm/sim -n simstudio --values my-values.yaml
168+
helm diff upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 -n simstudio --values my-values.yaml
168169
```
169170

170171
Then watch the rollout:

helm/artifacthub-repo.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Artifact Hub repository metadata for the Sim chart.
2+
#
3+
# Lives outside `helm/sim/` on purpose: it describes the *repository* rather
4+
# than the chart, so it must not be packaged into the `.tgz`, and a change to it
5+
# must not trip the chart's mandatory Chart.yaml version bump.
6+
#
7+
# The publish job pushes this file to the OCI registry under the reserved
8+
# `artifacthub.io` tag, which is where Artifact Hub looks for it:
9+
# ghcr.io/simstudioai/charts/sim:artifacthub.io
10+
#
11+
# `owners` is what backs an ownership claim — Artifact Hub matches the email of
12+
# the requesting account against this list, and processes claims immediately
13+
# rather than waiting for the next repository scan.
14+
owners:
15+
- name: Sim Team
16+
email: help@sim.ai
17+
18+
# Set this to the repository's Artifact Hub ID once the repository has been
19+
# registered at https://artifacthub.io/control-panel/repositories. It is what
20+
# turns on the "Verified Publisher" badge; until then the listing still works,
21+
# just unverified.
22+
# repositoryID: ""

helm/sim/Chart.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: v2
22
name: sim
33
description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents
44
type: application
5-
version: 1.9.0
6-
appVersion: "v0.8.18"
5+
version: 1.9.5
6+
appVersion: "v0.8.24"
77
kubeVersion: ">=1.25.0-0"
88
home: https://sim.ai
99
icon: https://raw.githubusercontent.com/simstudioai/sim/main/apps/sim/public/logo/primary/primary.svg
@@ -21,6 +21,9 @@ keywords:
2121
- nextjs
2222
annotations:
2323
category: developer-tools
24+
# Artifact Hub only accepts one value from its fixed list; the bare `category`
25+
# above is a different convention and does not categorise the listing.
26+
artifacthub.io/category: ai-machine-learning
2427
artifacthub.io/license: Apache-2.0
2528
artifacthub.io/links: |
2629
- name: Homepage

0 commit comments

Comments
 (0)