Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
# Documentation
*.md
!deploy/DOCKER.md
!deploy/plugin-bundle/README.md
!THIRD_PARTY_NOTICES.md
docs/
# admin-compliance gate (LegalDocumentView.vue) build-time imports
Expand Down Expand Up @@ -51,7 +50,7 @@ coverage/
.env.*
!.env.example

# Local third-party packages and credentials do not enter the host image.
# 本地闭源插件目录可能包含发布签名私钥,绝不能进入 Docker 构建上下文。
/plugins/

# Local config
Expand Down
127 changes: 77 additions & 50 deletions .downstream/native-domains.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Ordinary account bulk editing remains HTTP 202; this change does not replace
background tasks with synchronous editing. Existing API-key reveal and
account-field protections remain.

## Startup and retained data
## Startup and stored data

Migrations run first. Migration 264 stops startup, changing nothing, on a
database whose `codexrip.codex-runtime` installation was never retired, whose
Expand All @@ -35,28 +35,42 @@ saved Codex runtime configuration has no hash recorded by v0.2.11-codexrip.6,
key. Starting one of those three releases once satisfies the first two
conditions.

Then, before native settings load and before the plugin manager starts, one
transaction retires the other first-party plugin installations a database still
holds, except `codexrip.image-tools`, which it does not read or change:

1. Validates the saved first-party capability scopes and decrypts configuration.
2. Imports the effective observability switches only when the native settings
key is absent. An existing key is never overwritten. The retired Cindy
provider installation is disabled without importing its switches.
3. Saves original installation/binding/bootstrap intent under
`deplugin_retired_plugins`, disables the installations and their bindings,
and marks their bootstrap records removed.

Missing settings may use deployment defaults. Database errors, malformed saved
settings and unknown/non-equivalent capability scopes stop startup; they never
silently enable features. An installation that is disabled as a whole, in a
domain without an equivalent native switch, requires an explicit migration
decision rather than automatic activation.

The retired installation rows remain and are listed read-only, with the
receipt, at `GET /admin/plugins/retired`; the `codexrip.codex-runtime` row holds
no saved configuration, package, manifest or bindings. The upstream plugin
manager cannot list, modify, delete or replace the retired first-party keys.
Migration 272 stops startup, changing nothing itself, in four cases:

1. One of the seven first-party plugin installations exists and no receipt of
the retirement, the `settings` row `deplugin_retired_plugins`, is stored:
that installation was never retired. Starting v0.2.13-codexrip.8 once
retires it.
2. One of them exists and the stored receipt is not completed or has no entry
for it. No release writes such a receipt and none repairs it:
v0.2.13-codexrip.8 exits at start on a receipt that is not completed and
retires nothing once a completed one is stored. Whether the configuration
saved in such an installation is still needed is decided by hand; 272
applies once the installations it names are deleted.
3. Another installation is in the state `updating`.
4. Two enabled bindings share a capability, platform and account type.

Upstream's schema admits neither 3 nor 4 and the plugin manager writes neither;
disabling or uninstalling the plugin in v0.2.13-codexrip.8 clears both.

Each migration commits on its own, so a database on which 272 has stopped
already has 270 and 271, which v0.2.13-codexrip.8 and every earlier release
lack. Such an image starts on that database only with Image Studio off, which
the deleted `image_tools_config` setting leaves to
`GATEWAY_IMAGE_STUDIO_ENABLED`: with that variable `true` it exits at start,
and its Image Studio job routes fail on the dropped tables. It names
`usage_billing_dedup.account_id` in every billing claim, so its usage billing
fails until the column is added again
(`ALTER TABLE usage_billing_dedup ADD COLUMN account_id BIGINT`). Migration 271
is recorded by then and does not run a second time: after returning to this
release, drop the column by hand
(`ALTER TABLE usage_billing_dedup DROP COLUMN IF EXISTS account_id`). Billing
works with the column present until that is done.

The plugin tables, the plugin manager and its admin interfaces are upstream's:
the manager lists and manages every installation row. Missing settings may use
deployment defaults. Database errors and malformed saved settings stop startup;
they never silently enable features.

## Release and rollback boundary

Expand All @@ -68,43 +82,56 @@ backups, canaries, model calls or automatic rollback.
deployer recognizes the old and native prompt/skill directory locations by the
same content hashes; missing or ambiguous trees and changed schema remain errors.

