From f112011727c3cbafc8cff966392c06124ea5e812 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 22:19:49 +0100 Subject: [PATCH] C3: the forge module reads failed-steps' fill through the bundle's state call mesh/state.info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §623, A-214 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/gitea/failedsteps.ts | 19 ++++++++++++++----- modules/gitea/test/failedsteps.test.ts | 6 +++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/modules/gitea/failedsteps.ts b/modules/gitea/failedsteps.ts index 2177579eb..84473b12f 100644 --- a/modules/gitea/failedsteps.ts +++ b/modules/gitea/failedsteps.ts @@ -14,6 +14,7 @@ // judged on each `branch-moved` and each pull request's close (Heads, below). import type { State } from "@novox/mesh-sdk/state"; +import { askRuntime } from "@novox/mesh-sdk/runtime"; import { failedStepsByModuleFingerprintStepKey as failedKey, type FailedStep, type PullClosedEvent, type PullMergedEvent, type PullUpdatedEvent, } from "@novox/mesh-delivery-contracts-git"; @@ -21,8 +22,14 @@ import type { StepEndedEvent } from "@novox/mesh-delivery-contracts-mesh-pipelin /** The key prefix of the commits' fingerprints (§487). */ export const SEEN = "seen."; -/** The template state's cap (design 47 *The runner's kept data*): the fill is shown against it. */ -export const CAP_BYTES = 64 * 1024 * 1024; +/** The bucket's fill against its caps as the bundle's state call `mesh/state.info` answers it (design 47 G §623). */ +export interface StateFill { + bytes: number; + keys: number; + max_bytes: number; +} +/** The fill of the state `failed-steps`, asked of the runtime (§623). */ +export const askFill = (): Promise => askRuntime("mesh/state.info", { state: "failed-steps" }) as Promise; /** How long a `seen` key is kept after its `step-ended`, and how many go on one event (§581). */ export const SEEN_DAYS = 30; @@ -57,7 +64,9 @@ export class FailedSteps { constructor(private readonly st: State, private readonly log: (line: string) => void = () => {}, /** Whether a commit is now a watched branch's head or an open pull request's head. */ - private readonly current: (commit: string) => boolean = () => false) {} + private readonly current: (commit: string) => boolean = () => false, + /** The state's fill against its caps: the bundle's state call (§623). */ + private readonly fillOf: () => Promise = askFill) {} private turn(act: () => Promise): Promise { const r = this.chain.then(act, act); @@ -166,9 +175,9 @@ export class FailedSteps { held(commit?: string): Promise<{ failed: FailedStep[]; seen: Seen[]; fill: { keys: number; bytes: number; cap: number } }> { return this.turn(async () => { const { failed, seen } = await this.all(); - const bytes = [...failed.values(), ...seen.values()].reduce((n, v) => n + JSON.stringify(v).length, 0); + const info = await this.fillOf(); return { failed: [...failed.values()].filter((f) => !commit || f.commits.includes(commit)).sort((a, b) => (a.run < b.run ? -1 : 1)), - seen: [...seen.values()].filter((s) => !commit || s.commit === commit), fill: { keys: failed.size + seen.size, bytes, cap: CAP_BYTES } }; + seen: [...seen.values()].filter((s) => !commit || s.commit === commit), fill: { keys: info.keys, bytes: info.bytes, cap: info.max_bytes } }; }); } diff --git a/modules/gitea/test/failedsteps.test.ts b/modules/gitea/test/failedsteps.test.ts index 8ad58bc9c..07e8f32dc 100644 --- a/modules/gitea/test/failedsteps.test.ts +++ b/modules/gitea/test/failedsteps.test.ts @@ -82,14 +82,14 @@ test("git_failed_steps shows what is held and its fill, and git_failed_steps_dro const { failedStepsTools } = await import("../tools/failedsteps.js"); const { runAs } = await import("@novox/mesh-sdk/callers"); const { st } = memory(); - const f = new FailedSteps(st as any); + const f = new FailedSteps(st as any, () => {}, () => false, async () => ({ bytes: 4321, keys: 2, max_bytes: 64 * 1024 * 1024 })); await f.stepEnded(ended(C2, "failed")); const acts: any[] = []; let recorded = true; const [read, drop] = failedStepsTools(f, async (a) => { if (!recorded) throw new Error("no hand-act log"); acts.push(a); }); const shown: any = await read.run({}); - assert.deepEqual([shown.failed.map((x: any) => [x.key, x.run]), shown.seen.length, shown.fill.keys, shown.fill.cap], - [[["nats.fp-1.unit", "main.7.nats-22"]], 1, 2, 64 * 1024 * 1024]); + assert.deepEqual([shown.failed.map((x: any) => [x.key, x.run]), shown.seen.length, shown.fill.keys, shown.fill.bytes, shown.fill.cap], + [[["nats.fp-1.unit", "main.7.nats-22"]], 1, 2, 4321, 64 * 1024 * 1024], "the fill is the state call's answer, not counted from the keys"); assert.equal(((await read.run({ commit: C1 })) as any).failed.length, 0, "a commit asked for filters"); recorded = false; await assert.rejects(runAs({ "mesh/caller": "operator" }, {}, () => drop.run({ key: "nats.fp-1.unit", why: "stale" })), /hand act was not recorded, so nothing was dropped/);