fix(utils): throw AppError from app-log-files and verified-file guards - #1853
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
P1 regression gap: #1792/ADR 0010 require actionable recovery hints, but all tightened/new tests call |
|
CI note: |
|
Out of draft. CI is green on the actual head Review nits recorded but deliberately not fixed here: |
|
Re-reviewed exact head |
The symlink, not-a-regular-file, identity-changed and identity-race guards threw plain Error, so they surfaced as UNKNOWN with a misleading hint and tests could only assert them by message. They are now COMMAND_FAILED with a recovery hint (ADR 0010); the affected tests assert through assertThrowsAppError, and the two race guards gain planted-interleaving coverage. Closes #1792
The tightened assertions supplied only code and message, and the helper did not look at the hint, so deleting either hint constant left every targeted test green — vacuous for the half of #1792 that ADR 0010 actually cares about. assertThrowsAppError/assertRejectsAppError now accept a hint, checked against normalizeError's view so a dropped hint surfaces as the misleading per-code default rather than passing, and each guard pins its exact text as a literal (importing the constant would compare it to itself).
84e4632 to
8bf8695
Compare
|
Blocker accepted — the finding was correct, and the proof was vacuous exactly where the issue's value is. Fixed in What was wrong: every assertion supplied Fix:
Planted-red proof (three separate plants, each restored afterwards): (a) drop (b) reword (c) drop the hint from the app-log symlink guard — Restored: Still not fixed, deliberately: the raw |
|
Re-reviewed exact head |
|
Summary
Four file-identity guards in
src/utilsthrew plainError, so an agent hitting one sawUNKNOWNwith the generic "unexpected internal error" hint, and tests could only pin them by message regex (ADR 0010 §1; #1781 B4):app-log-files.ts— app.log path is a symlinkError("App-log file must not be a symbolic link: …")AppError('COMMAND_FAILED', same message, { hint: NOT_REGULAR_FILE_HINT })verified-file.ts— final path is not a regular fileError("Final path must be a regular file: …")COMMAND_FAILED+NOT_REGULAR_FILE_HINTverified-file.ts— identity changed between open and post-open lstatError("Final file identity changed …")COMMAND_FAILED+ concurrent-replacement hintverified-file.ts— create lost the identity race twiceError("… could not be opened without an identity race")COMMAND_FAILED+ concurrent-replacement hintCOMMAND_FAILEDbecause these are runtime failures of well-formed requests on daemon-derived paths (not user input → notINVALID_ARGS; not a capability gap or contention code). Hints answer "what next": remove the symlink/special file at this path and retry / stop the concurrent writer, then retry. The two "not a regular file" guards share one exported hint constant (NOT_REGULAR_FILE_HINTinverified-file.ts) — same failure mode seen from two sides, per ADR 0010 §3.Small structural side-effect: both
lstatwrappers used to throw from inside thetrywhosecatchkeys onerror.code === 'ENOENT'; anAppErroralso carries.code, so the guard now sits outside that catch (lstatIfPresent, shared by both files) rather than relying on'COMMAND_FAILED' !== 'ENOENT'.Callers checked:
app-log.ts,durable-capture-resource-store.ts(isMissingFilekeys on errno, unaffected),platform-runtime-app-log-process.ts/platform-runtime-network-host.ts(error instanceof Error ? error.message—AppError extends Error). None depend on the plain-Errorshape.Closes #1792.
Validation
src/daemon/__tests__/app-log.test.ts(3 cases),src/utils/__tests__/app-log-files.test.ts(1),src/utils/__tests__/verified-file.test.ts(3) tightened fromtoThrow(<string>)toassertThrowsAppError(…, { code: 'COMMAND_FAILED', message }).lstatis answered with a different file (identity changed), andopenSynckeeps returningEEXIST(identity race exhausted).git stashthe two production files, run the three test files):Tests 14 passed (14).pnpm check:affected --run: 166 affected test files / 923 tests green, lint/typecheck/format/layering clean.Touched files: 5 (2 production, 3 tests). Scope stayed on the two utils. No docs/skills change: no command surface moved; error
code/hintreach every surface throughnormalizeError.