Skip to content

Commit f22879d

Browse files
authored
fix(update): qualify activation from an immutable source commit (#4460)
`loopx update check --ref <sha>` reported activation_qualification_required trusted installed-versus-target source lineage is unavailable for an install that the same pinned SHA had just produced. The qualification read `freshness_source_git_commit` from `loopx doctor`, which only branch refs populate, so a full-commit install could never prove itself active even though the installer had genuinely installed that commit. A full commit SHA is its own commit identity, so the qualification now resolves the selected immutable ref to its own commit, compares it with the installed manifest source commit, and reports `runtime_active` on a match. A mismatch stays `activation_qualification_required` with an explicit "installed source commit does not match the selected immutable source commit" reason instead of the generic lineage message. Move the qualification into `loopx/activation_qualification.py` while changing it: `loopx/self_update.py` was at 1485 lines with this fix applied against the 1500-line maintainability ratchet, and this module owns the one decision the change touches. `loopx/self_update.py` returns to 1399 lines and keeps the schema constant and the not-applicable stub through an explicit import; `loopx/semantics/inventory_v0.json` regenerates to `source_files: 1177`. Disclosure: pinned-commit installs now qualify as `runtime_active` where they previously failed closed; branch-ref behavior is unchanged. Docs in `docs/product/release-readiness.md` and `docs/guides/installing-loopx.md` state the new receipt, and `examples/loopx-update-smoke.py` covers the matching and mismatching immutable cases. Validation: 65 passed / 1 skipped across `tests/test_self_update_runtime_activation.py`, `tests/test_archive_installer_commit_response.py`, `tests/test_doctor_install_freshness.py`, and `tests/canary/test_maintainability_ratchet.py`; `loopx canary premerge --from-git-diff --timeout-seconds 300` gate=passed with 19 executed checks, 0 failures, and a clean public/private boundary scan. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
1 parent 2e310d7 commit f22879d

7 files changed

Lines changed: 245 additions & 92 deletions

File tree

‎docs/guides/installing-loopx.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ not necessarily active in an installed release. Archive maintainers may use
292292
the tagged package channel and wait for the corresponding release instead of
293293
trying to switch an installed distribution to `main`.
294294

295+
An install taken from a pinned full commit SHA needs no separate lineage
296+
lookup: `loopx update check --ref <40-hex-commit>` compares that commit with the
297+
installed manifest source commit and reports `runtime_active` when they match,
298+
so the readback you would use to confirm your own pinned install stays
299+
actionable.
300+
295301
For an installation owned by another Python package manager, `update plan`
296302
reports that owner and its command; LoopX fails closed instead of guessing a
297303
pip mutation. For a live source checkout, it reports the contributor installer

‎docs/product/release-readiness.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ commit with the trusted source lineage reported by `loopx doctor`:
124124
belongs to a different `repo/ref`; the runtime-active claim must fail closed
125125
until identity is refreshed.
126126

127+
A pinned full commit SHA is its own trusted target, so
128+
`loopx update check --ref <40-hex-commit>` qualifies against the installed
129+
manifest source commit without waiting for a branch lineage lookup. Installed
130+
and pinned commits match: the receipt is `runtime_active`. They differ: the
131+
receipt stays `activation_qualification_required` and names the installed
132+
commit difference instead of the generic lineage message.
133+
127134
Closing a PR monitor after latest-`main` validation is valid, but the closeout
128135
must not say the fix is active in the installed runtime unless this receipt is
129136
`runtime_active`. Publishing a release remains a separate maintainer action.

‎examples/loopx-update-smoke.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,52 @@ def test_check_degrades_when_source_version_is_unavailable() -> None:
427427
assert "could not be checked" in payload["recommended_action"], payload
428428

429429

430+
def immutable_fresh_doctor_payload(ref: str) -> dict[str, object]:
431+
payload = fake_fresh_doctor_payload()
432+
freshness = payload["install_freshness"]
433+
assert isinstance(freshness, dict)
434+
freshness["manifest_source_ref"] = ref
435+
freshness["freshness_source_label"] = f"example/loopx@{ref}"
436+
freshness["freshness_source_git_commit"] = None
437+
return payload
438+
439+
440+
def test_immutable_source_ref_qualifies_activation() -> None:
441+
pinned = INSTALLED_COMMIT
442+
with mock.patch(
443+
"loopx.self_update.urlopen",
444+
return_value=FakeVersionResponse(__version__),
445+
):
446+
activated = build_update_plan(
447+
repo="example/loopx",
448+
ref=pinned,
449+
check_only=True,
450+
doctor_payload=immutable_fresh_doctor_payload(pinned),
451+
)
452+
qualification = activated["runtime_activation_qualification"]
453+
assert qualification["decision"] == "runtime_active", activated
454+
assert qualification["runtime_active"] is True, activated
455+
assert qualification["target_source_commit"] == pinned, activated
456+
assert qualification["revision_relation"] == "same", activated
457+
assert "no update needed" in activated["recommended_action"], activated
458+
459+
other = TARGET_COMMIT
460+
unproven = build_update_plan(
461+
repo="example/loopx",
462+
ref=other,
463+
check_only=True,
464+
doctor_payload=immutable_fresh_doctor_payload(other),
465+
)
466+
mismatch = unproven["runtime_activation_qualification"]
467+
assert mismatch["decision"] == "activation_qualification_required", unproven
468+
assert mismatch["target_source_commit"] == other, unproven
469+
assert "does not match the selected immutable source commit" in mismatch["reason"], unproven
470+
assert mismatch["successor"] == {
471+
"required": True,
472+
"kind": "activation_qualification",
473+
}, unproven
474+
475+
430476
def test_rollback_previous_executes_with_temp_home() -> None:
431477
with TemporaryDirectory() as tmpdir:
432478
home = Path(tmpdir)
@@ -545,6 +591,7 @@ def main() -> int:
545591
test_fresh_check_is_noop_recommendation()
546592
test_check_compares_selected_source_version()
547593
test_check_degrades_when_source_version_is_unavailable()
594+
test_immutable_source_ref_qualifies_activation()
548595
test_rollback_previous_executes_with_temp_home()
549596
test_rollback_restores_previous_when_doctor_fails()
550597
test_cli_check()

‎loopx/activation_qualification.py‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""Qualify whether the installed runtime proves a selected update source is active.
2+
3+
``loopx update check`` cannot call a release active just because a newer tag or
4+
branch exists: the installed archive has to carry source lineage that proves the
5+
selected update source reached the runtime. This module owns that decision so the
6+
question keeps one home instead of growing inside the self-update command surface.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import re
12+
from typing import Any
13+
14+
15+
RUNTIME_ACTIVATION_QUALIFICATION_SCHEMA_VERSION = (
16+
"loopx_runtime_activation_qualification_v0"
17+
)
18+
_FULL_COMMIT_PATTERN = re.compile(r"[0-9a-fA-F]{40}")
19+
20+
21+
def _immutable_source_commit(source: dict[str, Any]) -> str | None:
22+
"""Return the selected source commit when the ref is an immutable full SHA.
23+
24+
A full commit SHA is its own commit identity: ``scripts/install.sh`` installs
25+
it without a branch-resolution service, so the qualification can compare it
26+
against the installed manifest instead of reporting that the trusted target
27+
source lineage is unavailable.
28+
"""
29+
30+
ref = str(source.get("ref") or "")
31+
return ref.lower() if _FULL_COMMIT_PATTERN.fullmatch(ref) else None
32+
33+
34+
def runtime_activation_qualification(
35+
*,
36+
install_freshness: dict[str, Any],
37+
source: dict[str, Any],
38+
) -> dict[str, Any]:
39+
"""Return the typed activation qualification for an archive-snapshot install."""
40+
41+
installed_commit = install_freshness.get("manifest_source_git_commit")
42+
target_commit = install_freshness.get("freshness_source_git_commit")
43+
revision_relation = install_freshness.get("manifest_source_freshness_relation")
44+
selected_commit = _immutable_source_commit(source)
45+
if selected_commit and not target_commit:
46+
# An immutable ref names its own commit, so the qualification resolves it
47+
# the same way the installer does instead of reporting that the
48+
# installed-versus-target lineage is unavailable.
49+
target_commit = selected_commit
50+
if (
51+
isinstance(installed_commit, str)
52+
and installed_commit.lower() == selected_commit
53+
):
54+
revision_relation = "same"
55+
qualified_repo = install_freshness.get("manifest_source_repo")
56+
qualified_ref = install_freshness.get("manifest_source_ref")
57+
selected_repo = source.get("repo")
58+
selected_ref = source.get("ref")
59+
package_matches_runtime = install_freshness.get(
60+
"manifest_package_version_matches_runtime"
61+
)
62+
requires_upgrade = install_freshness.get("requires_upgrade")
63+
has_commit_pair = all(
64+
isinstance(commit, str) and bool(commit)
65+
for commit in (installed_commit, target_commit)
66+
)
67+
source_identity_matches = all(
68+
isinstance(value, str) and bool(value)
69+
for value in (qualified_repo, qualified_ref, selected_repo, selected_ref)
70+
) and (
71+
str(qualified_repo).removesuffix(".git").lower()
72+
== str(selected_repo).removesuffix(".git").lower()
73+
and str(qualified_ref).removeprefix("refs/heads/")
74+
== str(selected_ref).removeprefix("refs/heads/")
75+
)
76+
77+
if package_matches_runtime is False:
78+
decision = "release_or_install_successor_required"
79+
runtime_active: bool | None = False
80+
successor_kind = "release_or_install"
81+
reason = "release manifest package version does not match the active runtime"
82+
elif not source_identity_matches:
83+
decision = "activation_qualification_required"
84+
runtime_active = None
85+
successor_kind = "activation_qualification"
86+
reason = "trusted source lineage does not identify the selected update source"
87+
elif has_commit_pair and (
88+
installed_commit == target_commit or revision_relation == "installed_ahead"
89+
):
90+
decision = "runtime_active"
91+
runtime_active = True
92+
successor_kind = None
93+
reason = "installed source contains the trusted target source commit"
94+
elif has_commit_pair and revision_relation in {"installed_behind", "diverged"}:
95+
decision = "release_or_install_successor_required"
96+
runtime_active = False
97+
successor_kind = "release_or_install"
98+
reason = "installed source does not contain the trusted target source commit"
99+
elif has_commit_pair and selected_commit:
100+
decision = "activation_qualification_required"
101+
runtime_active = None
102+
successor_kind = "activation_qualification"
103+
reason = (
104+
"installed source commit does not match the selected immutable source commit"
105+
)
106+
else:
107+
decision = "activation_qualification_required"
108+
runtime_active = None
109+
successor_kind = "activation_qualification"
110+
reason = "trusted installed-versus-target source lineage is unavailable"
111+
112+
return {
113+
"schema_version": RUNTIME_ACTIVATION_QUALIFICATION_SCHEMA_VERSION,
114+
"decision": decision,
115+
"runtime_active": runtime_active,
116+
"installed_release_id": install_freshness.get("release_id"),
117+
"installed_version": install_freshness.get("current_version"),
118+
"installed_source_commit": installed_commit,
119+
"target_source_label": install_freshness.get("freshness_source_label"),
120+
"target_source_commit": target_commit,
121+
"revision_relation": revision_relation,
122+
"qualified_source": {
123+
"repo": qualified_repo,
124+
"ref": qualified_ref,
125+
},
126+
"source_identity_matches": source_identity_matches,
127+
"package_version_matches_runtime": package_matches_runtime,
128+
"requires_upgrade": requires_upgrade,
129+
"selected_source": {
130+
"repo": selected_repo,
131+
"ref": selected_ref,
132+
},
133+
"successor": {
134+
"required": runtime_active is not True,
135+
"kind": successor_kind,
136+
},
137+
"reason": reason,
138+
}

‎loopx/self_update.py‎

Lines changed: 5 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
from urllib.parse import quote
1212
from urllib.request import Request, urlopen
1313

14+
from .activation_qualification import (
15+
RUNTIME_ACTIVATION_QUALIFICATION_SCHEMA_VERSION,
16+
runtime_activation_qualification,
17+
)
1418
from .doctor import collect_doctor
1519
from .install_contract import NO_CLONE_INSTALL_URL
1620
from .runtime_activation import restart_services_for_runtime_activation
@@ -22,9 +26,6 @@
2226
DEFAULT_UPDATE_REF = "stable"
2327
ROLLBACK_PREVIOUS_ALIAS = "previous"
2428
SOURCE_VERSION_CHECK_SCHEMA_VERSION = "loopx_source_version_check_v0"
25-
RUNTIME_ACTIVATION_QUALIFICATION_SCHEMA_VERSION = (
26-
"loopx_runtime_activation_qualification_v0"
27-
)
2829
SOURCE_VERSION_CHECK_TIMEOUT_SECONDS = 3
2930
SOURCE_VERSION_READ_LIMIT_BYTES = 64 * 1024
3031
PERSISTED_PYTHON_FILENAME = ".loopx-python"
@@ -214,93 +215,6 @@ def _source_version_check(source: dict[str, Any]) -> dict[str, Any]:
214215
}
215216

