Description
Split out from #6702 (impl-critic finding S6, deferred during that PR's review as a pre-existing trust-model behavior unrelated to AutoSkill/trace-extraction specifically — it applies to any quarantined skill, by any origin).
In update_trust_for_reloaded_skills's source_kind-mismatch branch (crates/zeph-core/src/agent/skill_reload.rs, ~lines 133-142): when a skill's classified SourceKind changes between reloads (e.g. Hub → Local), the preserve-existing-trust-level branch is skipped even if the file's content (and hash) are unchanged, and trust falls back to *initial_level for the new source kind. Quarantined.is_active() is true and its severity() (2) is greater than Trusted's (0), so the mismatch guard takes *initial_level rather than keeping the higher Quarantined level.
classify_source_kind returns Local for anything outside managed_dir, and [skills.trust] local_level defaults to Trusted (crates/zeph-config/src/security.rs). So: moving (or copying) a Quarantined skill's directory from inside managed_dir to anywhere else — contents unchanged, hash still matches — flips its SourceKind from Hub to Local and promotes it straight to Trusted on the next reload, with no review step.
This is precisely the scenario in #6702's original Expected Behavior text ("Moving a skill directory aside... should reliably keep it out of loading/matching/trust-promotion, regardless of default_level") — except in reverse: moving a quarantined skill out of the managed tree, rather than moving a draft into an operator-named subdirectory within it. #6702's fix (eager trust-row write for AutoSkill drafts) does not address this, since it only prevents trust from being assigned in the first place absent an existing row — it does nothing to prevent an existing Quarantined row from being escalated on a legitimate content-preserving relocation.
Reproduction Steps
- Have a skill with an existing
skill_trust row at Quarantined (any origin — AutoSkill draft, manually quarantined, etc.) living inside managed_dir.
- Move (not copy — content and hash must be identical) its directory to a location outside
managed_dir (or, depending on setup, to a location that reclassifies as Local rather than Hub).
- Trigger a reload (file watcher fires on removal from the old location / addition at the new one, or restart the agent).
- Inspect the
skill_trust row for that skill afterward.
Expected Behavior
Trust level should not silently escalate on a pure relocation of unchanged content. At minimum, a source_kind change should never move a skill's effective trust level to something higher than what it already had, absent explicit operator action — the mismatch branch should compare severities and take the lower of *initial_level and the existing preserved level, not blindly the new source kind's default.
Actual Behavior
Quarantined → Trusted promotion on a content-identical relocation from managed_dir (Hub) to anywhere else (Local), since local_level commonly defaults to Trusted.
Environment
Suggested direction (non-binding)
In the source_kind-mismatch branch of update_trust_for_reloaded_skills, when hash matches but source_kind doesn't, take min(severity(*initial_level), severity(existing.trust_level)) rather than unconditionally *initial_level — i.e. a relocation can only ever hold or lower effective trust, never raise it, without an explicit operator action resetting the row.
Related
Description
Split out from #6702 (impl-critic finding S6, deferred during that PR's review as a pre-existing trust-model behavior unrelated to AutoSkill/trace-extraction specifically — it applies to any quarantined skill, by any origin).
In
update_trust_for_reloaded_skills's source_kind-mismatch branch (crates/zeph-core/src/agent/skill_reload.rs, ~lines 133-142): when a skill's classifiedSourceKindchanges between reloads (e.g.Hub→Local), the preserve-existing-trust-level branch is skipped even if the file's content (and hash) are unchanged, and trust falls back to*initial_levelfor the new source kind.Quarantined.is_active()istrueand itsseverity()(2) is greater thanTrusted's (0), so the mismatch guard takes*initial_levelrather than keeping the higherQuarantinedlevel.classify_source_kindreturnsLocalfor anything outsidemanaged_dir, and[skills.trust] local_leveldefaults toTrusted(crates/zeph-config/src/security.rs). So: moving (or copying) aQuarantinedskill's directory from insidemanaged_dirto anywhere else — contents unchanged, hash still matches — flips itsSourceKindfromHubtoLocaland promotes it straight toTrustedon the next reload, with no review step.This is precisely the scenario in #6702's original Expected Behavior text ("Moving a skill directory aside... should reliably keep it out of loading/matching/trust-promotion, regardless of
default_level") — except in reverse: moving a quarantined skill out of the managed tree, rather than moving a draft into an operator-named subdirectory within it. #6702's fix (eager trust-row write for AutoSkill drafts) does not address this, since it only prevents trust from being assigned in the first place absent an existing row — it does nothing to prevent an existingQuarantinedrow from being escalated on a legitimate content-preserving relocation.Reproduction Steps
skill_trustrow atQuarantined(any origin — AutoSkill draft, manually quarantined, etc.) living insidemanaged_dir.managed_dir(or, depending on setup, to a location that reclassifies asLocalrather thanHub).skill_trustrow for that skill afterward.Expected Behavior
Trust level should not silently escalate on a pure relocation of unchanged content. At minimum, a
source_kindchange should never move a skill's effective trust level to something higher than what it already had, absent explicit operator action — the mismatch branch should compare severities and take the lower of*initial_leveland the existing preserved level, not blindly the new source kind's default.Actual Behavior
Quarantined→Trustedpromotion on a content-identical relocation frommanaged_dir(Hub) to anywhere else (Local), sincelocal_levelcommonly defaults toTrusted.Environment
Suggested direction (non-binding)
In the
source_kind-mismatch branch ofupdate_trust_for_reloaded_skills, when hash matches butsource_kinddoesn't, takemin(severity(*initial_level), severity(existing.trust_level))rather than unconditionally*initial_level— i.e. a relocation can only ever hold or lower effective trust, never raise it, without an explicit operator action resetting the row.Related