Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions desktop/src/features/providers/lib/providerAvailability.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import assert from "node:assert/strict";
import test from "node:test";

import {
canQuoteJobs,
deriveProviderAvailability,
providerAvailabilityCaption,
refreshIntervalForLease,
} from "./providerAvailability.ts";

/**
* Covers buzz#84's freshness invariant (toon-meta#262 decision 12): never
* advertise a provider as available for longer than it stays routable on the
* connector, and a dropped session must cost reachability only — never the
* channel, balance, nonce position or reputation. Pure-function tests in the
* `huddleFeeQuote` mold — no DOM, no live connector.
*/

test("the refresh cadence mirrors the mesh's 45s/120s reference ratio", () => {
assert.equal(refreshIntervalForLease(120_000), 45_000);
assert.equal(refreshIntervalForLease(60_000), 22_500);
});

test("advertising off reads as unadvertised regardless of session state", () => {
const state = deriveProviderAvailability({
advertisingEnabled: false,
sessionConnected: true,
lastAdvertisedAtMs: 1_000,
nowMs: 1_000,
sessionLeaseTtlMs: 120_000,
});

assert.deepEqual(state, { kind: "unadvertised" });
assert.equal(canQuoteJobs(state), false);
});

test("advertising on with no publish yet is pending, not available", () => {
const state = deriveProviderAvailability({
advertisingEnabled: true,
sessionConnected: true,
lastAdvertisedAtMs: null,
nowMs: 1_000,
sessionLeaseTtlMs: 120_000,
});

assert.deepEqual(state, { kind: "pending" });
assert.equal(canQuoteJobs(state), false);
});

test("a live, fresh advertisement is available and reports its next refresh and expiry", () => {
const state = deriveProviderAvailability({
advertisingEnabled: true,
sessionConnected: true,
lastAdvertisedAtMs: 100_000,
nowMs: 110_000,
sessionLeaseTtlMs: 120_000,
});

assert.deepEqual(state, {
kind: "available",
refreshDueAtMs: 145_000,
expiresAtMs: 220_000,
});
assert.equal(canQuoteJobs(state), true);
});

test("a dropped session reads as stale immediately, mid-lease — offline costs reachability only", () => {
const state = deriveProviderAvailability({
advertisingEnabled: true,
sessionConnected: false,
lastAdvertisedAtMs: 100_000,
nowMs: 100_500,
sessionLeaseTtlMs: 120_000,
});

assert.deepEqual(state, { kind: "stale" });
assert.equal(canQuoteJobs(state), false);
});

test("a missed refresh past the lease reads as stale — never advertise longer than routable", () => {
const state = deriveProviderAvailability({
advertisingEnabled: true,
sessionConnected: true,
lastAdvertisedAtMs: 0,
nowMs: 120_000,
sessionLeaseTtlMs: 120_000,
});

assert.deepEqual(state, { kind: "stale" });
assert.equal(canQuoteJobs(state), false);
});

test("a lease boundary reads as fresh right up to, but not including, expiry", () => {
const state = deriveProviderAvailability({
advertisingEnabled: true,
sessionConnected: true,
lastAdvertisedAtMs: 0,
nowMs: 119_999,
sessionLeaseTtlMs: 120_000,
});

assert.equal(state.kind, "available");
});

test("caption text is honest and non-null only where there is something to say", () => {
assert.equal(providerAvailabilityCaption({ kind: "unadvertised" }), null);
assert.match(providerAvailabilityCaption({ kind: "pending" }), /Publishing/);
assert.match(
providerAvailabilityCaption({
kind: "available",
refreshDueAtMs: 0,
expiresAtMs: 0,
}),
/available/i,
);
assert.match(
providerAvailabilityCaption({ kind: "stale" }),
/not currently reachable/i,
);
});
122 changes: 122 additions & 0 deletions desktop/src/features/providers/lib/providerAvailability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* The provider-surface freshness invariant (buzz#84, toon-meta#262 decision
* 12): "the socket is the lease." A route exists exactly as long as the
* authenticated connector session, so an advertisement must never claim
* availability for longer than that session stays routable — or a buyer can
* pay for a job that cannot land.
*
* Two failure directions are asymmetric and the code below only ever errs
* toward the cheap one: reading a live provider as unavailable costs a
* little missed work; reading an unreachable one as available costs a buyer
* a rejected payment. A dropped session therefore reads as `stale`
* immediately, without waiting out the lease — and going offline costs
* reachability only, never the channel, balance, nonce position or
* reputation (nothing here touches any of those).
*
* `sessionLeaseTtlMs` is a caller-supplied parameter, not a constant defined
* here: connector#698 exports it as `SESSION_LEASE_BACKSTOP_TTL` and its own
* doc comment says this ticket should read it from there rather than
* duplicating the value. As of this writing that export is not yet present
* in any published `@toon-protocol/client`/`@toon-protocol/connector`
* release this repo can pin, so there is no live caller yet — this module is
* ready for one once the value is available, mirroring the
* `agentNetworkFlow.ts` precedent for shipping pure domain logic ahead of
* its data source.
*/

/** What the provider surface knows about its own current advertisement. */
export type ProviderAvailability =
/** The owner has not turned provider advertising on for this agent. */
| { kind: "unadvertised" }
/** Advertising is on, but no publish has landed yet — nothing to quote from. */
| { kind: "pending" }
/** Advertised, session live, and inside the connector's lease window. */
| { kind: "available"; refreshDueAtMs: number; expiresAtMs: number }
/** Session dropped, or the lease lapsed without a refresh — do not quote. */
| { kind: "stale" };

/**
* The mesh's own reference cadence: `mesh_llm/coordinator.rs`
* `STATUS_PUBLISH_INTERVAL` republishes every 45s within the 120s freshness
* window `mesh_llm/discovery.rs` `STATUS_FRESHNESS_SECS` allows.
*/
const MESH_REFRESH_INTERVAL_SECS = 45;
const MESH_FRESHNESS_WINDOW_SECS = 120;

/**
* How long after publishing an advertisement to refresh it, given the
* connector's session lease. Scaled at the mesh's own ratio so a refresh
* always lands well inside the lease rather than racing its edge.
*/
export function refreshIntervalForLease(sessionLeaseTtlMs: number): number {
return Math.floor(
(sessionLeaseTtlMs * MESH_REFRESH_INTERVAL_SECS) /
MESH_FRESHNESS_WINDOW_SECS,
);
}

/**
* Derive whether this agent's provider listing is currently honest to show
* as available, never throwing and never guessing in the buyer's favor.
*/
export function deriveProviderAvailability(input: {
/** Whether the owner has turned provider advertising on for this agent. */
advertisingEnabled: boolean;
/** Whether the connector session (the lease) is currently up. */
sessionConnected: boolean;
/** When the advertisement was last published, or null if never. */
lastAdvertisedAtMs: number | null;
nowMs: number;
/** The connector's session lease TTL — see the module doc for its source. */
sessionLeaseTtlMs: number;
}): ProviderAvailability {
const {
advertisingEnabled,
sessionConnected,
lastAdvertisedAtMs,
nowMs,
sessionLeaseTtlMs,
} = input;

if (!advertisingEnabled) return { kind: "unadvertised" };
if (lastAdvertisedAtMs === null) return { kind: "pending" };
// Offline costs reachability only, and it costs it immediately — waiting
// out the lease on a dropped session would advertise a provider that
// cannot actually take the job.
if (!sessionConnected) return { kind: "stale" };

const expiresAtMs = lastAdvertisedAtMs + sessionLeaseTtlMs;
if (nowMs >= expiresAtMs) return { kind: "stale" };

return {
kind: "available",
refreshDueAtMs:
lastAdvertisedAtMs + refreshIntervalForLease(sessionLeaseTtlMs),
expiresAtMs,
};
}

/**
* Whether this agent may currently produce a paid quote (buzz#84 gotcha:
* quoting costs money, so nothing should auto-quote outside a state that is
* both advertised and honestly reachable right now).
*/
export function canQuoteJobs(availability: ProviderAvailability): boolean {
return availability.kind === "available";
}

/** The caption a provider settings surface shows for each state. */
export function providerAvailabilityCaption(
availability: ProviderAvailability,
): string | null {
switch (availability.kind) {
case "unadvertised":
return null;
case "pending":
return "Publishing this agent's provider listing…";
case "available":
return "Advertised as available to buyers on the open job market.";
case "stale":
return "Not currently reachable — this agent's provider listing will not accept new jobs until its connector session reconnects.";
}
}
Loading