216217

217-
def _runtime_activation_qualification(
218-
*,
219-
install_freshness: dict[str, Any],
220-
source: dict[str, Any],
221-
) -> dict[str, Any]:
222-
installed_commit = install_freshness.get("manifest_source_git_commit")
223-
target_commit = install_freshness.get("freshness_source_git_commit")
224-
revision_relation = install_freshness.get("manifest_source_freshness_relation")
225-
qualified_repo = install_freshness.get("manifest_source_repo")
226-
qualified_ref = install_freshness.get("manifest_source_ref")
227-
selected_repo = source.get("repo")
228-
selected_ref = source.get("ref")
229-
package_matches_runtime = install_freshness.get(
230-
"manifest_package_version_matches_runtime"
231-
)
232-
requires_upgrade = install_freshness.get("requires_upgrade")
233-
has_commit_pair = all(
234-
isinstance(commit, str) and bool(commit)
235-
for commit in (installed_commit, target_commit)
236-
)
237-
source_identity_matches = all(
238-
isinstance(value, str) and bool(value)
239-
for value in (qualified_repo, qualified_ref, selected_repo, selected_ref)
240-
) and (
241-
str(qualified_repo).removesuffix(".git").lower()
242-
== str(selected_repo).removesuffix(".git").lower()
243-
and str(qualified_ref).removeprefix("refs/heads/")
244-
== str(selected_ref).removeprefix("refs/heads/")
245-
)
246-
247-
if package_matches_runtime is False:
248-
decision = "release_or_install_successor_required"
249-
runtime_active: bool | None = False
250-
successor_kind = "release_or_install"
251-
reason = "release manifest package version does not match the active runtime"
252-
elif not source_identity_matches:
253-
decision = "activation_qualification_required"
254-
runtime_active = None
255-
successor_kind = "activation_qualification"
256-
reason = "trusted source lineage does not identify the selected update source"
257-
elif has_commit_pair and (
258-
installed_commit == target_commit or revision_relation == "installed_ahead"
259-
):
260-
decision = "runtime_active"
261-
runtime_active = True
262-
successor_kind = None
263-
reason = "installed source contains the trusted target source commit"
264-
elif has_commit_pair and revision_relation in {"installed_behind", "diverged"}:
265-
decision = "release_or_install_successor_required"
266-
runtime_active = False
267-
successor_kind = "release_or_install"
268-
reason = "installed source does not contain the trusted target source commit"
269-
else:
270-
decision = "activation_qualification_required"
271-
runtime_active = None
272-
successor_kind = "activation_qualification"
273-
reason = "trusted installed-versus-target source lineage is unavailable"
274-
275-
return {
276-
"schema_version": RUNTIME_ACTIVATION_QUALIFICATION_SCHEMA_VERSION,
277-
"decision": decision,
278-
"runtime_active": runtime_active,
279-
"installed_release_id": install_freshness.get("release_id"),
280-
"installed_version": install_freshness.get("current_version"),
281-
"installed_source_commit": installed_commit,
282-
"target_source_label": install_freshness.get("freshness_source_label"),
283-
"target_source_commit": target_commit,
284-
"revision_relation": revision_relation,
285-
"qualified_source": {
286-
"repo": qualified_repo,
287-
"ref": qualified_ref,
288-
},
289-
"source_identity_matches": source_identity_matches,
290-
"package_version_matches_runtime": package_matches_runtime,
291-
"requires_upgrade": requires_upgrade,
292-
"selected_source": {
293-
"repo": selected_repo,
294-
"ref": selected_ref,
295-
},
296-
"successor": {
297-
"required": runtime_active is not True,
298-
"kind": successor_kind,
299-
},
300-
"reason": reason,
301-
}
302-
303-
304218
def _release_root_from_doctor(doctor_payload: dict[str, Any]) -> str | None:
305219
package = (
306220
doctor_payload.get("package")
@@ -651,7 +565,7 @@ def build_update_plan(
651565
else None
652566
)
653567
runtime_activation = (
654-
_runtime_activation_qualification(
568+
runtime_activation_qualification(
655569
install_freshness=install_freshness,
656570
source=source,
657571
)

‎loopx/semantics/inventory_v0.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@
902902
]
903903
},
904904
"summary": {
905-
"source_files": 1176,
905+
"source_files": 1177,
906906
"python_enums": 103,
907907
"python_closed_sets": 495,
908908
"python_literal_aliases": 8,

0 commit comments

Comments
 (0)