Skip to content

feat(doctor): name the running artifact, its revision and its members digest (#729, slice 1) - #887

Merged
github-actions[bot] merged 2 commits into
mainfrom
fix/729-runtime-artifact-identity
Nov 6, 2026
Merged

github-actions[bot] merged 2 commits into
mainfrom
fix/729-runtime-artifact-identity

Conversation

@cdeust

@cdeust cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner

Summary

Refs #729 (tranche 1). Tranches restantes listées sur l'issue.

Cause racine. Rien dans Cortex ne dit quel artefact un processus exécute. Le plugin Codex fixe une roue installée par numéro de version ; le plugin Claude lance le code de son répertoire de plugin. L'audit de #729 a trouvé la v4.23.5 installée alors que main avait 42 membres mcp_server différents. Un correctif présent seulement sur main ne pouvait donc pas être attribué au runtime en cours, et le doctor ne permettait pas de trancher.

Correctif. Nouveau check optionnel Runtime artifact identity (mcp_server/doctor_artifact.py), exécuté par les deux listes du doctor (PostgreSQL et SQLite) :

  • roue installée : sa version, plus un sha256 sur les lignes mcp_server triées de son RECORD (chemin,sha256=…). Chaque membre est recalculé sur disque ; un fichier qui diffère du RECORD fait échouer ce check optionnel et le message dit comment réinstaller ;
  • arbre source ou installation editable : la révision git, les éventuels changements non commités sous mcp_server, et le même digest calculé sur ses fichiers. Le résultat est étiqueté « source evidence, not a distributed artifact », comme le demande le critère « keep main-only evidence separately labeled ».

Type of change

  • New feature (non-breaking change which adds functionality)

Test plan

  • pytest tests_py/test_doctor_artifact.py : 11 passed. Ils couvrent l'encodage RECORD (valeur connue), un digest de référence codé en dur, l'exclusion du bytecode, les lignes RECORD hors paquet ou sans hash, une roue intacte, une roue altérée, une roue installée ailleurs que l'arbre importé, un arbre sans git, l'argv git exact, et la présence du check dans les deux backends.
  • pytest tests_py/architecture tests_py/test_doctor*.py tests_py/scripts : 1915 passed, 18 skipped. tests_py/test_doctor*.py + tests_py/handlers/test_check_setup.py après la version finale : 119 passed, 3 skipped.
  • Porte de mutation ADR-1122 lancée en local (GITHUB_BASE_REF=main python scripts/ci_mutation_diff.py) : 2 file(s), 0 failed, 0 untested, aucun survivant dans les 8 fonctions de doctor_artifact.py ni dans _artifact_identity.
  • Reçu de bout en bout : roue construite depuis cet arbre (uv build --wheel), installée sans dépendances dans un venv isolé. Le check sur la roue donne installed hypermnesia-mcp 4.23.5, 732 mcp_server members, digest f8b4ad4531908bd9cab5612259503e14ef87af54de1eeade872e94935ee5c9fc. Le check sur le checkout donne source tree … 732 mcp_server files, digest f8b4ad45…c9fc : le même digest. Après ajout d'une ligne dans mcp_server/doctor_managed.py de la roue installée : False … 1 differ from its RECORD.
  • ruff check et ruff format --check (0.17.3) sur les fichiers modifiés : OK. python scripts/check_craftsmanship.py : OK.
  • Non vérifié : une installation réelle par uv tool install (chemin Codex) et un répertoire de plugin Claude installé. torch est refusé par le proxy ici, donc les tests ont tourné avec --no-install-package torch. Windows et macOS non testés en local (la CI les couvre).

Audit notes

  • Revue : routine autopilot uniquement, pas de revue humaine à ce stade.
  • ruff check / ruff format --check sur tout le dépôt signalent des écarts déjà présents sur main (scripts/ci_pipeline_source_contract.py, scripts/tests/test_no_main_comparison.py, tests_py/scripts/test_ci_review_required.py), hors de ce diff.

Coding-standards compliance

  • Layers : le nouveau module importe seulement mcp_server.shared.subprocess_safe et la stdlib, sur le modèle de doctor_managed.py.
  • 300 lignes par fichier et 40 par méthode respectées par les nouveaux fichiers (doctor.py était déjà au-dessus du plafond et dans la baseline : +8 lignes).
  • Chaque constante porte un # source:. Le timeout git reprend _WORKTREE_LIST_TIMEOUT_S.

Breaking changes

Aucun : un check optionnel de plus dans la sortie du doctor, qui ne change pas le code de sortie sauf s'il détecte une roue altérée (et même dans ce cas, c'est un avertissement : optional=True).

