Skip to content

feat(ops): install and repair managed local Server runtimes - #2203

Open
knqiufan wants to merge 2 commits into
oceanbase:masterfrom
knqiufan:codex/managed-runtime-installer-2115
Open

knqiufan wants to merge 2 commits into
oceanbase:masterfrom
knqiufan:codex/managed-runtime-installer-2115

Conversation

@knqiufan

@knqiufan knqiufan commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Which issue or RFC does this PR close?

Refs #2115 (10-A and 10-B), #2114, and RFC #1733.

Builds on the independent Ops boundary merged in #2190. This PR contains the installer-specific changes and does not close the end-to-end acceptance issue.

Rationale for this change

The independent Ops tool can manage a prepared Server, but cannot yet acquire or repair its runtime. This change adds fixed-resource installation and same-version repair through the existing approval, operation journal, cancellation, and native service boundaries. A damaged Server interpreter is not needed to run the repair.

What changes are included in this PR?

  • A protected, SHA-256-pinned producer catalog selects the complete Python/Server resource for the host platform and architecture. Bounded HTTPS downloads, verified cache reuse, complete archive/file manifests, safe extraction, and owned temporary-file cleanup reject untrusted or damaged resources.
  • Managed install and repair plans bind resource, configuration, database, service, destination, and space observations. Apply revalidates the plan; checkpoints reuse the existing operation/attempt journal and locks. Recovery preserves ownership across interruption and later stop operations, and cannot recreate an explicitly removed registration.
  • First registration can resume after a transient port conflict or interruption before native changes. Protected removal identities distinguish incomplete registration from a later explicit CLI/Ops removal; legacy dispatch records remain readable with conservative recovery.
  • Corrupt owned staging is verified, discarded and reacquired on resume or a new approved install. Replaced directories, links and unsafe ownership are preserved and rejected.
  • New configuration uses loopback, SQLite, Dashboard enabled, local access without a token, and no models. Existing configuration revisions, installation identity, database content, and artifact history survive program repair.
  • Shared runtime repair holds the exact owned service stopped during publication and verification. Managed Python starts with -I -B; old service definitions retain their serialization and startup behavior.
  • Bilingual operator documentation and the RFC 1455 default-authentication clarification describe the implemented policy and remaining acceptance gaps. Desktop IPC and UI are outside this change.

Are there any user-facing changes?

The trusted launcher can select managed mode with --catalog-file and --catalog-sha256; the existing prepared-runtime --target-file mode remains available. Requests cannot select arbitrary download URLs, executable paths, or versions. Results report the actual endpoint and installation/resource identities without claiming connection compatibility or a default Scope.

Current limitations:

  • There is no complete target-version, read-only whole-database compatibility check. Existing databases remain unknown and managed startup is blocked, including normal start/restart after the first initialization and configuration changes that would actually restart an active service. Same-version repair can finish while stopped; it does not authorize database use. This is not yet a complete daily restart workflow.
  • Formal qualified producer resources, signing/notarization, and clean standard-user Windows/macOS acceptance depend on build(desktop): publish Desktop and local runtime installation artifacts #2112 and the platform distribution work. No production catalog or download URL is invented here.
  • Windows destination and staging paths are checked before download. A development resource whose dependency paths exceed the ordinary Windows path budget is rejected; a short-directory smoke test is not proof that the default production layout is qualified.
  • Older CLI versions that reject the new isolated_runtime definition field cannot manage these new registrations; use the matching independent Ops tool.

How was this change tested?

Local validation used Windows and Python 3.12.13.

  • uv run --no-sync python -m pytest -n 4 --basetemp D:/t/pr2203-installer-tests tests/test_ops_installer.py tests/test_ops_resources.py tests/test_ops_local_policy.py tests/test_service_runtime_repair.py tests/e2e/test_managed_runtime_repair.py -q --tb=short: 89 passed, 1 skipped. This includes public Memory revision/history retention through same-version resource repair.
  • All nine focused recovery cases passed: first-registration port conflict and dispatch interruption, corrupt staging through resume and a new install operation, replaced staging preservation, and four explicit-removal recovery scenarios. The four primary registration/staging regressions failed before the fixes. These checks use real protected files and a real loopback listener with a simulated service manager.
  • SKIP=ty-check UV_NO_SYNC=1 uv run --no-sync prek run -a passed.
  • Whole-repository ty check --python-platform linux and affected-source/test Windows type checks passed. The default whole-repository Windows type check still reports existing POSIX-only attributes in unrelated code.
  • uv lock --locked --offline and uv lock --locked --offline --directory e2e/bub passed.
  • The dependency's native-command suite passed all 22 cases, including real native-child survival through the simulated systemd path.
  • Actual native service registration, production HTTPS acquisition, qualified release resources, signing/notarization, clean-machine and login/reboot acceptance remain platform/release work. Local file/process tests do not establish those results.
  • Current remote checks are available in the PR checks.

AI usage statement

Implemented and tested with OpenAI Codex. Existing review findings guided the registration and staging recovery fixes. Validation distinguishes real protected-file, socket and process behavior from simulated native service managers.

