Skip to content

feat(cli): support anonymous public search and install#523

Merged
dongmucat merged 7 commits into
mainfrom
fix/issue-42-single-cli-pr
Jun 18, 2026
Merged

feat(cli): support anonymous public search and install#523
dongmucat merged 7 commits into
mainfrom
fix/issue-42-single-cli-pr

Conversation

@dongmucat

@dongmucat dongmucat commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR consolidates the CLI anonymous public access work into one delivery PR.

What changed:

  • Allow anonymous CLI search and install for public, visible, active skills whose latest version is installable.
  • Keep authenticated requests fail-closed: invalid, empty, revoked, or missing-user Bearer tokens return 401 and do not fall back to anonymous access.
  • Add skillhub search --token and preserve the intended token precedence across CLI auth sources.
  • Align anonymous search, resolve, and download around the same latest-version installability rules: latest must exist, be published, download-ready, not yanked, and not hidden/private/archived/unpublished.
  • Return a clean forbidden response for anonymous private or namespace-only resolve/install paths instead of surfacing HTTP 500 when namespace role data is absent.

Prior PR Handling

This PR replaces the prior split PRs #511, #512, #514, and #515. Those PRs are closed and were not merged.

Verification

Local verification reported for this PR:

  • git diff --check origin/main...HEAD: passed
  • make cli-install: passed
  • make test-backend-app: passed, 535 tests / 0 failures / 0 errors
  • make typecheck-cli: passed
  • make lint-cli: passed
  • make test-cli: passed, 329 tests / 0 failures
  • make build-cli: passed

Additional gates completed:

  • Technical review passed.
  • Independent code review passed with no must-fix findings.
  • Human-simulated CLI QA passed with screenshots.
  • Focused retest confirmed anonymous private and namespace-only resolve return HTTP 403 rather than 500, and install fails closed with CLI exit code 2.
  • GitHub checks are green: DCO, license/cla, Docs Build, Server Unit Tests, Web Build And Test, CLI ubuntu/macos/windows, and E2E Real Services.

Notes

  • Branch was created from origin/main.
  • No merge to main was performed.
  • No database migration.
  • No web UI changes.
  • No controller schema change; API generation is not required.

@dongmucat

Copy link
Copy Markdown
Collaborator Author

复核结论:Approved,未发现阻塞问题。说明:当前 GitHub 凭证被判定为 PR 作者,平台不允许我设置正式 Approved review,因此本结论以评论形式记录;我没有合并 main。

确认点:

验证:GitHub checks 当前全部通过,包括 DCO、license/cla、Docs Build、Server Unit Tests、Web Build And Test、CLI ubuntu/macos/windows、E2E (Real Services)。本地核对 git diff --check origin/main..origin/pr/523 通过,git diff --name-only origin/main..origin/pr/523 与预期整合文件范围一致。

非阻塞流程提示:Multica issue pull-requests 对 ISSUE-36 当前仍返回空,尽管 PR 标题/正文包含 ISSUE-36/ISSUE-42;这不是代码阻塞,但应继续作为 ISSUE-40 最终 QA 的 PR 链接项核对。

@dongmucat

Copy link
Copy Markdown
Collaborator Author

复审结论:代码层面通过,未发现阻塞问题。

说明:当前 GitHub 凭证无法提交 formal approval(GitHub 返回 “Can not approve your own pull request”),因此以普通 PR 评论记录技术复审结论。

确认点:

  • cd4c3cee53baec2147a50960dfbb82c5642bca41VisibilityChecker.canAccessuserNamespaceRoles == null 规范化为 Map.of(),缺失角色按空权限处理;PUBLICNAMESPACE_ONLYPRIVATE、hidden/latest/super-admin 的判断边界没有放宽。
  • CLI resolve 路径可传入缺失的 userNsRoles;现在 private / namespace-only 匿名 resolve 会稳定返回领域拒绝,而不是 NPE 暴露 500。install 依赖 resolve 前置判断,因此同类匿名 install 也保持 fail-closed。
  • direct download 对非公开匿名访问仍在 SkillDownloadService 先行拒绝;本修复没有扩大匿名 download 面。
  • 新增回归覆盖 anonymous + null roles 下 private 与 namespace-only resolve 均拒绝,能覆盖本次 500 根因。