Reviewer checklist

  • CHANGELOG.md updated under the appropriate section.
  • Documentation updated (CHANGELOG ; aucune page docs ne liste les checks du doctor).
  • No secrets / credentials / PII in the diff.
  • CI passes on the latest commit.

Generated by Claude Code

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot : unit en échec sur 7aa2eec, run 42030886572, job 127256834134, image ubuntu-26.04. Correctif 3099e90 (fixup!).

Diagnostic. La porte de mutation ADR-1122 a laissé survivre x_wheel_members__mutmut_3 (or remplacé par and), que la même porte tuait en local sous Python 3.11. La CI tourne en 3.12. Or, depuis 3.12, Distribution.files omet les fichiers absents du disque : le test ne voyait donc plus les lignes RECORD hors paquet. Le même effet touche la production : un membre supprimé d'une roue installée aurait échappé à la vérification.

Correctif à la racine. wheel_members lit le RECORD tel qu'il est écrit (csv), sans passer par Distribution.files. Deux tests ajoutés : un membre supprimé est signalé, et une distribution sans RECORD n'a aucun membre.

Preuves locales.

  • Sous 3.12.3, wheel_members renvoie la ligne du paquet même absente du disque.
  • Porte de mutation (GITHUB_BASE_REF=main python scripts/ci_mutation_diff.py, en 3.11) : 2 file(s), 0 failed, 0 untested.
  • Roue construite depuis 3099e90 et installée dans un venv 3.12 : même digest que l'arbre source (42c8ebe5…9958). Après suppression de doctor_managed.py dans cette roue : False … 1 differ from its RECORD, first mcp_server/doctor_managed.py.

Non vérifié. La porte de mutation elle-même sous 3.12 : le venv 3.12 local n'a pas les dépendances, et torch est refusé par le proxy. Le reçu viendra du nouveau run unit.


Generated by Claude Code