@hidb4ai hidb4ai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two recovery failures remain in the managed installer. The inline comments describe the reproductions and required recovery behavior. These probes used real protected files and a simulated service manager; the port-conflict case also used a real loopback listener.

Comment thread src/powercontext/ops/installer.py Outdated
facts = previous["plan"].get("installation") or {}
if (
facts.get("registration_present") is True
or state.get("stage") in {"registration_dispatching", "complete"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep first-time registration failures recoverable

registration_dispatching is persisted before controller.install(), so it does not prove that registration ever existed. I reproduced a temporary port conflict during installation: the controller rejected it before any native mutation, but after releasing the listener, install preflight and resume still returned registration_missing. Interrupting immediately after the dispatching checkpoint produces the same result. Please distinguish incomplete first registration from explicit removal so a transient failure does not leave installation without a supported recovery path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复核 head 37036d1f(fix(ops): recover incomplete managed installations):本条 P2 已闭合。

  • ManagedInstaller._pending 现在按「是否有过真实注册证据」而不是「是否写过派发检查点」判定:只有 registration_present is True、stage == complete、components.registration == installed、或本 attempt 计划里的 registration_removal 与当前移除身份不一致(即之后发生明确移除)时才 registration_missing;旧记录缺该键时仍按 registration_dispatching / native_command 兜底要求人工检查,不会被自动重放。
  • 当前移除身份来自 ServiceController.registration_removal_path(受保护文件,format_version/UUID 校验失败即 ServiceError),并进入 facts["registration_removal"];protocol._validate_installation_plan 放行该键并用 identifier() 校验(_IDENTIFIER 允许连字符,UUID 可通过)。
  • exact head 树跑 tests/test_ops_installer.py:29 passed(452 s)。变异验证:删掉 installer.py 中 "registration_removal": snapshot["registration_removal"], 一行后,test_first_registration_failure_can_resume 的两个参数化用例同时失败(rejected 而非 succeeded)⇒ 新用例确实保护了这次修改。

一点相邻事实,不作要求:registration_removal 记录损坏时 _registration_removal() 抛 ServiceError,而 snapshot() 会把它带进所有 Ops 命令的前置事实,等于让全部操作失败;这与「旧记录仍可读」的文档措辞不同,若预期是仅阻断依赖注册判定的操作,需要再确认。

Comment thread src/powercontext/ops/installer.py Outdated
state["prepared"] = {"directory": str(result.directory), "identity": result.identity}
checkpoint("prepared")
else:
self._verify_prepared(state["prepared"], cancelled=resource_cancelled)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Allow reacquiring corrupt owned staging resources

Cancel at the prepared checkpoint, corrupt a regular staged file, then resume. Verification returns resource_corrupt on every attempt because the existing directory is always reused. A fresh install operation inherits the same corrupt staging state and fails too, while repair returns installation_missing. Please provide a supported way to prepare the resource again after confirming ownership of the damaged staging tree; rejecting corrupt bytes should not leave installation permanently dependent on that tree.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复核 head 37036d1f:本条 P2 已闭合。

  • 现在 resource_state != "verified" 且 state["prepared"] 指向的目录仍存在时,先 _verify_prepared;只有 resource_corrupt 才进入重建,其余错误照旧抛出。重建前先核三类归属:路径必须是绝对路径、resolve().parent 必须等于 programs/staging 的 resolve 结果、validate_owned_tree(path) 必须等于记录里的 identity;任一不符即 OpsError("resource_identity_changed") 并保留现场,之后才 rmtree 并把 state["prepared"] 置空重新准备。
  • 覆盖用例:test_corrupt_owned_staging_can_be_prepared_again[True/False](resume 与新 install 两条路径)与反向的 test_replaced_staging_is_preserved_and_not_reacquired(被替换的目录含 user-owned 文件时不清理、返回 resource_identity_changed)。
  • exact head 树跑 tests/test_ops_installer.py:29 passed(452 s)。变异验证:把 if prepared is not None and ( 改成 if False and ((等价于旧行为:不复检、直接复用)后,test_corrupt_owned_staging_can_be_prepared_again 两个参数化用例同时失败(failed 而非 succeeded)⇒ 两条路径都有回归保护。

一点相邻事实,不作要求:新增的 shutil.rmtree(path) 若因只读文件或占用而抛 OSError(Windows 上较常见),会被 ops/__main__.py 的 except (OSError, ValueError) 归为 invalid_input_or_binding,与真实原因不符;如果希望恢复路径给出可判断的错误码,可在此处收敛成独立的 Ops 错误。

@AlexStocks AlexStocks added the 1.4.1 v1.4.1 release tracking label Oct 24, 2026
@knqiufan
knqiufan force-pushed the codex/managed-runtime-installer-2115 branch from 29f5e65 to 09614b0 Compare October 24, 2026 11:54
@hidb4ai
hidb4ai marked this pull request as ready for review October 25, 2026 01:48
@hidb4ai hidb4ai added 1.5.0 v1.5.0 release tracking and removed 1.4.1 v1.4.1 release tracking labels Oct 25, 2026
@knqiufan
knqiufan force-pushed the codex/managed-runtime-installer-2115 branch from 09614b0 to 37036d1 Compare October 25, 2026 09:20

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.5.0 v1.5.0 release tracking

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants