Source Retirement
A package folder the repository deletes takes the nodes it imported with it — the same rule as deleting one file from the folder, applied to the last file too.
The defect this closes
Deleting 24 of a package's 25 files pruned 24 nodes on the next sync. Deleting all 25 pruned
nothing, ever. The sync refused the empty listing — correctly, for the case the refusal was
written for (#1326: a mistyped or mis-cased subdirectory also lists nothing, and importing an empty
snapshot under FullReplace would mirror the whole Space away) — and a deleted folder produces the
very same empty listing. Nothing told the two apart, so a renamed or removed package left every node
it had ever imported live for good.
Measured on both production meshes, 2026-10-06. MeshWeaver.Plugins renamed DeepSign to
Signature (c3262d1e9). DeepSign/_GitSync kept following subdirectory: DeepSign, found nothing,
and recorded lastSyncOutcome: Refused on every commit since — 1,442 versions of that node on
memex.systemorph.com. Meanwhile DeepSign/RequestSignatureMenu and DeepSign/SignaturesMenu — two
UiContribution nodes — stayed in the mesh-wide contribution catalog, so every node's ⋯ More
offered Request Signature twice and Signatures twice, the old pair pointing at /DeepSign/…
URLs the package had retired. The earlier investigation of the same refusal (#4499) read it as a
configuration fault ("the subdirectory was the wrong half"), which is what the refusal's own wording
says — and why it was never fixed at the source.
How a deletion is told apart from a typo
By the last commit this source imported (lastSyncCommitSha): the sync reads the configured
folder there.
| At the last imported commit | Now | Verdict |
|---|---|---|
| files | none | Retired — git deleted the folder |
| none | none | Refused — the folder was never there (a typo) |
| — (first import, no base) | none | Refused |
| unreadable / truncated listing | none | Refused |
Every unknown answers refuse, the direction that deletes nothing. An operator who re-points a source at a folder that exists at neither commit (the usual typo) is still refused. 🚨 The proof reads the folder the source is configured with now: re-pointing it at a different folder that did exist at the last imported commit and is gone at the new one reads as a retirement, and retires what this source imported even though the folder those nodes came from may still exist. That corner stays provenance-gated and recoverable — point the source back and the next sync imports the nodes again — but it is accepted, not excluded.
🚨 It is a read of the folder at the base commit, not a git diff. The production repository
client (GitProtocolRepoClient) does not forward GetChangedPaths to the compare API, so it answers
every diff with null — a proof built on the diff would never fire where it is needed.
Once retired, a source stays retired under the same configuration: on a later commit the base is
the retirement commit, where the folder is already empty, so the recorded Retired outcome (scoped by
the configuration fingerprint) is what carries the verdict forward. If the folder comes back, the
listing is no longer empty and the ordinary import brings it back.
What goes, and what stays
StaticRepoImporter.RetireSource removes exactly what the ordinary prune would remove for an empty
source — ComputePrunableNodes with every guard intact:
- Provenance — only paths the source's import manifest records. A node created in the partition at runtime was never the source's to delete.
- Governance (
_Access,_Activity,_Policy, …) and mesh-minted release records stay. - A partition decoupled with "sync: none" is left alone.
- A NodeType that still has instances anywhere on the mesh is held and stamped
PendingRetirement(NodeTypeInstanceProbe), exactly as in an ordinary import.
Two differences, both deliberate:
- The partition root stays. Deleting it is recursive and would take the sync source, the access
grants and every runtime node with it. That is the governed package removal's job — the Store's
SystemRemoval(Provision → Remove), which also cleans each viewer's installed copy and checks for dependent packages. The retirement'slastSyncNotesays so. - The source's own instances do not hold their type. A package often ships an instance of its
own type (a desk, a workspace). The probe runs once, before anything is deleted, and
PlanRetirementholds a type only for an instance that will SURVIVE — one outside the retired set, or one the probe counted but could not name. A held type also keeps every folder node above it: a delete is recursive, and deleting the folder would delete the type. The decision is a pure function of the probe's answer, so it never depends on how far an index has caught up with a delete made a moment earlier.
What is still a person's decision
The retired partition keeps its root (Store/Plugin, so the catalog may still list it) and its
_GitSync. Whether the package is gone for good — and so whether its root, its grants and the copies
viewers installed should go — is decided through the governed removal, by a global admin. The sync
retires content; it never removes a package.
Related
- Sources Sync on Push — when a source imports, and the provenance rule the prune follows
- Static Repo Import — the importer and its prune guards