Migration 264 has no down path. An image change back to v0.2.11-codexrip.6, .7
or .8 after it starts only on a database that keeps a retired
`codexrip.codex-runtime` installation; without that row those images exit at
start. They do not read the stored compression switch, so request compression
follows `gateway.openai_codex_request_zstd`, and without `sub2api_plugin_state`
their outbound fingerprint view and quality-run endpoints fail while forwarding
continues. Images at or before v0.2.11-codexrip.5 also need
`sub2api_plugin_state` and `sub2api_plugin_leases` for their route code and
require a database dump taken before migration 264.
Migration 264 has no down path. Images at or before v0.2.11-codexrip.5 need
`sub2api_plugin_state` and `sub2api_plugin_leases`, which it drops, for their
route code.

Migration 270 has no down path either. It drops the three Image Studio tables
with the job history and the records of the stored image files and deletes the
`image_tools_config` setting; the files under `<data dir>/image-studio` are not
removed. An earlier image starts on such a database only with Image Studio off,
which the missing setting leaves to `GATEWAY_IMAGE_STUDIO_ENABLED`: with that
variable `true` it exits at start, and its Image Studio job routes fail on the
dropped tables.
removed.

Migration 271 has no down path. It drops `usage_billing_dedup.account_id`, which
the billing claim no longer writes and nothing reads. An earlier image names
that column in every billing claim, so its usage billing fails on such a
database until the column is added again
(`ALTER TABLE usage_billing_dedup ADD COLUMN account_id BIGINT`). Migration 271
is already recorded by then and does not run a second time: after returning to
this release or a later one, drop the column by hand
(`ALTER TABLE usage_billing_dedup DROP COLUMN IF EXISTS account_id`). Billing
works with the column present until that is done.

Returning to a plugin-based host is not an image change. That host needs schema
objects and plugin data the current database no longer holds, so it requires a
database dump that still has them. Such a legacy-host rollback has not been run
and is not an automatic failure path.
the billing claim no longer writes and nothing reads.

Migration 272 has no down path. It deletes the seven first-party plugin
installations (`codexrip.account-tools`, `codexrip.admin-observability`,
`codexrip.cindy-provider`, `codexrip.codex-runtime`, `codexrip.image-tools`,
`codexrip.model-policy` and `codexrip.prompt-skills`) with their saved
configuration, package bytes, manifests and capability bindings, and the
`settings` row that held the receipt of their retirement. It drops the schema
only those plugins used, so that `sub2api_plugin_installations` and
`sub2api_plugin_bindings` are again as upstream's migrations 229 and 230 define
them: the tables `sub2api_plugin_bootstrap` and `sub2api_plugin_updates`, the
columns `revision`, `runtime_generation`, `package_sha256` and `update_policy`,
the state `updating`, the trigger and function `sub2api_plugin_revision`, and
the limit of the unique index on enabled bindings to two capabilities. Other
installations, their bindings and the `admin_observability_config` setting are
not written.

A migration does not reach what the seven plugins stored outside the database:
their package files `<plugin dir>/packages/codexrip.*.s2plugin`, their unpacked
trees `<plugin dir>/installed/codexrip.*/` and any Redis key
`plugin:kv:v1:codexrip.*`, where `<plugin dir>` is `plugins.data_dir` or, when
that is not set, `<data dir>/plugins`. Nothing reads these once 272 has
applied; they are deleted by hand at the release that applies it. The files
and keys of other plugins in the same directory and under the same prefix are
in use and stay.

After migration 272 no earlier image starts on the database. Every earlier
release since v0.2.8-codexrip.5 retires first-party plugin installations at
start: finding no receipt, it selects
`sub2api_plugin_installations.runtime_generation`, the query fails on the
dropped column and the process exits with `initialize native features`. A
plugin-based host needs the dropped schema and the deleted plugin data as well.
Going back to an earlier release is therefore not an image change: it requires a
database dump taken before the migrations that release does not have. Such a
rollback has not been run and is not an automatic failure path.

## Verification

Normal required PR checks validate the native domain packages, account scope,
configuration/storage boundaries and frontend behavior. The release reuses that
native configuration and frontend behavior. The release reuses that
source validation. Health endpoints establish availability only.

## Console theme
Expand Down
6 changes: 3 additions & 3 deletions .github/scripts/test_native_domains.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ go -C backend test -p=1 -tags unit \
./internal/codexruntime/... \
-count=1

