Skip to content

✨ feat(manager): add CPA update recommendation status - #818

Merged
seakee merged 2 commits into
v2from
feat/v2-update-01-cpa-status-recommendation
Sep 18, 2026
Merged

seakee merged 2 commits into
v2from
feat/v2-update-01-cpa-status-recommendation

Conversation

@seakee

@seakee seakee commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the Update01 Manager-owned, read-only CPA update recommendation/status surface.
Manager now combines fresh Runtime observation with a separately persisted official-stable discovery cache without executing any Runtime mutation.

Baseline: v2@92b955f70c77012af0e5d7e0558ea2199222c8f2

Head: f51ba65cf9ff947191aaa762bec79d1bd63aa831

Scope

  • Frontend panel
  • Manager Server
  • CPA panel mode
  • Full Docker mode
  • Native packages / release
  • Docs / Wiki
  • CI / build / tooling

Changes

  • Add an independent cpaupdate application service that owns official stable discovery, semver comparison, capability-aware recommendation policy, and read-only projection.
  • Add GET /usage-service/runtime/updates and POST /usage-service/runtime/updates/check behind existing panel/admin authorization with Cache-Control: no-store.
  • Persist only validated discovery cache under cpa_update_check_v1; Runtime identity, generation, active artifact, current version, state, and capabilities remain fresh observations.
  • Share one Embedded RuntimeClient instance between CPA update status and lifecycle reconciliation.
  • Expire successful discovery after the existing seven-hour self-update freshness window; older and future-dated success evidence is stale and non-actionable.
  • Resolve the current Manager-owned External CPA connection on every RuntimeClient.Status() call so initial setup and A-to-B connection changes take effect without a Manager restart.
  • Add internal/service/cpaupdate to the required Manager race gate and its workflow-integrity assertion.

Authority split

  • Manager owns discovery and recommendation of one exact stable targetVersion.
  • Supervisor continues to own independent official artifact resolution, digest verification, staging, activation, and rollback. This PR does not change Runtime Protocol or Supervisor code.

Trusted current-version authority

  • Embedded current version is accepted only from a model-valid ActiveGatewayArtifact with engine=cpa, exact canonical sha256:<64 lowercase hex> artifact identity, and matching CPAObservedVersion.
  • The response preserves active_artifact_id for the future Update02 execution fence.
  • A missing active artifact is unsupported; CPAObservedVersion alone is never promoted to Embedded update authority.

Fixed official source and bounds

  • Product-owned source: https://api.github.com/repos/router-for-me/CLIProxyAPI/releases/latest.
  • The source requires HTTPS, rejects redirects, uses a 10-second request bound and a 256 KiB response bound, accepts exactly one JSON value, and requires an explicit non-draft/non-prerelease canonical vMAJOR.MINOR.PATCH tag.
  • Only the normalized version is consumed. Asset URLs, digests, download URLs, paths, and commands are ignored.

Status matrix

Observation State Actionable
No successful discovery never_checked No
Stable target newer than trusted current update_available Only when discovery is fresh and both capabilities exist
Stable target equals current up_to_date No
Current is newer than official stable ahead_of_stable No downgrade
Trusted artifact version is not supported stable semver unknown_version No
Trusted active artifact is absent unsupported No
External CPA managed_externally No

Missing prepare_update or activate_update leaves a visible recommendation non-actionable; capability is read only from RuntimeObservedStatus.Capabilities.

External behavior

  • External CPA remains managed_externally and always non-actionable.
  • ExternalClient.Status() resolves the current connection source for every observation.
  • Regression coverage proves A-to-B connection changes and startup-unconfigured-to-later-configured transitions use the new connection without reconstructing the client or restarting Manager Server.

Persistence, freshness, and cooldown

  • cpa_update_check_v1 stores schema version, last attempt/success, safe last error, and validated target version only.
  • Invalid JSON, unknown schema/fields, invalid versions, inconsistent timestamps, and trailing JSON fail closed.
  • Explicit checks use a persisted 60-second cooldown. GET never invokes discovery.
  • A failed discovery may retain the last target for display, but marks it stale/non-actionable.
  • A successful cache is fresh through exactly seven hours. Evidence older than seven hours or dated in the future remains displayable but is stale and non-actionable.

User Impact

Authenticated Manager clients can inspect trusted CPA current identity/version, official latest stable version, recommendation state, and advertised update capabilities. No UI is added and no update is executed.

