Resolve open npm Dependabot alerts for the web app and docs site by refreshing dependency overrides and lockfiles.
Also sets the staging web forwarded-proto default so `make staging` can render the shared Nginx template when using the bare nginx image.
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
A deployment that stands up its own organisation-wide namespace — to use
instead of the built-in global one — finds it invisible to everybody. The
namespace listing only returns namespaces the caller belongs to, and the
only thing that ever added members automatically was hard-wired to the slug
"global".
Make that list a setting. namespace.default-membership holds the slugs every
newly activated account is enrolled in, defaulting to ["global"], which is
what every deployment did before. Publishing only requires membership of any
role, so being enrolled is enough to publish there; no extra grant needed.
GlobalNamespaceMembershipService becomes DefaultNamespaceMembershipService,
since it no longer means one specific namespace.
Where the strictness sits:
- Saving validates every slug resolves to an ACTIVE namespace, so a typo
fails at the moment an administrator makes it.
- Enrolling tolerates a slug that no longer resolves: it logs and skips.
A namespace that was deleted or renamed must not cost somebody their
login.
Adding a namespace to the list after people have signed up leaves them out,
the same trap the personal-namespace work hit, so this ships with the same
preview-then-apply backfill.
Verified against a real PostgreSQL, end to end: create a namespace, reject an
unknown slug, save with whitespace and duplicates and see them normalised,
preview, apply, re-preview showing nothing left, and a fresh registration
landing in the global namespace, the new shared one, and its own personal one
at once.
Turning provisioning on only affects accounts activated afterwards, which
on a registry that has already been running means nobody. The first person
to hit this on our deployment was the operator who enabled it: they signed
in, got no namespace, and had no way to find out why.
Two fixes.
Backfill. POST /api/v1/admin/settings/personal-namespace/backfill walks the
active accounts and gives a namespace to those without one, skipping system
accounts and anyone who already owns a non-global namespace. Details worth
knowing:
- dryRun reports the plan — each account and the slug it would take —
without writing. The console requires a preview before it will enable the
apply button.
- The response lists only accounts that changed or could not be placed;
the rest are counted, so an operator reads the work rather than the whole
directory.
- A run stops at a per-run account cap and reports truncated rather than
looking like it covered everything.
- Slugs promised earlier in a run are reserved, so one batch cannot hand
the same slug to two accounts.
- Not @Transactional: each namespace is created in its own transaction, so
an account that cannot be placed does not discard the rest of the run.
Diagnosability. The skip paths — provisioning disabled, account already owns
a namespace, no slug available — were silent returns, which is why "nothing
happened and I cannot tell why" was the actual user experience. They now log
their reason; account activation is rare enough that the extra lines cost
nothing.
Self-hosted deployments want every new account to have somewhere of its
own to publish, without asking an administrator for a namespace first and
without pushing drafts into `global`.
Add an operator-controlled policy, off by default so upgrading changes no
behaviour. When enabled, an account that becomes usable gets a namespace
it owns. "Private" here means a team namespace whose only member is that
account: namespaces have no visibility flag, and skill visibility stays a
property of each skill.
Trigger points. UserActivatedEvent is published wherever an account first
becomes usable:
- LocalAuthService.register
- IdentityBindingService.bindOrCreate, for ACTIVE first logins
- AdminUserAppService.updateUserStatus, on a transition into ACTIVE
The third matters for deployments that gate access behind approval: those
accounts are created PENDING at the first OAuth attempt and only become
usable when an administrator approves them.
Why an AFTER_COMMIT listener rather than a call alongside
GlobalNamespaceMembershipService.ensureMember. Both namespace.created_by
and namespace_member.user_id reference user_account(id), which rules out
each obvious alternative:
- Joining the registration transaction lets a slug clash roll the
registration back, so a namespace failure costs the user their account
— or, on OAuth, their login.
- Suspending it with REQUIRES_NEW leaves the new transaction unable to see
the uncommitted user_account row, so the foreign key check blocks on the
outer transaction's row lock and the two wait on each other.
Provisioning after commit avoids both. The listener is deliberately not
@Async, so the namespace exists by the time the user's next request
arrives, and it swallows failures.
Naming. Two templates over ${username}, ${email_prefix} and ${user_id};
unknown placeholders are left in place so a typo is visible rather than
silently dropped. ${username} falls back to the email local part and then
to the user id. Slugs go through the existing slug rules, which is why the
console renders a live preview: underscores are not legal in a slug, so
`${username}_space` yields `alice-space`. A taken or reserved candidate
gets a numeric suffix, so `admin` becomes `admin-2`. Owning any non-global
namespace already skips provisioning, which keeps re-enabling an account
from handing out a second one.
The templates are not exposed in application.yml: they contain ${...},
which Spring would resolve as property references, and Boot 3.2 predates
placeholder escaping. Only the enable flag lives there; templates are set
in the console and default from PersonalNamespaceProvisioningProperties.
Updating the policy writes an audit entry with the before and after.
Align the label system design with click-through chips on skill detail
and wrapping search filter chips when many labels are present.
Signed-off-by: Mikhail Neradkov <michael.neradkov@gmail.com>
* fix(api): tell callers why a request was forbidden
The scope filter already computes an exact reason ("Missing API token
scope: skill:delete", "API token cannot access endpoint: /x") and the
access-denied handler discarded it, returning a bare "Forbidden" for
every case: missing scope, endpoint closed to API tokens, and paths
that simply don't exist. Clients cannot tell those apart, so they
guess — the published CLI reports every 403 as "token may lack
required scope", which sent us debugging token scopes for an hour when
the real causes were a revoked token and a mistyped namespace path.
The reason now rides in the response via a new error.forbidden.detail
message (en + zh), and is logged alongside the exception type.
Signed-off-by: Gal Eyal <gal.e@popai.health>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(api): safely expose API token denial reasons
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
---------
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
* docs(faq): add community-sourced deployment and operations Q&A
Adds entries collected from real user-support threads to the reference FAQ
(both zh and en):
- 502 on auth APIs while the page loads, traced to server startup failure
on the SKILLHUB_DOWNLOAD_ANON_COOKIE_SECRET placeholder guard
- config changes not taking effect (.env.release.example vs .env.release,
restart vs recreate)
- built-in skill sync failure in offline environments
- upgrade path with Flyway auto-migration and volume retention
- external dependencies and the lack of MySQL support
- granting SUPER_ADMIN to an OAuth account via the bootstrap admin
- telling CLI and server versions apart
- installing skills into a target directory on an intranet
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
* docs(faq): move entries to the published docs source and fix inaccuracies
Move the new FAQ entries from document/ (a generated tree that the docs
build does not read) to docs/skillhub/, which is what make docs-build and
the Pages deploy actually publish.
Also address review feedback:
- drop the SKILLHUB_BUILTIN_SKILLS_ENABLED tip; compose.release.yml does
not pass that variable through, so setting it has no effect
- correct the dependency list: object storage defaults to local, S3 is
recommended for production
- soften the 502 wording, since upstream/DNS/network can also cause it
- state the 32-character minimum for the cookie secret
- give a real bulk-install example and qualify v0.2.12 as a server version
- drop entries already covered by existing upgrade/MySQL/version questions
Signed-off-by: FenjuFu <92919259+FenjuFu@users.noreply.github.com>
* docs(faq): correct deployment and admin guidance
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
* docs(faq): fix remaining recreate guidance
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
* docs(faq): clarify bulk install paths
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
---------
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
Signed-off-by: FenjuFu <92919259+FenjuFu@users.noreply.github.com>
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Co-authored-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>