Skip to content

apply: resources cannot retract a file the previous manifest declared (cross-entry removals are empty) #2162

Description

@totalfrank

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

  1. 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).
  2. 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).
  3. 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.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions