* feat(auth): expose skill lifecycle routes to API tokens
With an API token, v0.2.19 can remove a whole skill (DELETE
/api/v1/skills/{ns}/{slug} with skill:delete) but cannot archive or
unarchive a skill, nor delete a single draft/rejected version. Those
three routes are opened by AUTHORIZATION_POLICIES (authenticated
fallback) yet have no entry in API_TOKEN_POLICIES, so a bearer request
falls through to "unsupported" and is rejected with 403.
That contradicts the contract written above SESSION_ONLY_ROUTES in
RouteSecurityPolicyRegistry: bearer tokens are rejected on exactly the
listed session-only routes and nowhere else, and anything else the
authorization list opens must be reachable with a token holding the
required scope.
Add API-token policies for both the /api/v1 and /api/web prefixes that
SkillLifecycleController serves:
- POST .../skills/{ns}/{slug}/archive and .../unarchive require
skill:publish. They are owner-level operations, gated by the same
assertCanManageLifecycle check as publishing, so they sit at the same
scope tier.
- DELETE .../skills/{ns}/{slug}/versions/{version} requires
skill:delete, matching the existing whole-skill delete.
Whole-skill DELETE on /api/web stays session-only as documented; the
new version-delete pattern does not overlap it. No scope allow-list
exists outside the registry (TokenController and ApiTokenScopeService
accept any scope string), so no other change is needed for tokens to
carry these scopes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdnYX4jTS3JwMMP9JCGxzU
* feat(skills): let skill owners yank a published version
Yanking a published version is only available through
POST /api/v1/admin/skills/versions/{versionId}/yank, which is
session-only (SESSION_ONLY_ROUTES covers /api/v1/admin/**) and requires
SKILL_ADMIN or SUPER_ADMIN. A skill owner therefore cannot pull a
broken release themselves, neither from the web surface nor from a
script holding an API token.
In package registries yank is an act of the publisher: `cargo yank`
and PyPI's "yank release" are performed by the package owner, not by a
registry admin, because the goal is to stop new installs of a bad
release while keeping the artifact available for lock files. SkillHub
already lets owners archive, unarchive, rerelease and delete draft
versions through SkillLifecycleController under the
assertCanManageLifecycle rule (owner, or namespace ADMIN/OWNER); yank
belongs on the same surface with the same rule.
Changes:
- SkillGovernanceService: add an owner-checked yankVersion(skill,
version, actor, roles, ip, ua, reason) that runs
assertCanManageLifecycle and then the same yank logic as the admin
variant, now shared in yankVersionInternal. The admin entry point is
unchanged for AdminSkillController.
- SkillLifecycleAppService / GovernanceWorkflowAppService: resolve
skill and version by namespace/slug/version, delegate to the new
domain method, and return SkillLifecycleMutationResponse with action
YANK and the resulting version status. The YANK_SKILL_VERSION audit
record and SkillVersionYankedEvent are emitted by the domain service
exactly as for the admin path.
- SkillLifecycleController: POST /{namespace}/{slug}/versions/{version}/yank
on both /api/v1/skills and /api/web/skills, optional body
AdminSkillActionRequest (reason).
- RouteSecurityPolicyRegistry: require skill:yank for the new route on
both prefixes, so tokens can reach it as the SESSION_ONLY_ROUTES
comment promises for every route the authorization list opens. The
admin yank stays session-only. No allow-list of scopes exists outside
the registry; the docs' scope enumeration is updated to include
skill:yank.
- Tests: RouteSecurityPolicyRegistryTest (scope required on both
prefixes, admin route still closed), SkillGovernanceServiceTest
(owner and namespace ADMIN allowed, MEMBER forbidden, unpublished
rejected), SkillLifecycleControllerTest (envelope with and without
body).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdnYX4jTS3JwMMP9JCGxzU
* fix(auth): complete API token lifecycle access
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
* fix(skills): align owner lifecycle token access
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
---------
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Co-authored-by: Thiago Nascimento Nogueira <thiago.nascimento.nogueira@emeal.nttdata.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Add ownerId and ownerDisplayName fields to SkillSummaryResponse,
and render author and last update time at the bottom of each skill card.
在技能卡片底部新增作者和最近更新时间显示,搜索结果接口新增
ownerId 和 ownerDisplayName 字段。
Log: 技能卡片新增作者和更新时间
Influence: 搜索结果页技能卡片底部显示作者和更新时间信息,接口新增 ownerId/ownerDisplayName 字段。
Signed-off-by: wurongjie <wurongjie@uniontech.com>
* feat(api): return skill labels from the skill listing endpoints on request
Skill labels were reachable only one skill at a time, through
/api/{v1,web}/skills/{namespace}/{slug}/labels, so a client rendering a list had
to issue a follow-up request per row.
Add includeLabels=true to GET /api/v1/skills and GET /api/web/skills. The labels
array is populated only when the parameter is set and left out of the payload
otherwise, so existing responses are byte-identical.
Labels for the whole page are resolved by SkillLabelProjectionService in three
queries — assignments, definitions, translations — rather than three per skill.
Closes#710
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
* fix(api): use include parameter for skill labels
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
* test(api): reject unsupported include before search
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
---------
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Co-authored-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
* feat(my-skills): add keyword search, namespace filter and clickable pagination
Add comprehensive filtering and search capabilities to the My Skills page:
- Keyword search: search by skill name, slug, or description
- Namespace filter: filter skills by namespace
- Clickable pagination: page number buttons with smart ellipsis
- State preservation: sync search state to URL, restore when returning from detail page
- Debounced search: 300ms debounce to avoid excessive queries
- Fix: hide stale rejected preview badge when newer version is published
Backend changes:
- MySkillAppService: add keyword and namespace filtering logic
- SkillLifecycleProjectionService: only show preview versions newer than published
- MeController: add keyword and namespace query parameters
- 6 new test cases covering search and filter scenarios
Frontend changes:
- my-skills.tsx: search input, namespace dropdown, URL state sync
- pagination.tsx: clickable page numbers with ellipsis
- use-user-queries.ts: prevent flicker on query transitions
- skill-detail.tsx: remove invalid rejected badge display
- router.tsx: URL parameter validation
- i18n: add search-related translation keys
Synced from SAAS commits:
- 939fa749 (feat: search and filters)
- dc14df6c (fix: search flicker)
- 0168ea81 (fix: rejected badge)
- c9eefa93 (fix: stale preview)
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
* fix(tests): address test failures in PR #493
Backend test fixes:
- Remove unnecessary Mockito stubbing for filtered-out skills
- Add missing findBySkillIdAndStatus stub for published version lookup
- Update MeController test mocks to match new method signature (keyword, namespace params)
Frontend fixes:
- Fix TypeScript error: useMyNamespaces returns ManagedNamespace[] not PagedResponse
- Add type annotation for namespace map callback parameter
E2E test fix:
- Update URL regex to allow query parameters (returnTo from search page)
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
* fix(tests): resolve test failures in PR #493
Backend:
- Remove unnecessary mock stubbings for skillId 2 and 3 in MySkillAppServiceTest.listMySkills_combinesKeywordNamespaceAndStatusFilters
- The test filters results to only return skill with id=1, so mocks for id 2 and 3 were never called, causing UnnecessaryStubbingException
Frontend:
- Add missing mocks for useLocation, useSearch, useMyNamespaces, and useDebounce in my-skills.test.ts
- MySkillsPage component uses these hooks but the test setup didn't provide mocks, causing 'No QueryClient set' and 'No export' errors
All 4 frontend tests now pass locally.
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
---------
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Fix three blockers and one contract drift issue surfaced in code review:
1. API token policy: add skill:publish scope policy and authentication
policy for /api/cli/v1/skills/*/publish/validate. Without these the
AntPathMatcher pattern /publish would not cover /publish/validate,
so Bearer-token requests would be rejected by the scope filter.
2. Warnings semantics: dry-run now treats warnings as making valid=false.
The CLI publish flow uses confirmWarnings=false, so the real publish
rejects any warnings; dry-run must mirror that to avoid false positives.
3. Visibility parameter: validate endpoint now accepts the same
visibility multipart field as publish. The CLI forwards --visibility
so invalid values are caught at dry-run time rather than at publish.
4. Schema drift: resolvedSlug and resolvedVersion are nullable in
practice (returned as null when validation fails before resolution).
Updated schema.d.ts to reflect string | null instead of optional string.
Tests added:
- RouteSecurityPolicyRegistryTest: validate endpoint scope check
- CliDryRunValidateTest: custom + invalid visibility cases
- publish-dry-run.test.ts: --visibility forwarded to server
Add a validate-only endpoint (POST /api/cli/v1/skills/{namespace}/publish/validate)
that runs the full pre-publish validation chain without persisting anything.
This allows developers to check their package locally before actual publishing.
The validation covers:
- SKILL.md existence and frontmatter parsing (name, description required)
- File extension whitelist and size limits
- Credential leak scanning with line-number precision
- Slug generation and name conflict detection
CLI usage: `skillhub publish <path> --dry-run`
Closes#429
Resolved conflicts by keeping both sides:
- web/src/api/client.ts: preserve paginated listMembers(slug, {page,size})
and add delete(slug) from main.
- web/src/shared/hooks/use-namespace-queries.ts: keep useUpdateNamespace
and useTransferNamespaceOwnership from this branch, plus useDeleteNamespace
from main.
Add namespace edit dialog, transfer ownership dialog, and member list pagination. Update API layer to support pagination (PagedResponse), add useUpdateNamespace and useTransferNamespaceOwnership hooks with pagination-aware optimistic updates. Integrate edit button in NamespaceHeader and transfer button on members page. Add i18n keys for all new features (en + zh).
Introduce a dedicated `/space/$namespace/$slug/compare` page that compares
two published skill versions GitHub-style: left file list + right unified
diff. Backend exposes `GET /versions/compare` returning structured diff
(computed via java-diff-utils) with per-file hunks, binary placeholder,
and truncation flags. Frontend uses two version selectors scoped to
PUBLISHED versions, a file search box, active-file highlighting, and
whitespace-preserving unified view. E2E covers the publish + rerelease
+ approve round trip; controller/domain tests cover happy path and
same-version rejection.
Add an "Update" button to each skill card on the My Skills dashboard
page. Clicking it navigates to the Publish page with the skill's
namespace and visibility pre-selected, reducing manual steps when
re-publishing a skill package.
- Add visibility field to SkillSummaryResponse so the list API exposes
each skill's current visibility setting
- Add publish-prefill module to normalize and validate URL search params
- Wire TanStack Router validateSearch on the publish route
- Add E2E tests covering the prefill flow and invalid-param fallback
- Remove duplicate useSubmitForReview/useConfirmPublish in hooks and client
- Restore resolveVersionStatusLabel (version-status-badge not in this branch)
- Fix missing Card closing tag in versions tab