# Preserve configuration/storage boundaries without starting real IO.
go -C backend test -p=1 -tags unit ./internal/service ./internal/repository \
-run '^(TestCodexRuntimeSettingIsOnePlainRowOverTheDeployDefault|TestPrepareOpenAICodexWireRequest.*|TestNativeCodexErrorDiagnosticsProjectEveryStoredAttempt|TestNativePluginBoundaryProtectsRetiredRecordsAndAllowsThirdParties|TestNativeFeatureBootstrapPreservesEffectiveConfiguration|TestNativeSettingLoadFailureNeverReenablesSavedSwitches|TestTrafficObservation.*|TestAccountTrafficOutcomeRulesKeepCancellationAboveErrorsAndRequireWSTerminals|TestDecodeSwitchSettingsRejectsUnknownAndNonBooleanValues)$' \
# Preserve the native configuration contracts without starting real IO.
go -C backend test -p=1 -tags unit ./internal/service \
-run '^(TestCodexRuntimeSettingIsOnePlainRowOverTheDeployDefault|TestPrepareOpenAICodexWireRequest.*|TestNativeCodexErrorDiagnosticsProjectEveryStoredAttempt|TestNativeSettingLoadFailureNeverReenablesSavedSwitches|TestTrafficObservation.*|TestAccountTrafficOutcomeRulesKeepCancellationAboveErrorsAndRequireWSTerminals|TestDecodeSwitchSettingsRejectsUnknownAndNonBooleanValues)$' \
-count=1
6 changes: 3 additions & 3 deletions .github/workflows/downstream-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
python3 .github/scripts/test_release_preflight.py
- name: Agent protocol contracts
run: bash .github/scripts/test_agent_protocol.sh
- name: Native domain and retirement contracts
- name: Native domain contracts
run: bash .github/scripts/test_native_domains.sh
- name: Reasoning recovery contracts
run: bash .github/scripts/test_reasoning_recovery.sh
Expand Down Expand Up @@ -74,9 +74,9 @@ jobs:
- name: Account taxonomy migration integration test
working-directory: backend
run: go test -p=1 -tags integration ./internal/repository -run '^Test(AccountTaxonomy.*|DeleteAccountFolder.*)Integration$|^TestAPIKeyRepoSuite$/^TestReleaseAcceptanceKeyIsAdminListedReadOnlyAndAuthenticates$' -count=1
- name: Repository retirement and audit integration tests
- name: Repository audit integration tests
working-directory: backend
run: go test -p=1 -tags integration ./internal/repository -run '^Test(PGXBatchInsertAuditAndSystemLogs|Migration236.*|NativeFeatureRetirementIsAtomic)$' -count=1
run: go test -p=1 -tags integration ./internal/repository -run '^Test(PGXBatchInsertAuditAndSystemLogs|Migration236.*)$' -count=1
- name: Historical Cindy migration tests
working-directory: backend
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,5 @@ issues/

# Vitest / Vite cache at repo root
.vite/
# Installed packages and local third-party projects are not versioned.
# 本地闭源插件开发目录及构建产物不进入 Sub2API 仓库
/plugins/
7 changes: 3 additions & 4 deletions DOWNSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ This fork maintains the `codexrip` patch set over official Sub2API releases.

The five first-party domains run inside the host and use native Vue pages.
The official third-party plugin framework remains available. Domain settings,
startup data preservation and the legacy-host rollback boundary are described in
[native domains](.downstream/native-domains.md).
the stored data the migrations removed and the rollback boundary are described
in [native domains](.downstream/native-domains.md).

Account bulk operations remain HTTP 202 jobs with progress, cancellation and
failed-item retry. Both edit entries use the frozen selected IDs whenever there
Expand Down Expand Up @@ -60,8 +60,7 @@ The three `238_*` migrations retain separate filenames and checksums.
- New releases use immutable `vX.Y.Z-codexrip.N` tags on `main`.
- Images use the tag without `v`: `ghcr.io/htexplicit/sub2api:X.Y.Z-codexrip.N`.
- Downstream Release authenticates to GHCR, verifies source/build materials and
publishes the native host image digest and provenance. It reuses PR validation;
first-party package signing and separate plugin release assets are retired.
publishes the native host image digest and provenance. It reuses PR validation.
- Production Deploy resolves the fixed digest through the existing restricted SSH
updater. Ordinary updates use `operation=deploy-preserve`, preserving runtime
settings and resources, naturally draining requests, and rebuilding only Sub2API.
Expand Down
10 changes: 7 additions & 3 deletions backend/cmd/server/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,20 @@ func provideCleanup(
return func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

type cleanupStep struct {
name string
fn func() error
}

// 应用层清理步骤可并行执行,基础设施资源(Redis/Ent)最后按顺序关闭。
parallelSteps := []cleanupStep{
{"PluginManager", func() error {
if pluginManager != nil {
pluginManager.Stop()
}
return nil
}},
{"AccountJobRuntime", func() error {
if accountJobs != nil {
accountJobs.Stop()
Expand Down Expand Up @@ -469,9 +476,6 @@ func provideCleanup(
}

runParallel(parallelSteps)
if pluginManager != nil {
pluginManager.Stop()
}
runSequential(infraSteps)

// Check if context timed out
Expand Down
29 changes: 14 additions & 15 deletions backend/cmd/server/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading