mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
Merge remote-tracking branch 'origin/main' into feat/pulse-hourly-mean-complete
This commit is contained in:
commit
13b8920dd5
88 files changed
+5627
-772
No files matched your search
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Structural guard (#1126): `reasoning_effort` reaches a model only through
|
||||
* the setting the resolver bound to the provider instance.
|
||||
*
|
||||
* Default must send nothing, so the key may not appear on any wire except as
|
||||
* the bound value, and only the two clients whose endpoint a person chose may
|
||||
* carry it. Pinned here:
|
||||
*
|
||||
* 1. the key appears in exactly the Local client and the OpenAI client,
|
||||
* once each, and only as `reasoning_effort: this.reasoningEffort`;
|
||||
* 2. nothing but the binder assigns `.reasoningEffort`;
|
||||
* 3. every exported resolver in `provider.ts` calls `bindReasoningEffort`.
|
||||
*
|
||||
* That clients are constructed only in `provider.ts` (so a resolver always
|
||||
* binds them) is frozen by `ai-call-timeout-guard.test.ts`.
|
||||
*
|
||||
* Its limit: it reads source text. A body built under a computed key, or a
|
||||
* resolver that binds through an alias, would slip it.
|
||||
* `reasoning-effort.test.ts` and `provider-reasoning-effort.test.ts` prove
|
||||
* the behaviour on the wire.
|
||||
*
|
||||
* Mutation check: hard-code `reasoning_effort: "none"` into either client,
|
||||
* set `client.reasoningEffort = ...` in a route, or drop `bindReasoningEffort`
|
||||
* from `resolveProviderForTest`: each one goes red here.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { walkSourceFiles } from "./helpers/source-files";
|
||||
|
||||
const SRC = join(process.cwd(), "src");
|
||||
|
||||
function stripComments(source: string): string {
|
||||
return source
|
||||
.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||
.replace(/(^|[^:])\/\/.*$/gm, "$1");
|
||||
}
|
||||
|
||||
function code(rel: string): string {
|
||||
return stripComments(readFileSync(join(SRC, rel), "utf8"));
|
||||
}
|
||||
|
||||
const files = walkSourceFiles(SRC, { floor: 3000 })
|
||||
.filter((p) => !p.startsWith("generated/"))
|
||||
.filter((p) => !p.includes("__tests__"))
|
||||
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"));
|
||||
|
||||
describe("reasoning_effort on the wire", () => {
|
||||
// As an object key: the published descriptions name the field in prose.
|
||||
const KEY = /\breasoning_effort["']?\s*:/g;
|
||||
const sites = files.filter((rel) => new RegExp(KEY).test(code(rel)));
|
||||
|
||||
it("appears in the Local and the OpenAI client only", () => {
|
||||
expect(sites.sort()).toEqual([
|
||||
"lib/ai/local-client.ts",
|
||||
"lib/ai/openai-client.ts",
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(["lib/ai/local-client.ts", "lib/ai/openai-client.ts"])(
|
||||
"%s sends it once, as the bound value",
|
||||
(rel) => {
|
||||
const source = code(rel);
|
||||
expect(source.match(new RegExp(KEY))?.length).toBe(1);
|
||||
expect(source).toMatch(
|
||||
/\breasoning_effort\s*:\s*this\s*\.\s*reasoningEffort\b/,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
/** Where `.reasoningEffort` may be assigned, and why. */
|
||||
const ASSIGNMENT_SITES: Record<string, string> = {
|
||||
"lib/ai/reasoning-effort.ts":
|
||||
"The binder, the one place a provider instance gets its value.",
|
||||
"lib/ai/provider-chain.ts":
|
||||
"Writes the stored chain entry, not a provider instance.",
|
||||
};
|
||||
|
||||
describe("only the binder assigns it to a provider", () => {
|
||||
it("names every assignment site, with a reason", () => {
|
||||
const sites = files.filter((rel) =>
|
||||
/\.\s*reasoningEffort\s*=(?!=)/.test(code(rel)),
|
||||
);
|
||||
expect(sites.sort()).toEqual(Object.keys(ASSIGNMENT_SITES).sort());
|
||||
});
|
||||
});
|
||||
|
||||
describe("every exported resolver binds it", () => {
|
||||
const source = code("lib/ai/provider.ts");
|
||||
const resolvers = source
|
||||
.split(/\nexport\s+/)
|
||||
.map((chunk) => {
|
||||
const m = /^async\s+function\s+(\w+)\s*\(/.exec(chunk);
|
||||
if (!m) return null;
|
||||
const header = chunk.slice(0, chunk.indexOf("{\n"));
|
||||
if (
|
||||
!/Promise<\s*(?:AIProvider|ProviderChainResolved\[\])\s*>/.test(header)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const end = chunk.indexOf("\n}\n");
|
||||
return { name: m[1], body: chunk.slice(header.length, end) };
|
||||
})
|
||||
.filter((r): r is { name: string; body: string } => r !== null);
|
||||
|
||||
it("finds the resolvers (an empty set is a failure, not a pass)", () => {
|
||||
expect(resolvers.map((r) => r.name).sort()).toEqual([
|
||||
"resolveProvider",
|
||||
"resolveProviderChain",
|
||||
"resolveProviderForTest",
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(resolvers.map((r) => r.name))(
|
||||
"%s calls bindReasoningEffort",
|
||||
(name) => {
|
||||
const resolver = resolvers.find((r) => r.name === name);
|
||||
expect(resolver?.body ?? "").toMatch(/\bbindReasoningEffort\s*\(/);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Structural guard: the runtime image puts the @napi-rs/canvas native binary
|
||||
* of the version the app pins next to that version's loader.
|
||||
*
|
||||
* ## The failure this freezes (GitHub #1124)
|
||||
*
|
||||
* The pnpm store holds two canvas versions: the one in package.json and an
|
||||
* older one that pdf-parse's own pdfjs-dist pulls in. The runner stage picked
|
||||
* the musl binary with `find | head -1`, got the old one, and copied it into
|
||||
* the new loader's directory, where the loader looks first. PDF rendering
|
||||
* still worked, so nothing looked wrong; `loadImage` called an
|
||||
* `Image.decode` the old binary lacks, and every preview thumbnail failed
|
||||
* with "image.decode is not a function" on both architectures.
|
||||
*
|
||||
* ## What it proves and what it does not
|
||||
*
|
||||
* The builder records the resolved canvas version, the runner hoists by that
|
||||
* name, and the build stops when the copied binary's package is a different
|
||||
* version. Whether the binary loads is proved when the image runs; this file
|
||||
* reads the Dockerfile only.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const DOCKERFILE = readFileSync(join(process.cwd(), "Dockerfile"), "utf8");
|
||||
|
||||
function stage(name: string): string {
|
||||
const start = DOCKERFILE.search(new RegExp(`^FROM .* AS ${name}$`, "m"));
|
||||
expect(start, `the Dockerfile has a ${name} stage`).toBeGreaterThan(0);
|
||||
const next = DOCKERFILE.slice(start + 1).search(/^FROM /m);
|
||||
return next < 0
|
||||
? DOCKERFILE.slice(start)
|
||||
: DOCKERFILE.slice(start, start + 1 + next);
|
||||
}
|
||||
|
||||
describe("canvas binary hoist", () => {
|
||||
it("records the resolved canvas version in the builder", () => {
|
||||
expect(stage("builder")).toMatch(
|
||||
/require\('\/app\/node_modules\/@napi-rs\/canvas\/package\.json'\)\.version"[\s\\]*>\s*\/app\/\.canvas-version/,
|
||||
);
|
||||
});
|
||||
|
||||
it("hoists the loader and the musl binary of that version only", () => {
|
||||
const runner = stage("runner");
|
||||
expect(runner).toContain(
|
||||
"COPY --from=builder /app/.canvas-version /tmp/.canvas-version",
|
||||
);
|
||||
expect(runner).toContain(
|
||||
'CANVAS_DIR="/app/node_modules/.pnpm/@napi-rs+canvas@${CANVAS_VERSION}/',
|
||||
);
|
||||
expect(runner).toContain("canvas-linux-*-musl@${CANVAS_VERSION}/");
|
||||
// A version-blind pick is the defect: any `canvas…@*` glob decides by
|
||||
// readdir order which of the store's copies wins.
|
||||
expect(runner).not.toMatch(/@napi-rs\+canvas[^"'\s]*@\*/);
|
||||
});
|
||||
|
||||
it("fails the build when the copied binary is another version", () => {
|
||||
const runner = stage("runner");
|
||||
expect(runner).toMatch(
|
||||
/\[ "\$BIN_VERSION" = "\$CANVAS_VERSION" \] \|\| \{[^}]*exit 1; \}/,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user