Summary
Every other overwrite category converges the whole area the manifest owns:
mcp deactivates servers no longer declared, skills deactivates skills no
longer declared, cli_tools removes tools no longer declared. resources does
not. Its ownership unit is the individual entry: a file entry owns its path,
a directory entry owns its subtree, and a path that the previous document
declared and the current one omits is simply left on disk.
apply/materialisers/resources.py states this explicitly in its module
docstring ("Cross-entry removals ... are v1-empty by the work item's own
definition") and gives the reason: the BaaS transport has no "who wrote this
file" ledger, so "delete everything not declared" would wipe user and engine
files from the workspace.
Consequence
# v1
resources:
- {path: data/a.csv, from: content}
- {path: data/b.csv, from: content}
# v2 — b.csv removed from the document
resources:
- {path: data/a.csv, from: content}
After applying v2, data/b.csv is still in the workspace. The only ways to
retract it are a manual delete or restructuring the declaration into a single
directory entry so the subtree replace handles it.
For a feature whose premise is "the document is the desired state", this is
the one category where the desired state is not fully expressed.
Why the missing ledger already exists
The apply report history is that ledger. Every successful apply's
ApplyReport.entries for the resources category lists exactly the paths the
manifest wrote (member paths for directory entries, the path for file entries).
The same read-back mechanism that #2155 uses for strict baselines
(_last_resolutions walking the last N reports) can answer "which resource
paths did the last successful apply write". Paths in that set and not in the
current plan are manifest-owned and safe to remove; paths the manifest never
wrote are never touched. No transport change is needed.
Proposed design
- In
resources plan, read the previous successful apply's resources
entry identities for this bot (a new helper beside _last_resolutions,
same bounded walk, newest report that has a resources category wins).
- Compute
retract = previous_paths - current_paths, excluding any path that
falls under a directory entry in the current plan (those are handled by the
subtree replace already).
- Emit those paths on
CategoryPlan.removals alongside the declared-tree
markers, so dry-run shows them and write deletes them before writing
members. Report them through CategoryResult.removals as today.
- Do not retract on the first apply after this change lands if the
previous report predates it and carries no resources entries (empty
previous_paths → nothing retracted), which is the safe default.
Edge cases to decide
- A file the manifest wrote and the user then edited in place: still
manifest-owned by provenance, so it is retracted. State this in the docs;
it matches how directory replace already treats hand-edited members.
- A previous file entry
data/x.csv that the current document now covers via
a directory entry data/: excluded by rule 2 (the subtree replace owns it).
- A previous directory entry
data/ that the current document drops entirely:
retract every member path the previous report listed under it, then attempt
a tree delete of data/ (best effort, same False/re-probe handling as
today).
- Reports are capped at N=10 in the walk; if the last successful
resources
apply is older than that, no retraction happens. Acceptable; say so in the
docstring.
Docs to update
manifest-schema.zh-CN.md §3.2 and user-manual.zh-CN.md wherever they say
resources removals are per-entry only.
Summary
Every other overwrite category converges the whole area the manifest owns:
mcpdeactivates servers no longer declared,skillsdeactivates skills nolonger declared,
cli_toolsremoves tools no longer declared.resourcesdoesnot. Its ownership unit is the individual entry: a file entry owns its
path,a directory entry owns its subtree, and a path that the previous document
declared and the current one omits is simply left on disk.
apply/materialisers/resources.pystates this explicitly in its moduledocstring ("Cross-entry removals ... are v1-empty by the work item's own
definition") and gives the reason: the BaaS transport has no "who wrote this
file" ledger, so "delete everything not declared" would wipe user and engine
files from the workspace.
Consequence
After applying v2,
data/b.csvis still in the workspace. The only ways toretract it are a manual delete or restructuring the declaration into a single
directory entry so the subtree replace handles it.
For a feature whose premise is "the document is the desired state", this is
the one category where the desired state is not fully expressed.
Why the missing ledger already exists
The apply report history is that ledger. Every successful apply's
ApplyReport.entriesfor theresourcescategory lists exactly the paths themanifest wrote (member paths for directory entries, the path for file entries).
The same read-back mechanism that #2155 uses for strict baselines
(
_last_resolutionswalking the last N reports) can answer "which resourcepaths did the last successful apply write". Paths in that set and not in the
current plan are manifest-owned and safe to remove; paths the manifest never
wrote are never touched. No transport change is needed.
Proposed design
resourcesplan, read the previous successful apply'sresourcesentry identities for this bot (a new helper beside
_last_resolutions,same bounded walk, newest report that has a
resourcescategory wins).retract = previous_paths - current_paths, excluding any path thatfalls under a directory entry in the current plan (those are handled by the
subtree replace already).
CategoryPlan.removalsalongside the declared-treemarkers, so dry-run shows them and
writedeletes them before writingmembers. Report them through
CategoryResult.removalsas today.previous report predates it and carries no resources entries (empty
previous_paths→ nothing retracted), which is the safe default.Edge cases to decide
manifest-owned by provenance, so it is retracted. State this in the docs;
it matches how directory replace already treats hand-edited members.
data/x.csvthat the current document now covers viaa directory entry
data/: excluded by rule 2 (the subtree replace owns it).data/that the current document drops entirely:retract every member path the previous report listed under it, then attempt
a tree delete of
data/(best effort, sameFalse/re-probe handling astoday).
resourcesapply is older than that, no retraction happens. Acceptable; say so in the
docstring.
Docs to update
manifest-schema.zh-CN.md§3.2 anduser-manual.zh-CN.mdwherever they sayresources removals are per-entry only.