… digest (#729, slice 1)

The doctor now reports which Cortex artifact the process imports mcp_server
from. An installed wheel: version, a sha256 over the mcp_server rows of its
RECORD, each member re-hashed on disk (a member that differs fails the
optional check). A source tree or editable install: its git revision, any
uncommitted mcp_server change, and the same digest over its files, labelled
as source evidence, not a distributed artifact.

Refs #729

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYrtpbmv4ABTvzFm6X7
@cdeust
cdeust force-pushed the fix/729-runtime-artifact-identity branch from 3099e90 to b894775 Compare November 5, 2026 21:53
@cdeust
cdeust marked this pull request as ready for review November 6, 2026 00:28
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T00:40:53.374352Z e37274e New commits
🔒 Security Review ✅ Completed 2026-11-06T00:43:16.227407Z e37274e New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 3m 57s —— View job


Revue de la PR #887 (revue 1 sur 3, commit b894775)

  • Lire le diff et son contexte (le fixup 3099e90 est déjà intégré à b894775)
  • Lancer les cinq revues : qualité, sécurité, performance, tests, documentation
  • Vérifier les constats et publier le verdict

La revue complète est publiée ici : #887 (comment)

Blocker (1). Dans mcp_server/doctor_artifact.py:105-106, un git status en échec (délai dépassé, code de sortie non nul, git absent) renvoie None. Le check le traite comme une absence de changements. Il annonce alors un arbre propre à <sha> avec ok=True, sans l'avoir vérifié. C'est un repli silencieux, ce que CLAUDE.md interdit. Le check voisin _worktree_locations (doctor.py:392-400) fait l'inverse et transforme un None de git en avertissement.

Notes (3).

  • Un OSError ou une ligne RECORD vide lève une exception qui interrompt tout le doctor, alors que le check est optionnel.
  • rglob ne tient pas compte de .gitignore : l'égalité entre le digest de la roue et celui de l'arbre, annoncée dans la docstring et le CHANGELOG, peut donc être fausse.
  • Le conseil _REINSTALL lance une installation sans --reinstall, qui ne remplace probablement pas les fichiers altérés.

Verdict: block
· Branche fix/729-runtime-artifact-identity

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b894775031

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mcp_server/doctor_artifact.py Outdated
Comment on lines +41 to +43
_REINSTALL = (
"Reinstall the runtime: `python plugins/hypermnesia-mcp-codex/scripts/"
"runtime.py setup` (Codex) or reinstall the plugin (Claude)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recommend a forced reinstall for an altered Codex runtime

When an installed Codex runtime has modified files but still has the pinned version, the recommended runtime.py setup command does not repair it. In plugins/hypermnesia-mcp-codex/scripts/runtime.py, the setup branch invokes uv tool install without reinstall flags and then checks only the installed version. An offline local-wheel reproduction confirmed that repeating this installation reports “already installed” and leaves the modified file untouched. uv tool install --help explicitly describes --reinstall as “Reinstall all packages, regardless of whether they're already installed.” Provide a repair command or setup mode that actually reinstalls the package; otherwise users following this warning remain on the altered runtime.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Confirmé : runtime.py setup lance uv tool install sans --reinstall, donc il ne remplace pas un runtime altéré qui a déjà la bonne version. Le commit qui suit (fixup!) fait recommander par le check la commande exacte uv tool install --reinstall --no-build "hypermnesia-mcp[postgresql,sqlite]==<version installée>", et le test vérifie ce texte mot pour mot.


Generated by Claude Code

Comment on lines +87 to +88
if not file.is_file() or record_hash(file.read_bytes()) != expected:
altered.append(path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report unreadable artifact members without aborting doctor

If an installed package member exists but is unreadable, file.read_bytes() raises PermissionError instead of returning an optional failed check. This affects even an unused module or resource that doctor otherwise never needs to open. Reproducing the check as an unprivileged user against an installation containing a mode-000 member confirms that the exception escapes artifact_identity. Because doctor._run_full_check() evaluates all checks in a list comprehension before printing the report and has no exception handler, this optional probe aborts the entire diagnostic command and suppresses every other check's results. Catch filesystem read errors and report the affected member as an optional warning; the source-tree hashing path needs equivalent handling.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Confirmé : un PermissionError sortait de artifact_identity et faisait échouer tout _run_full_check. Le commit qui suit rattrape OSError et ValueError (une ligne RECORD malformée) autour du check, sur la roue comme sur l'arbre source. Le résultat devient un check optionnel en échec, cannot read the running artifact: <cause>, qui nomme le fichier ; les autres checks continuent de tourner. Les lignes RECORD vides sont ignorées. Trois tests couvrent ces cas : fichier illisible, RECORD malformé, ligne vide.


Generated by Claude Code

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Revue Claude de #887 (revue 1 sur 3, commit b894775)

J'ai lancé cinq revues : qualité, sécurité, performance, tests et documentation. J'ai ensuite vérifié moi-même le défaut retenu, dans le code.

Blocker

1. Un git status en échec est affiché comme un arbre propre (mcp_server/doctor_artifact.py:105-106)

  • Le code. run_with_hard_timeout renvoie None dans trois cas : délai dépassé, code de sortie non nul, git introuvable (mcp_server/shared/subprocess_safe.py:26-30). La ligne state = " with uncommitted changes" if changes else "" traite ce None comme "", c'est-à-dire comme un arbre propre.
  • Scénario. rev-parse HEAD réussit, puis status --porcelain dépasse les 2 s (cache froid, Windows, système de fichiers réseau) ou échoue. Le check affiche alors source tree … at <sha> (version …) avec ok=True. Il affirme donc que mcp_server correspond à <sha> sans l'avoir vérifié. C'est justement l'attribution que release: bind parity claims to the distributed plugin and runtime artifacts #729 doit rendre fiable.
  • Règle enfreinte. C'est un repli silencieux, interdit par CLAUDE.md (« no silent fallbacks »). Le précédent du même fichier va dans l'autre sens : _worktree_locations (doctor.py:392-400, ADR-1079) transforme un None de git en WARN optionnel.
  • Correctif. Si changes is None, renvoyer ok=False, optional=True avec un message du type « revision state unknown: git status failed », plus un test où status échoue alors que rev-parse réussit.
  • Même problème, en plus léger, ligne 104. "no git revision" mélange trois situations sous ok=True : un dossier qui n'est pas un checkout, git absent, git trop lent.

Corriger →

Notes, no action before merge

  • Une exception non rattrapée arrête tout le doctor (_run_full_check, doctor.py:631), alors que le check est optionnel. Deux cas la provoquent : un OSError dans read_bytes() (doctor_artifact.py:67, 87), ou une ligne RECORD vide, qui lève ValueError au dépaquetage path, digest, *_ (:77).
  • tree_members utilise rglob("*"), qui ignore .gitignore. Un .DS_Store sous mcp_server/ change donc le digest de l'arbre, alors que git ne le signale pas comme modification. L'égalité roue/arbre annoncée dans le docstring (:16-18) et dans le CHANGELOG ne tient alors plus.
  • Le conseil _REINSTALL renvoie vers runtime.py setup, qui lance uv tool install sans --reinstall. Quand la version installée est déjà la bonne, cette commande ne remplace sans doute pas les fichiers altérés.

Reste vérifié, rien à signaler

  • Couches. Le module n'importe que la stdlib, mcp_server et shared.
  • Plafonds. Le fichier fait 130 lignes, chaque fonction moins de 40.
  • # source:. Les sources citées sont exactes : timeout de 2,0 s, nom de la distribution, nom du paquet.
  • Détection de la roue. Elle est correcte dans les trois cas : installation editable, roue masquée par un checkout, roue installée.
  • Tests existants. Aucun n'indexe les checks d'une façon qui casserait : CHECKS[-1] reste bien _recorded_failures.
  • Sécurité. Les commandes passent par une liste argv, sans shell. Les lignes RECORD contenant .. n'exposent que des hashes que l'attaquant connaît déjà.

Verdict: block

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 1m 50s —— View job


Revue de la PR #887 (revue 2 sur 3, commit e37274e)

  • Vérifier que le Blocker de la revue 1 est corrigé : un git status en échec renvoie désormais un check optionnel en échec, et un test le couvre.
  • Revoir uniquement les commits poussés après b894775 : il n'y en a qu'un, le fixup! e37274e.
  • Lancer les cinq revues : qualité, sécurité, performance, tests, documentation. Aucune n'a trouvé de Blocker.
  • Publier le verdict

La revue complète est publiée ici : #887 (comment)

Pas lancé : pytest, faute d'autorisation dans cette exécution. J'ai vérifié les tests en traçant le code.

Notes (3), aucune action requise avant le merge :

  • _REINSTALL omet --python, que runtime.py setup passe.
  • Un arbre sans .git échoue maintenant et propose un git status qui ne lui sert à rien.
  • Une RECORD absente est décrite comme un arbre source.

Verdict: approve
· Branche fix/729-runtime-artifact-identity

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Revue Claude de #887 (revue 2 sur 3, commits après b894775 : e37274e)

J'ai lancé cinq revues sur le seul fixup! e37274e : qualité, sécurité, performance, tests et documentation. J'ai vérifié moi-même dans le code le Blocker précédent et les notes retenues.

Blocker de la revue 1 : corrigé

  • git status en échec affiché comme un arbre propre (mcp_server/doctor_artifact.py:120-124). Un None de git rend maintenant ok=False, optional=True, avec revision state unknown: \git status` failedet une commande pour diagnostiquer. C'est le précédent_worktree_locations. test_failed_git_status_is_not_a_clean_treecouvre ce cas :rev-parseréussit,statusrenvoieNone`.
  • Les deux notes reprises par Codex sont aussi corrigées.
    • Un OSError ou une RECORD malformée ne fait plus échouer tout le doctor, grâce au try/except de artifact_identity:136-141. Les lignes RECORD vides sont ignorées.
    • Le conseil de réparation force bien --reinstall.

Blockers

Aucun dans e37274e. D'après la trace du code, les nouveaux tests touchent bien leurs branches en 3.11 et 3.12. test_source_tree_without_git_says_so reste vert, car il ne vérifie que le préfixe.

Notes, no action before merge

  • _REINSTALL (doctor_artifact.py:43-47) omet le --python sys.executable que runtime.py setup passe (runtime.py:65-66). uv peut donc choisir un autre interpréteur, ce qui est le cas décrit dans le docstring de runtime.py.
  • Un arbre sans .git tombe maintenant en ok=False (:111-124), mais il garde le conseil git -C … status, qui ne sert à rien là. Le test :165-170 ne fixe ni ok ni fix.
  • Une RECORD absente ou illisible fait renvoyer None par dist.read_text. L'installation est alors décrite comme un « source tree » (:150-153) au lieu d'échouer, ce qui contredit en partie le post-condition :132-135.

Verdict: approve

@github-actions
github-actions Bot merged commit 499c69d into main Nov 6, 2026
18 checks passed
github-actions Bot pushed a commit that referenced this pull request Nov 6, 2026
…ees outside git (#729, slice 2) (#904)

Review notes on #887 (slice 1), traced in doctor_artifact.py:
- tree_members hashed every file under mcp_server/, so an ignored
  .DS_Store changed the source-tree digest while hatchling leaves it out
  of the wheel; a checkout now digests git's tree (ls-files -co
  --exclude-standard).
- a tree without .git failed with a `git -C … status` fix that cannot
  help; it is now named 'outside git' and passes on its digest.
- an installed wheel without RECORD was described as a source tree; it
  now fails with the reinstall fix.
- the reinstall fix omitted --python, which runtime.py setup passes.

Refs #729


Claude-Session: https://claude.ai/code/session_018kyZYrc9zFgbqiq6bsahve

Co-authored-by: Claude <[email protected]>
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.

2 participants