instar 1.3.306 → 1.3.308
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/SafeFsExecutor.d.ts +12 -0
- package/dist/core/SafeFsExecutor.d.ts.map +1 -1
- package/dist/core/SafeFsExecutor.js +19 -2
- package/dist/core/SafeFsExecutor.js.map +1 -1
- package/package.json +1 -1
- package/scripts/instar-dev-precommit.js +48 -16
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.307.md +20 -0
- package/upgrades/1.3.308.md +20 -0
- package/upgrades/gate-blocked-audit-hygiene.eli16.md +11 -0
- package/upgrades/safefs-rm-retry-default.eli16.md +7 -0
- package/upgrades/side-effects/gate-blocked-audit-hygiene.md +41 -0
- package/upgrades/side-effects/safefs-rm-retry-default.md +40 -0
|
@@ -24,6 +24,18 @@ export interface SafeFsOptions {
|
|
|
24
24
|
export type SafeRmOptions = fs.RmOptions & SafeFsOptions;
|
|
25
25
|
export type SafeRmDirOptions = fs.RmDirOptions & SafeFsOptions;
|
|
26
26
|
export declare class SafeFsExecutor {
|
|
27
|
+
/**
|
|
28
|
+
* Recursive force-deletes default to fs.rm's native transient-error retry
|
|
29
|
+
* (ENOTEMPTY/EBUSY/EPERM). A recursive tree delete racing a concurrent
|
|
30
|
+
* writer (git finishing an object write, an mtime scanner walking the
|
|
31
|
+
* tree) fails ENOTEMPTY exactly once and succeeds milliseconds later —
|
|
32
|
+
* fs.rm retries this class natively, but only when maxRetries is set.
|
|
33
|
+
* Live: CI shard failure 2026-06-05 in handoff-manager.test.ts cleanup
|
|
34
|
+
* (`ENOTEMPTY: directory not empty, rmdir '/tmp/handoff-…/.git'`) on a
|
|
35
|
+
* loaded runner — a whole-class flake every tmpdir-cleanup test inherits.
|
|
36
|
+
* Explicit caller-set values always win; non-recursive deletes unchanged.
|
|
37
|
+
*/
|
|
38
|
+
private static withRmRetryDefaults;
|
|
27
39
|
/** Async fs.promises.rm wrapper. */
|
|
28
40
|
static safeRm(target: string, opts: SafeRmOptions): Promise<void>;
|
|
29
41
|
/** Sync fs.rmSync wrapper. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SafeFsExecutor.d.ts","sourceRoot":"","sources":["../../src/core/SafeFsExecutor.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,OAAO,EAEL,oBAAoB,EACrB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,EAAE,CAAC,SAAS,GAAG,aAAa,CAAC;AACzD,MAAM,MAAM,gBAAgB,GAAG,EAAE,CAAC,YAAY,GAAG,aAAa,CAAC;AAsF/D,qBAAa,cAAc;IACzB,oCAAoC;WACvB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvE,8BAA8B;IAC9B,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;IAY5D,wCAAwC;WAC3B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAW3E,kCAAkC;IAClC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;IAWhE,uCAAuC;WAC1B,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7E,iCAAiC;IACjC,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAiClE;;;OAGG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA2BzH;;;;OAIG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAIhI;AAID,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"SafeFsExecutor.d.ts","sourceRoot":"","sources":["../../src/core/SafeFsExecutor.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,OAAO,EAEL,oBAAoB,EACrB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,EAAE,CAAC,SAAS,GAAG,aAAa,CAAC;AACzD,MAAM,MAAM,gBAAgB,GAAG,EAAE,CAAC,YAAY,GAAG,aAAa,CAAC;AAsF/D,qBAAa,cAAc;IACzB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAOlC,oCAAoC;WACvB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvE,8BAA8B;IAC9B,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;IAY5D,wCAAwC;WAC3B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAW3E,kCAAkC;IAClC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;IAWhE,uCAAuC;WAC1B,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7E,iCAAiC;IACjC,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAiClE;;;OAGG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA2BzH;;;;OAIG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAIhI;AAID,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -98,12 +98,29 @@ function guard(target, operation, fnName) {
|
|
|
98
98
|
}
|
|
99
99
|
// ── Public API ──────────────────────────────────────────────────────
|
|
100
100
|
export class SafeFsExecutor {
|
|
101
|
+
/**
|
|
102
|
+
* Recursive force-deletes default to fs.rm's native transient-error retry
|
|
103
|
+
* (ENOTEMPTY/EBUSY/EPERM). A recursive tree delete racing a concurrent
|
|
104
|
+
* writer (git finishing an object write, an mtime scanner walking the
|
|
105
|
+
* tree) fails ENOTEMPTY exactly once and succeeds milliseconds later —
|
|
106
|
+
* fs.rm retries this class natively, but only when maxRetries is set.
|
|
107
|
+
* Live: CI shard failure 2026-06-05 in handoff-manager.test.ts cleanup
|
|
108
|
+
* (`ENOTEMPTY: directory not empty, rmdir '/tmp/handoff-…/.git'`) on a
|
|
109
|
+
* loaded runner — a whole-class flake every tmpdir-cleanup test inherits.
|
|
110
|
+
* Explicit caller-set values always win; non-recursive deletes unchanged.
|
|
111
|
+
*/
|
|
112
|
+
static withRmRetryDefaults(rmOpts) {
|
|
113
|
+
if (rmOpts.recursive && rmOpts.force && rmOpts.maxRetries === undefined) {
|
|
114
|
+
return { ...rmOpts, maxRetries: 3, retryDelay: 100 };
|
|
115
|
+
}
|
|
116
|
+
return rmOpts;
|
|
117
|
+
}
|
|
101
118
|
/** Async fs.promises.rm wrapper. */
|
|
102
119
|
static async safeRm(target, opts) {
|
|
103
120
|
const { operation, ...rmOpts } = opts;
|
|
104
121
|
const canonical = guard(target, operation, 'safeRm');
|
|
105
122
|
try {
|
|
106
|
-
await fsp.rm(target, rmOpts);
|
|
123
|
+
await fsp.rm(target, SafeFsExecutor.withRmRetryDefaults(rmOpts));
|
|
107
124
|
audit('safeRm', operation, canonical, 'allowed');
|
|
108
125
|
}
|
|
109
126
|
catch (err) {
|
|
@@ -116,7 +133,7 @@ export class SafeFsExecutor {
|
|
|
116
133
|
const { operation, ...rmOpts } = opts;
|
|
117
134
|
const canonical = guard(target, operation, 'safeRmSync');
|
|
118
135
|
try {
|
|
119
|
-
fs.rmSync(target, rmOpts);
|
|
136
|
+
fs.rmSync(target, SafeFsExecutor.withRmRetryDefaults(rmOpts));
|
|
120
137
|
audit('safeRmSync', operation, canonical, 'allowed');
|
|
121
138
|
}
|
|
122
139
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SafeFsExecutor.js","sourceRoot":"","sources":["../../src/core/SafeFsExecutor.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"SafeFsExecutor.js","sourceRoot":"","sources":["../../src/core/SafeFsExecutor.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -45,6 +45,23 @@ const TRACES_DIR = path.join(ROOT, '.instar', 'instar-dev-traces');
|
|
|
45
45
|
// DECISIONS_DIR instead: distinct filenames can never conflict.
|
|
46
46
|
// (The frozen legacy file remains at .instar/instar-dev-decisions.jsonl.)
|
|
47
47
|
const DECISIONS_DIR = path.join(ROOT, '.instar', 'instar-dev-decisions');
|
|
48
|
+
|
|
49
|
+
// Set by writeDecisionAudit; consumed by the process 'exit' handler that
|
|
50
|
+
// finalizes the entry's verdict. BOTH the declaration AND the handler
|
|
51
|
+
// registration live here, above the top-level audit call site — placed
|
|
52
|
+
// after it, the declaration TDZ-throws inside writeDecisionAudit's
|
|
53
|
+
// try/catch, and the handler registration is never even reached when
|
|
54
|
+
// blockCommit exits first (top-level statements run in source order).
|
|
55
|
+
let pendingAuditEntry = null;
|
|
56
|
+
process.on('exit', (code) => {
|
|
57
|
+
if (!pendingAuditEntry) return;
|
|
58
|
+
try {
|
|
59
|
+
const { entryPath, entryData } = pendingAuditEntry;
|
|
60
|
+
entryData.verdict = code === 0 ? 'pass' : 'blocked';
|
|
61
|
+
fs.writeFileSync(entryPath, JSON.stringify(entryData, null, 2) + '\n');
|
|
62
|
+
execSync(`git add ${JSON.stringify(path.relative(ROOT, entryPath))}`, { cwd: ROOT });
|
|
63
|
+
} catch { /* best-effort — 'pending' is still more truthful than no verdict */ }
|
|
64
|
+
});
|
|
48
65
|
const WINDOW_MS = 60 * 60 * 1000; // 60 minutes
|
|
49
66
|
const MIN_ARTIFACT_CHARS = 200;
|
|
50
67
|
|
|
@@ -811,23 +828,31 @@ function writeDecisionAudit({ slug, suggestedTier, declaredTier, riskFloor, risk
|
|
|
811
828
|
while (fs.existsSync(entryPath)) {
|
|
812
829
|
entryPath = path.join(DECISIONS_DIR, `${ts.replace(/[:.]/g, '-')}-${safeSlug}-${n++}.json`);
|
|
813
830
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
831
|
+
const entryData = {
|
|
832
|
+
ts,
|
|
833
|
+
slug,
|
|
834
|
+
suggestedTier,
|
|
835
|
+
declaredTier,
|
|
836
|
+
// riskFloor (the number) keeps the entry self-contained for later review
|
|
837
|
+
// without re-running the classifier — not just the derived belowFloor.
|
|
838
|
+
riskFloor,
|
|
839
|
+
riskFloorReasons,
|
|
840
|
+
belowFloor,
|
|
841
|
+
files,
|
|
842
|
+
loc,
|
|
843
|
+
// Finalized by the process exit handler below: 'pass' when the gate
|
|
844
|
+
// allowed the commit, 'blocked' otherwise. The riding-the-retry design
|
|
845
|
+
// (a blocked evaluation's entry rides the next successful commit, see
|
|
846
|
+
// header comment) is deliberate — but without a verdict, a rode-along
|
|
847
|
+
// entry written under a stale/unresolved trace slug READS as a real
|
|
848
|
+
// shipped decision for that slug. Live recurrence (2026-06-05): both
|
|
849
|
+
// echo (#836) and codey (#842) shipped mislabeled "unknown"/foreign-slug
|
|
850
|
+
// entries in one day. The verdict makes every entry self-describing.
|
|
851
|
+
verdict: 'pending',
|
|
852
|
+
};
|
|
853
|
+
fs.writeFileSync(entryPath, JSON.stringify(entryData, null, 2) + '\n');
|
|
830
854
|
execSync(`git add ${JSON.stringify(path.relative(ROOT, entryPath))}`, { cwd: ROOT });
|
|
855
|
+
pendingAuditEntry = { entryPath, entryData };
|
|
831
856
|
return entryPath;
|
|
832
857
|
} catch {
|
|
833
858
|
// best-effort — never block on audit I/O
|
|
@@ -835,6 +860,13 @@ function writeDecisionAudit({ slug, suggestedTier, declaredTier, riskFloor, risk
|
|
|
835
860
|
}
|
|
836
861
|
}
|
|
837
862
|
|
|
863
|
+
// ── Verdict finalization ──────────────────────────────────────────────────
|
|
864
|
+
// The process 'exit' handler that finalizes each entry's verdict is
|
|
865
|
+
// registered next to the pendingAuditEntry declaration near the top of this
|
|
866
|
+
// file (it must be registered BEFORE the top-level gate flow can exit). One
|
|
867
|
+
// hook covers every exit path: enforceTier1's process.exit(0), the Tier-2
|
|
868
|
+
// fall-through, and every blockCommit.
|
|
869
|
+
|
|
838
870
|
// ─── Tier-1 lite enforcement ──────────────────────────────────────────────
|
|
839
871
|
// Tier-1 requirement set: a staged ELI16 (trace.eli16Path) passing the length
|
|
840
872
|
// check + a staged side-effects artifact (trace.sideEffectsPath, sha-matched if
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-05T16:13:14.546Z",
|
|
5
|
+
"instarVersion": "1.3.308",
|
|
6
6
|
"entryCount": 198,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The instar-dev pre-commit gate's decision-audit entries gain a verdict field ('pass' | 'blocked', written as 'pending' and finalized by a process exit hook that also re-stages the corrected file). The riding-the-retry design is unchanged — blocked-run entries still ride the next successful commit — but each entry is now self-describing.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
Nothing user-visible — contributor audit hygiene. Gate audit records that ride into a PR from earlier blocked attempts (often under an unresolved or stale slug) are now labeled as blocked evaluations instead of reading like real shipped decisions.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- Decision-audit entries carry verdict: pass | blocked (pending only if finalization itself failed). Reviewers can distinguish shipped decisions from rode-along blocked evaluations at a glance.
|
|
17
|
+
|
|
18
|
+
## Evidence
|
|
19
|
+
|
|
20
|
+
Live recurrence (2026-06-05, twice in one day): echo's PR #836 swept in a blocked-attempt entry labeled with a DIFFERENT PR's slug (the then-freshest stale trace), and codey's PR #842 shipped an "unknown"-slug entry from a pre-trace gate run — both read as real decisions and needed review-time explanation. Pinned by 2 new sandbox tests in tests/unit/instar-dev-precommit-audit-staging.test.ts (blocked run finalizes 'blocked' incl. the STAGED copy; passing Tier-1 run finalizes 'pass') alongside the existing 4; the development itself caught the source-order trap twice (TDZ on the shared variable, exit-before-registration on the hook) — both now documented in code comments and covered by the blocked-path test.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
SafeFsExecutor's safeRm/safeRmSync default fs.rm's native transient-error retry (maxRetries 3, retryDelay 100ms) for recursive+force deletes when the caller didn't set maxRetries. Explicit values win; non-recursive deletes unchanged.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
Nothing user-visible — internal robustness. Deleting a directory tree that something else is briefly touching (the classic "directory not empty" race) now retries for a moment instead of failing, which removes a whole class of flaky CI failures and transient cleanup errors.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- All recursive force-deletes through the SafeFsExecutor funnel tolerate transient ENOTEMPTY/EBUSY races (3 retries x 100ms) by default.
|
|
17
|
+
|
|
18
|
+
## Evidence
|
|
19
|
+
|
|
20
|
+
Live CI failure (2026-06-05, PR #844's first run): handoff-manager.test.ts cleanup failed ENOTEMPTY rmdir on /tmp/handoff-.../.git on a loaded shard — a green change turned red by a cleanup race every tmpdir test inherits. Pinned by 3 new tests in tests/unit/SafeFsExecutor.test.ts (race-window convergence, explicit maxRetries 0 respected, non-recursive unchanged).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Gate audit entries now say whether the commit actually passed
|
|
2
|
+
|
|
3
|
+
Every gated commit writes a small audit record — which tier the change declared, what the risk heuristic suggested, how big the diff was. By design, a record written during a BLOCKED attempt also survives: it gets staged, and when the developer fixes the problem and commits again, the old record rides along. The intent was honest bookkeeping — every gate evaluation leaves a trace.
|
|
4
|
+
|
|
5
|
+
The problem: nothing in the record said whether the evaluation passed or was blocked. And blocked attempts often run before the developer's trace file is fully resolved, so the record gets written under the wrong name — the slug says "unknown", or worse, it carries the slug of some OTHER pull request whose stale trace happened to be the freshest file at that moment. The result reads like a real shipped decision for a change that never shipped under that name. This bit twice in one day: Echo's registry-fix PR carried a record labeled with a different PR's slug, and Codey's header PR carried an "unknown" record — both rode into main as confusing audit noise, and both had to be explained in review.
|
|
6
|
+
|
|
7
|
+
The fix keeps the deliberate riding-the-retry design and makes every record self-describing: a **verdict** field, finalized the moment the gate process exits. Records start as "pending" when written, and a process exit hook — one hook covering every way the gate can finish — rewrites them as "pass" or "blocked" and re-stages the corrected file. A reviewer who sees verdict "blocked" next to a foreign slug now knows exactly what they're looking at: a failed evaluation under a stale trace, not a shipped decision.
|
|
8
|
+
|
|
9
|
+
Two implementation traps are documented in the code because both actually fired during development: the shared variable and the exit hook must both live ABOVE the gate's main flow in the file, because the gate runs top-to-bottom and can exit (blocked) before ever reaching code placed lower down. The first draft had the hook at the bottom; blocked runs exited before the hook was registered, and the verdict silently stayed "pending" — caught by the new tests.
|
|
10
|
+
|
|
11
|
+
Nothing changes about when records are written, staged, or how files are named. Old records without a verdict field remain valid history. Two new tests pin both sides: a blocked run's record finalizes as "blocked" (including the staged copy), and a passing Tier-1 run's record finalizes as "pass".
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Deleting a busy folder no longer fails the whole test run
|
|
2
|
+
|
|
3
|
+
When the system deletes a folder tree (a test's temporary directory, an old worktree), something else is occasionally still touching a file inside it at that exact moment — git finishing a write, a file scanner mid-walk. The delete then fails with "directory not empty" even though trying again fifty milliseconds later would succeed. Node's built-in delete function knows about this exact situation and can retry automatically — but only if the caller asks for retries, and ours never did.
|
|
4
|
+
|
|
5
|
+
This bit us today in CI: a test suite's cleanup tried to delete its temporary git repo while something still held the .git folder, failed once, and turned a completely green change into a red build. Any test that cleans up a temporary directory inherits this lottery ticket, and on a loaded CI runner the lottery pays out regularly.
|
|
6
|
+
|
|
7
|
+
The fix goes in the one funnel every delete already passes through (the safety wrapper that guards against deleting the wrong thing): recursive force-deletes now default to three quick retries, a third of a second of patience total. Explicit caller settings always win — passing zero retries keeps the old fail-fast behavior — and non-recursive deletes are untouched. Three tests pin the behavior: a delete racing a concurrent writer converges, an explicit zero is respected, and non-recursive semantics are unchanged.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Side-Effects Review — Gate decision-audit verdict finalization
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `gate-blocked-audit-hygiene`
|
|
4
|
+
**Date:** `2026-06-05`
|
|
5
|
+
**Author:** `instar-echo`
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
`writeDecisionAudit` writes entries with `verdict: 'pending'` and records `{entryPath, entryData}` in a module variable; a `process.on('exit')` hook (registered next to the variable, ABOVE the gate's top-level flow) rewrites the entry with `verdict: code === 0 ? 'pass' : 'blocked'` and re-stages it. Entry naming, staging, timing, and the riding-the-retry property are unchanged.
|
|
10
|
+
|
|
11
|
+
## Decision-point inventory
|
|
12
|
+
|
|
13
|
+
- Entry schema — extended — new `verdict` field ('pending' at write; finalized at exit). Additive: old entries without the field remain valid; no reader migration (the decision-audit-presence CI check matches by path, not schema — verified, its 9 tests untouched and green).
|
|
14
|
+
- Exit hook — added — one hook covers every exit path (enforceTier1's exit(0), Tier-2 fall-through, every blockCommit's exit(1)); synchronous-only work; its own try/catch leaves 'pending' on failure (still more truthful than no verdict).
|
|
15
|
+
- Source-order placement — the variable AND the hook registration live above the top-level audit call site. Both failure modes fired in development: a below-call-site declaration TDZ-throws inside writeDecisionAudit's try/catch (silently skipping tracking), and a below-flow registration is never reached when blockCommit exits early (verdict stayed 'pending' — caught by the new blocked-path test).
|
|
16
|
+
|
|
17
|
+
## Direction of failure
|
|
18
|
+
|
|
19
|
+
- Old failure: rode-along entries from blocked runs (often under 'unknown'/foreign slugs) read as real shipped decisions — twice in one day (echo #836, codey #842).
|
|
20
|
+
- New behavior: every entry self-describes its outcome.
|
|
21
|
+
- Conservative direction: a finalization failure leaves 'pending' — ambiguous but honest; never a wrong verdict.
|
|
22
|
+
|
|
23
|
+
## Side-effects checklist
|
|
24
|
+
|
|
25
|
+
1. **Over-block:** none — the gate's pass/block decisions are untouched; this only records them.
|
|
26
|
+
2. **Exit-hook safety:** synchronous fs/execSync only (required in 'exit' handlers); wrapped in try/catch; cannot change the exit code (the 'exit' event can't).
|
|
27
|
+
3. **Double-stage:** the entry is git-added at write AND at finalize — idempotent (same path, updated content).
|
|
28
|
+
4. **CI interplay:** decision-audit-presence-check matches entries by PATH under .instar/instar-dev-decisions/ — schema-agnostic; its tests run green against the new field.
|
|
29
|
+
5. **Level-of-abstraction fit:** the verdict is determined by the process outcome, not by threading state through every blockCommit call site — one mechanism, all paths.
|
|
30
|
+
6. **External surfaces:** none — local gate script + committed JSON records.
|
|
31
|
+
7. **Rollback cost:** revert; entries return to verdict-less (old shape), already-committed verdict entries remain valid.
|
|
32
|
+
|
|
33
|
+
## Scope not taken
|
|
34
|
+
|
|
35
|
+
- No slug-resolution improvement for blocked runs (the verdict defuses the mislabeling harm; resolving the "right" slug for an unmatched trace is guesswork).
|
|
36
|
+
- No retroactive verdict backfill of historical entries.
|
|
37
|
+
- No blocked-entry relocation/exclusion — riding-the-retry stays (deliberate #827-era design: every evaluation leaves a committed trace).
|
|
38
|
+
|
|
39
|
+
## Rollback
|
|
40
|
+
|
|
41
|
+
Revert the commit.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Side-Effects Review — SafeFsExecutor recursive-delete retry defaults
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `safefs-rm-retry-default`
|
|
4
|
+
**Date:** `2026-06-05`
|
|
5
|
+
**Author:** `instar-echo`
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
New private `withRmRetryDefaults`: when a safeRm/safeRmSync call is `recursive && force` and the caller did NOT set `maxRetries`, default `{maxRetries: 3, retryDelay: 100}` (fs.rm's native transient-error retry). Guard, audit, and error propagation unchanged.
|
|
10
|
+
|
|
11
|
+
## Decision-point inventory
|
|
12
|
+
|
|
13
|
+
- `withRmRetryDefaults` — added — fills only `undefined`; explicit values (incl. 0) win.
|
|
14
|
+
- `safeRm` / `safeRmSync` — modified — options pass through the helper.
|
|
15
|
+
- `safeRmdirSync`, `safeUnlink*`, guard/audit paths — untouched.
|
|
16
|
+
|
|
17
|
+
## Direction of failure
|
|
18
|
+
|
|
19
|
+
- Old: a transient ENOTEMPTY/EBUSY race failed the delete immediately (CI flake class; live shard failure 2026-06-05).
|
|
20
|
+
- New: up to 3 retries over ~300ms; a PERSISTENT error still throws identically (same error, same audit 'denied' record).
|
|
21
|
+
- Conservative direction: deletes the caller asked for succeed slightly more often; nothing is deleted that wasn't requested — the guard runs BEFORE any retry and the target path never changes.
|
|
22
|
+
|
|
23
|
+
## Side-effects checklist
|
|
24
|
+
|
|
25
|
+
1. **Latency:** worst case +300ms on a genuinely failing recursive delete — negligible against the operations involved.
|
|
26
|
+
2. **Semantics:** retry only widens the success window of the SAME operation; no new deletion scope. SourceTreeGuard evaluation is unaffected (it runs once, before).
|
|
27
|
+
3. **Race-with-writer behavior:** a writer that keeps writing past the window still fails the delete with the original error — honest failure preserved.
|
|
28
|
+
4. **Caller control:** explicit `maxRetries` (incl. 0 for fail-fast) is never overridden — pinned by test.
|
|
29
|
+
5. **Funnel placement:** single chokepoint benefits every existing and future caller — no per-callsite churn.
|
|
30
|
+
6. **External surfaces:** none.
|
|
31
|
+
7. **Rollback:** revert; behavior returns to fail-fast.
|
|
32
|
+
|
|
33
|
+
## Scope not taken
|
|
34
|
+
|
|
35
|
+
- No retry on safeUnlink/safeRmdirSync (single-entry ops don't exhibit the tree-walk race).
|
|
36
|
+
- No backoff tuning knob (constants suffice; revisit only with evidence).
|
|
37
|
+
|
|
38
|
+
## Rollback
|
|
39
|
+
|
|
40
|
+
Revert the commit.
|