Compatibility / Runtime Notes

  • CPA panel mode: reports External CPA as managed_externally; no mutation support is inferred, and connection setup/switches are observed dynamically.
  • Manager Server mode: adds independent CPA discovery state and transitional authenticated Manager routes; existing CPAMP self-update routes and state remain unchanged.
  • Full Docker / native packages: Embedded status uses the same RuntimeClient authority as reconciliation.
  • GitHub Docker Runtime12/14/17/18/19 gate passed on exact head f51ba65cf9ff947191aaa762bec79d1bd63aa831 in PR Check run 35307419215.

Data / Security Notes

  • No secret, Runtime token, Supervisor URL, filesystem path, raw GitHub response, Runtime observation, or executable metadata is persisted or returned.
  • Discovery state is separate from manager_update_check_v1; no SQLite schema migration is required.
  • Runtime transport/auth remains inside existing RuntimeClient adapters.

Risk / Rollback

Risk level: Medium

Rollback notes: revert the two feature-branch commits. The additive cpa_update_check_v1 row is inert for older builds and does not alter existing CPAMP self-update state.

Verification

  • Type check
  • Lint
  • Tests
  • Build
  • Manual UI check
  • Docs/link check
  • Not applicable, docs-only

Commands / evidence:

cd apps/manager-server
go test ./...                                                       PASS
go test -race ./internal/service/cpaupdate ./internal/service/runtime \
  ./internal/repository/setting ./internal/service/bootstrap       PASS
go vet ./...                                                        PASS
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build ./cmd/cpa-manager-plus
                                                                    PASS

go test ./bin/ci/runtime-boundary/main.go ./bin/ci/runtime-boundary/main_test.go
go run ./bin/ci/runtime-boundary/main.go                             PASS
vitest run tests/prCheckClassifier.test.mjs tests/ciWorkflowIntegrity.test.mjs
                                                                    49 PASS
git diff --check                                                    PASS

GitHub PR Check run 35307419215 on exact head f51ba65cf9ff947191aaa762bec79d1bd63aa831:

  • Manager Server, required race gate, vet, and Windows build: PASS.
  • Frontend, Runtime Supervisor, Ingress, Native Control, Demo and Docs, Release Content: PASS.
  • Docker Build including Runtime12, Runtime14, Runtime17, Runtime18, and Runtime19: PASS.
  • Required checks: PASS.

Focused regression evidence:

  • GET discovery source requests: 0.
  • Two POST checks inside the cooldown: one upstream request and two fresh Runtime observations.
  • Seven-hour freshness boundary, expired cache, and future-dated success evidence are covered; stale recommendations remain non-actionable.
  • One External client observes connection A then B, and an initially unconfigured client observes a later configured connection, without reconstruction or Manager restart.
  • Fake RuntimeClient makes PrepareUpdate, ActivateUpdate, Start, Stop, and Restart panic; all read/check tests pass with zero mutations.
  • Existing internal/service/updatecheck tests pass unchanged as part of go test ./....

Screenshots / Recordings

N/A — backend-only Update01; no UI changes.

Docs

  • README / README_CN updated for user-visible capabilities
  • Matching docs manual and navigation updated
  • Demo fixtures, screenshots, and deep links reviewed
  • Release notes needed
  • Not needed — explanation included below

Docs decision: Update01 exposes a transitional authenticated Manager control surface for later Update03 UI work. It makes no Public API v2 claim and does not change current user documentation.

Scope exclusions

  • No PrepareUpdate or ActivateUpdate call.
  • No Start, Stop, or Restart update side effect.
  • No UI, automatic/background update, Update All, prerelease channel, Public API v2, Update02/Update03, or Phase2 work.
  • No Runtime Supervisor or Runtime Protocol change.
  • This PR claims only Update01 implementation complete, not Update Handoff complete.

Attribution check

Commit and PR content contain no AI attribution markers, Co-authored-by, generated-by footer, or unrelated attribution changes.

Related

N/A — implements the approved local task-v2-update-01-cpa-status-recommendation handoff plan.

Add fixed-source stable CPA discovery, fail-closed persisted recommendation state, and authenticated read/check endpoints. Share the Embedded RuntimeClient with lifecycle reconciliation while keeping all Runtime mutations outside Update01.
Expire cached CPA recommendations after the existing seven-hour freshness window and fail closed on future success timestamps. Resolve External CPA connections for every Runtime observation so setup and connection changes apply without restarting Manager Server. Add the CPA update service to the required race gate.
@seakee
seakee merged commit 4dbf6f2 into v2 Sep 18, 2026
30 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