From 56807434e86a5018ed32a02a865670bb93947192 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sun, 8 Nov 2026 21:51:13 +0100 Subject: [PATCH 1/3] Make the client-abort log test wait on the handler, not on fixed sleeps --- docs/reading-cost.md | 17 +++++++++++++++++ src/http/app.test.ts | 26 +++++++++++++++++++------- todo.md | 7 ------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/docs/reading-cost.md b/docs/reading-cost.md index f2b20c9..e63b9bf 100644 --- a/docs/reading-cost.md +++ b/docs/reading-cost.md @@ -457,3 +457,20 @@ Read most (opens and searches): - `src/auth/gate.ts` 1 - `src/auth/jwt.ts` 1 - `src/server.ts` 1 + +## Reading cost, chunk ac2b7411e145, finished 2026-11-08T20:50:21Z + +105677 new input tokens before the first commit. + +Read most (opens and searches): + +- `README.md` 15 +- `src/http/app-admin.test.ts` 7 +- `todo.md` 5 +- `CHANGELOG.md` 3 +- `ci.sh` 3 +- `examples/README.md` 3 +- `examples/plugins/auth-admin/admin-shared.test.ts` 3 +- `src/plugin-host/conflicts.ts` 3 +- `AGENTS.md` 2 +- `compose.yml` 2 diff --git a/src/http/app.test.ts b/src/http/app.test.ts index 9ec296f..b49918a 100644 --- a/src/http/app.test.ts +++ b/src/http/app.test.ts @@ -169,14 +169,22 @@ test("ctx.log after a client abort doesn't throw: the request log is ended only // deferred until the handler settles (regression for the abort race). let afterCloseOk = false; let afterCloseErr: string | undefined; + const started = Promise.withResolvers(); + const closed = Promise.withResolvers(); + const logged = Promise.withResolvers(); + let calls = 0; const plugin: Plugin = { apiVersion: "1.0.0", id: "slow", routes: [{ handler: async (ctx) => { - await new Promise((r) => setTimeout(r, 120)); // outlasts the client abort below - try { ctx.log.info("after abort", {}); afterCloseOk = true; } // would throw if end() already ran - catch (e) { afterCloseErr = String(e); } + if (calls++ === 0) { + started.resolve(); + await closed.promise; // the response has closed under the running handler + try { ctx.log.info("after abort", {}); afterCloseOk = true; } // would throw if end() already ran + catch (e) { afterCloseErr = String(e); } + logged.resolve(); + } return { json: { ok: true } }; }, method: "GET", @@ -184,16 +192,20 @@ test("ctx.log after a client abort doesn't throw: the request log is ended only }], }; const app = createApp({ log: createLogger({ level: "none" }), plugins: [plugin] }); + // Registered after the host's own listener, so this "close" handler runs after the host's. + app.once("request", (_req, res) => res.on("close", () => closed.resolve())); await new Promise((r) => app.listen(0, r)); t.after(() => app.close()); const base = `http://localhost:${(app.address() as AddressInfo).port}`; - // Abort the request mid-handler (well before the 120ms), forcing res "close" while it still runs. + // Abort the request once the handler is running, forcing res "close" while it still runs. const ac = new AbortController(); - setTimeout(() => ac.abort(), 20); - await assert.rejects(fetch(base + "/slow/go", { signal: ac.signal })); // the client sees the abort + const aborted = assert.rejects(fetch(base + "/slow/go", { signal: ac.signal })); // the client sees the abort + await started.promise; + ac.abort(); + await aborted; - await new Promise((r) => setTimeout(r, 200)); // let the handler finish post-abort + await logged.promise; // the handler finished its post-abort log assert.equal(afterCloseErr, undefined, "ctx.log did not throw after the client disconnected"); assert.ok(afterCloseOk, "the handler logged successfully after close"); diff --git a/todo.md b/todo.md index d83f794..755276f 100644 --- a/todo.md +++ b/todo.md @@ -12,7 +12,6 @@ | ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score | |---|---|---|---|---|---|---|---|---|---|---|---| -| 61 | 0.5.0 | | | **Make the client-abort log test in `src/http/app.test.ts` wait on the handler, not on fixed sleeps.** | 1 | 1 | 1 | 1 | 2 | code health by 2 | 12.0 | | 5 | 0.5.0 | | | **List grants that no installed plugin declares, with a remove action.** | 2 | 2 | 2 | 4 | 4 | 2 by 3 | 2.1 | | 41 | 0.5.0 | | | **Apply a permission save as one Keto call, so it cannot stop part-way.** | 2 | 2 | 2 | 4 | 3 | 2 by 2 | 2.1 | | 19 | 0.6.0 | | question | **Run the documented Docker commands on macOS and fix whatever misbehaves.** | 2 | 2 | 1 | 3 | 3 | 1 by 3 | 3.3 | @@ -26,12 +25,6 @@ ## Details -### 61. Make the client-abort log test in `src/http/app.test.ts` wait on the handler, not on fixed sleeps. - -"ctx.log after a client abort doesn't throw" aborts at 20 ms, lets the handler log at 120 ms and asserts -at 200 ms. On a loaded CI runner the handler hadn't logged by then (PR #226, run 5739), so the gate went -red on unrelated work. Await a promise the handler resolves after it logs instead. - ### 5. List grants that no installed plugin declares, with a remove action. `grantDiff` never revokes an undeclared name, and nothing shows one either, so it can't be audited or -- 2.52.0 From 216089c067c1b5aa59f21dc06cee55c17086553d Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sun, 8 Nov 2026 21:53:53 +0100 Subject: [PATCH 2/3] =?UTF-8?q?Answer=20stability=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=200=20=F0=9F=9F=A1=20(0=20misread);=20drop=20the?= =?UTF-8?q?=20call-count=20guard,=20tighten=20the=20test's=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/http/app.test.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/http/app.test.ts b/src/http/app.test.ts index b49918a..cff1dbe 100644 --- a/src/http/app.test.ts +++ b/src/http/app.test.ts @@ -172,19 +172,16 @@ test("ctx.log after a client abort doesn't throw: the request log is ended only const started = Promise.withResolvers(); const closed = Promise.withResolvers(); const logged = Promise.withResolvers(); - let calls = 0; const plugin: Plugin = { apiVersion: "1.0.0", id: "slow", routes: [{ handler: async (ctx) => { - if (calls++ === 0) { - started.resolve(); - await closed.promise; // the response has closed under the running handler - try { ctx.log.info("after abort", {}); afterCloseOk = true; } // would throw if end() already ran - catch (e) { afterCloseErr = String(e); } - logged.resolve(); - } + started.resolve(); + await closed.promise; + try { ctx.log.info("after abort", {}); afterCloseOk = true; } // would throw if end() already ran + catch (e) { afterCloseErr = String(e); } + logged.resolve(); return { json: { ok: true } }; }, method: "GET", @@ -192,20 +189,20 @@ test("ctx.log after a client abort doesn't throw: the request log is ended only }], }; const app = createApp({ log: createLogger({ level: "none" }), plugins: [plugin] }); - // Registered after the host's own listener, so this "close" handler runs after the host's. + // Added after the host's request listener, so this "close" listener fires after the host's. app.once("request", (_req, res) => res.on("close", () => closed.resolve())); await new Promise((r) => app.listen(0, r)); t.after(() => app.close()); const base = `http://localhost:${(app.address() as AddressInfo).port}`; - // Abort the request once the handler is running, forcing res "close" while it still runs. + // Abort the request mid-handler. const ac = new AbortController(); const aborted = assert.rejects(fetch(base + "/slow/go", { signal: ac.signal })); // the client sees the abort await started.promise; ac.abort(); await aborted; - await logged.promise; // the handler finished its post-abort log + await logged.promise; assert.equal(afterCloseErr, undefined, "ctx.log did not throw after the client disconnected"); assert.ok(afterCloseOk, "the handler logged successfully after close"); -- 2.52.0 From 1cc98017d2c153be876f591f3f5e9177209f6dce Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sun, 8 Nov 2026 21:53:53 +0100 Subject: [PATCH 3/3] =?UTF-8?q?Answer=20prose=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=200=20=F0=9F=9F=A1=20(0=20misread);=20the=20three?= =?UTF-8?q?=20=F0=9F=9F=A2=20nits=20applied=20in=20the=20previous=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 2.52.0