docs: expose indirect DELETE second unlink and propose completed-proof skip

This commit is contained in:
jason.woltje committed 2026-10-25 16:08:06 -05:00
1 parent 97a6118885
commit 835c8dcb14
2 files changed
+33

No files matched your search

@@ -0,0 +1,16 @@
--- a/packages/bus/src/credentials.mjs
+++ b/packages/bus/src/credentials.mjs
@@ -568,7 +568,12 @@
// revoke for every service, then the agent's dir. A file other than a token or its tmp refuses.
remove(instance) {
const services = [...this.#refs.keys()].filter((k) => k.startsWith(instance + ':')).map((k) => k.slice(instance.length + 1));
- for (const service of services) this.revoke(instance, service);
+ for (const service of services) {
+ const proof = service === 'model' ? this.#modelProofs.get(instance) : null;
+ const completedModel = proof?.instance === instance && proof.phase === 'finished'
+ && !this.#tokens.has(instance + ':model') && !this.#held.has(instance + ':model') && !this.#inflight.has(instance + ':model');
+ if (!completedModel) this.revoke(instance, service);
+ }
const agentDir = dirname(this.#storePath(instance, 'gitea'));
let names;
try {
@@ -0,0 +1,17 @@
# 656 /1735D3: composite DELETE re-enters generic model unlink after completion
STATIC source/caller trace only; no execution, source application or old helper/test edit. Baseline tested27f68/ModelKeys8148/runtime9566; all1652focusedmappedinputs unchanged. All-intent checked-view scope1742 is accepted; #657/#658 remain Sage-owned and unresolved for final acceptance.
Current modelOrganization.revoke invokes modelKeys.completeRevoke(instance), then calls original(business,agent,how,services). runtime original revoke:342 uses credentials.remove(instance) for DELETE. Credentials.remove:445 derives services from ALL #refs, including the implicit model reference. Model-only final completion is required to keep that reference/date/bookkeeping (as generic revoke does), so the completed model still appears in services. remove:446 calls revoke(instance,'model') again. Since model cache is already cleared, the generic modelConfirmed guard does not refuse; revoke attempts unlinkSync(model) at:437 a SECOND time. It normally sees ENOENT, but it is fallible and invalidates the new private completion proof in the proposed model-only invalidation hook. FIRE's configured service list excludes implicit model, so it does not take this second-model-unlink route.
Thus the drafted finishModelRevocation method itself satisfies ONE final unlink followed by synchronous memory operations/no abort callback/no further FS, but the existing composite DELETE caller would re-enter generic revoke afterwards. This is exactly the indirect form of1735D3's forbidden second fallible model pathname unlink if that rule covers the full DELETE chain. It cannot be hidden as 'only ENOENT' or falsely described as retaining disk intent after a later failure; the model completion already succeeded before generic delete runs.
## Proposed minimal owner exception (UNAPPLIED, not selected)
Permit a MODEL-ONLY completed-proof check in Credentials.remove's service loop: skip the redundant model revoke ONLY when this SAMEinstance's latest private proof is phase=finished, cached token absent and held/inflight absent. That phase can be reached only after the full remote401/fresh-view/validated-single-final-unlink path through ModelKeys.completeRevoke. Every model rewrite/reload/ref mutation/close already invalidates that proof. Initial no-record/no-proof, active/revoking/prepared/unknown/stale states take the existing guard/path unchanged; no filename or revoking label authorizes this skip.
This preserves model reference/date/status and returned services metadata, changes no signature/API/caller/IPC/vocabulary, and leaves generic remove's other-service memory-first and unknown-name fence byte-for-byte behavior unchanged. New model/model.tmp names appearing after proven completion are still rejected by the existing credentialKind fence, never added to a deletion allowlist. It covers BOTH runtime organization DELETE and credential-verbs DELETE without routing around either. It avoids intrusive reference removal/status changes or a new general writer/cleanup method.
The exact dependent UNAPPLIED loop proposal is task656-delete-completed-skip-proposed.diff. It is AGAINST the draft integrated Credentials virtual source, not current production; do not apply it independently. This is a narrow exception to the literal 'generic remove unchanged' requirement and therefore needs explicit owner acceptance before the all-file proposal is declared complete. If owner instead treats later generic agent cleanup as outside1735D3, state that scope explicitly and keep separate truthful failure outcomes; no exception is silently assumed.
NEW isolated tests will require one final pathname unlink across matched DELETE, plus a generic-remove-after-exact-model-completion counter regression and controls for active/unconfirmed/no-proof ordinary model entries. Prefix matched DELETE already fails its earlier recovery assertion, so its later unlink counter is not claimed reached on RED. No broader onlytmp/differing recovery, new path, selector, key restore, signal authority or real service traffic is introduced. Source/test execution remains held; the core single-final-unlink/proof draft is not accepted as a complete DELETE solution until this caller boundary is ruled on.