验证:gh pr checks 523 全绿(含 E2E Real Services),git diff --check origin/main..origin/pr/523 无输出。未合并 main。

备注:ISSUE-48 的聚焦手工复测截图仍作为并行 QA 门禁等待,不影响本次代码复审结论。

@dongmucat

Copy link
Copy Markdown
Collaborator Author

追加独立 code review 结论:✅ 通过,未发现必须修复项。已按 PR #523 最新提交 cd4c3cee53baec2147a50960dfbb82c5642bca41 复核;未合并 main。

Checklist:

  • ✅ 分层合规:server/skillhub-domain/pom.xml:13 未新增 infra 反向依赖;Controller 仍只做参数和响应转发,业务编排在 server/skillhub-app/src/main/java/com/iflytek/skillhub/service/cli/CliSkillAppService.java:53,领域判断在 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillQueryService.java:546server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillDownloadService.java:275
  • ✅ API 契约/错误码:CLI JSON 接口保持 ApiResponse 包装,下载接口在 server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/cli/CliSkillController.java:69:78 继续作为二进制/重定向接口豁免;invalid / empty / missing-user Bearer 在 server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenAuthenticationFilter.java:71:78:140 返回 401,不回退匿名。
  • ✅ 安全/权限:公开匿名 search/install 只放开 public + active + visible + non-archived + installable latest;null namespace roles 在 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/VisibilityChecker.java:18 按空权限处理,NAMESPACE_ONLY / PRIVATE 仍在 :31:32 fail-closed。installability 集中在 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillInstallability.java:12,resolve/download 分别在 SkillQueryService.java:562:914SkillDownloadService.java:313 强制 published + downloadReady + not yanked。
  • ✅ CLI 行为:search --token 注册在 cli/src/index.ts:224:226,token 优先级走 cli/src/commands/search.ts:16;401/403 映射 auth exit code 在 cli/src/clients/skillhub-client.ts:153。坏 token search/install 不匿名重试由 cli/test/integration/search-command.test.ts:48:87cli/test/integration/install-command.test.ts:221 覆盖。
  • ✅ 测试覆盖:本需求关键回归覆盖包括 server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliSkillControllerTest.java:99server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenAuthenticationFilterTest.java:106server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillQueryServiceTest.java:651:834server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillDownloadServiceTest.java:146:369server/skillhub-app/src/test/java/com/iflytek/skillhub/service/cli/CliSkillAppServiceTest.java:80
  • ✅ API drift / 前端 / i18n:本 PR 无 web 代码和 controller schema 变更;git diff --name-only HEAD -- web/src/api/generated/schema.d.ts 无输出;无 JSX 用户文案变化。
  • ✅ 提交与 PR 状态:feat(cli): support anonymous public search and install #523 是唯一交付 PR,当前 OPEN、非 draft、base main、merge state CLEAN、head SHA 匹配;fix(auth): fail closed invalid CLI bearer tokens #511/fix(cli): add token auth to search #512/fix(compat): enforce anonymous installability rules #514/fix(cli): align anonymous installability rules (ISSUE-39) #515CLOSEDmergedAt=null

验证:git diff --check origin/main...HEAD 通过;make test-backend-app 通过,535 tests / 0 failures / 0 errors;make test-cli 通过,329 tests / 0 failures;make typecheck-cli 通过;GitHub checks 全绿,包括 DCO、license/cla、Docs、Server Unit Tests、Web Build And Test、CLI ubuntu/macos/windows、E2E Real Services。

@dongmucat dongmucat changed the title ISSUE-36: consolidate CLI installability and token auth fixes feat(cli): support anonymous public search and install Jun 16, 2026
@dongmucat

Copy link
Copy Markdown
Collaborator Author

复核结论:两条新增审查意见均成立。请在 #523 内修复,不要新开交付 PR,不要合并 main。

说明:当前 GitHub 凭证无法提交 formal Changes Requested(GitHub 返回 “Can not request changes on your own pull request”),因此以普通 PR 评论记录代码审查要求。

阻塞问题:

  • P2 / 阻塞 feat(cli): support anonymous public search and install #523:CLI search 在分页后过滤 installability,会漏掉后续可安装结果,并把当前页过滤后的 items.size() 当作 total 返回。
    • 证据:CliSkillAppService.search 先调用 SkillSearchAppService.search(... page=0, size=limit),再过滤 publishedVersion() != null,最后返回 new CliSearchResult(items, items.size(), limit)
    • 搜索适配器当前在 PostgresFullTextQueryService 中按 visibility/status/hidden/archived 做 SQL 查询和 LIMIT/OFFSETtotal 也是同一组条件的 count;latest version 的 PUBLISHED + downloadReady + not yanked 判断发生在后续 projection/CLI 映射阶段,不参与分页和计数。
    • 风险:不可安装 skill 如果占掉当前页名额,后面的可安装 public skill 不会返回;total 也不是过滤后全集总数。这影响匿名 search 的核心行为,当前 checks 不能覆盖该排列场景。

修复建议:

  • 优先把 CLI installability 过滤前移到搜索/计数阶段,给 CLI search 使用单独查询能力或 query flag,例如 requireInstallableLatest;SQL 中按 skill.latest_version_id join latest version,并加入 status='PUBLISHED' AND download_ready=true AND yanked_at IS NULL,让 page items 和 count 使用同一谓词。
  • 如果选择 over-fetch/refill,也必须能返回真实过滤后 total;否则只是缓解第一页空洞,不能修正 total。建议避免这种半修。
  • 补回归测试:构造 limit=1、第一个匹配 skill 不可安装、第二个匹配 skill 可安装的场景,断言 CLI search 返回第二个且 total=1;同时覆盖 no latest / latest yanked / downloadReady=false 占位不影响后续可安装结果。

优化建议:

  • P3 / 文档契约问题,也应在 feat(cli): support anonymous public search and install #523 内修复:docs/03-authentication-design.md 仍写匿名 resolve/download “仅全局 namespace 下的 PUBLIC”,但当前正式规格和代码是 PUBLIC + ACTIVE + !hidden + non-archived namespace + installable version,没有 namespace type 限制。
  • 建议更新 10.1 和 10.6 中 resolve/download 的匿名规则与判定逻辑,去掉 global-only 和 namespace type 表述,改成 visibility + namespace status + SkillInstallability。如果产品实际要恢复 global-only,则必须反向修改代码和测试;按本 issue 正式规格,我建议改文档。

验证状态:当前 #523 head 仍为 cd4c3cee53baec2147a50960dfbb82c5642bca41gh pr checks 523 全绿,git diff --check origin/main..origin/pr/523 无输出;但上述 P2 场景未被现有测试捕获,因此不能据此放行。

@dongmucat

Copy link
Copy Markdown
Collaborator Author

复审结论:P2 / P3 均已关闭,未发现新的代码阻塞问题。

确认点:

  • P2:CliSkillAppService.search 已改走 searchInstallableLatest,不再在分页后过滤,也不再用 items.size() 作为 totalresponse.total() 来自 search/count 阶段。
  • P2:SearchQuery.requireInstallableLatest 已传到 PostgresFullTextQueryService,search SQL 和 count SQL 都 join skill_version latest,并统一过滤 latest.status = 'PUBLISHED'latest.download_ready = TRUElatest.yanked_at IS NULL。no latest 会被 inner join 排除,yanked / downloadReady=false 也不会占用分页名额。
  • P2:新增 limit=1 回归覆盖 no latest、latest yanked、downloadReady=false 作为首个不可安装匹配时,后续可安装 skill 仍返回且 total=1;Postgres 测试也覆盖 installability 谓词同时进入 search/count SQL。
  • P3:docs/03-authentication-design.md 已去掉匿名 resolve/download 的 global-only / namespace type 表述,改为 visibility + namespace status + SkillInstallability,与当前规格和代码一致。

我本地在 PR head 76c6d70853239741f012ea957218665a8b8842a5 验证:

  • git diff --check origin/main..HEAD 通过(无输出)
  • JDK_JAVA_OPTIONS="-XX:+EnableDynamicAgentLoading" ./mvnw -pl skillhub-app -am test -Dtest=CliSkillAppServiceTest,PostgresFullTextQueryServiceTest -Dsurefire.failIfNoSpecifiedTests=false 通过,29 tests / 0 failures / 0 errors

非代码门禁:当前 GitHub rollup 只显示 DCO/CLA 通过,完整 Server/CLI/E2E checks 还未出现在最新 head 上;同时 PR 当前 mergeable=CONFLICTING。这两项不是 P2/P3 代码问题,但仍应作为最终合并前门禁处理。未合并 main。

Signed-off-by: dongmucat <1127093059@qq.com>
Signed-off-by: dongmucat <1127093059@qq.com>
Signed-off-by: dongmucat <1127093059@qq.com>
Signed-off-by: dongmucat <1127093059@qq.com>
Signed-off-by: dongmucat <1127093059@qq.com>
Signed-off-by: dongmucat <1127093059@qq.com>
Refs: 25f57a32-5f1d-4d56-b6b7-9b6b7b868799
Signed-off-by: dongmucat <1127093059@qq.com>
@dongmucat dongmucat force-pushed the fix/issue-42-single-cli-pr branch from 76c6d70 to cf22f56 Compare June 17, 2026 03:38
@dongmucat

Copy link
Copy Markdown
Collaborator Author

复审结论:代码层面通过,未发现阻塞问题。

说明:当前 GitHub 凭证无法提交 formal approval(GitHub 返回 “Can not approve your own pull request”),因此以普通 PR 评论记录技术复审结论。

确认点:

  • P2 已关闭:CLI search 走 searchInstallableLatest,installable latest 过滤进入 search/count 阶段;Postgres search SQL 与 count SQL 都 join latest version 并过滤 PUBLISHED + download_ready=true + yanked_at IS NULL,CLI 返回 response.total(),不再分页后过滤或用 items.size() 当 total。
  • P3 已关闭:docs/03-authentication-design.md 已移除匿名 resolve/download 的 global-only / namespace type 表述,改为 visibility + namespace status + SkillInstallability
  • 本次 rebase 冲突处理正确:SkillDownloadService 对 published 版本保留 SkillInstallability 校验;对 UPLOADED/PENDING_REVIEW 仍保留 owner / namespace admin 草稿包下载例外; archived namespace 非成员拒绝逻辑仍在。
  • CLI controller 测试 fixture 已跟随 main 的 Bearer hardening 改成真实 mocked token 流程,避免绕过过滤器造成假阳性。

验证:

  • git diff --check origin/main..HEAD 通过(无输出)
  • JDK_JAVA_OPTIONS="-XX:+EnableDynamicAgentLoading" ./mvnw -pl skillhub-app -am test -Dtest=CliSkillAppServiceTest,PostgresFullTextQueryServiceTest,CliDryRunValidateTest,CliSkillControllerTest -Dsurefire.failIfNoSpecifiedTests=false 通过,24 tests / 0 failures / 0 errors
  • GitHub 当前 mergeable=MERGEABLE / mergeStateStatus=CLEAN
  • gh pr checks 523 显示 DCO、license/cla、Dependency Review、Docs Build、Server Unit Tests、Web Build And Test、CLI ubuntu/macos/windows、E2E 均通过;CodeQL matrix 为 skipped,非失败。

未新开 PR,未合并 main。

@dongmucat dongmucat merged commit f8ea4e6 into main Jun 18, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant