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
23 changes: 23 additions & 0 deletions scripts/check-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,29 @@ for (const text of requiredWorkerCopy) {
if (!worker.includes(text)) failures.push(`Missing worker agent behavior: ${text}`);
}

// The current-product readiness verdict must key off the intake path only.
// The Website Appraisal depends on the D1 email_signups table behind
// /api/signups; the retired Agent Desk's AI binding and agent tables are
// reported as legacy state, never as the current product's readiness. A
// regression that lets the legacy machinery gate /health — or that drops the
// intake table from the check — would make the machine-readable truth lie.
const requiredHealthCopy = [
"surface: APPRAISAL_SURFACE",
"signupsTable",
"checks.db && checks.signupsTable",
"email_signups",
"agentRunsTable",
"usageLimitsTable"
];

for (const text of requiredHealthCopy) {
if (!worker.includes(text)) failures.push(`Missing worker health truth: ${text}`);
}

if (worker.includes("checks.ai && checks.db")) {
failures.push("Worker /health verdict must not gate on the retired Agent Desk AI binding");
}

for (const text of requiredPublicArtifacts) {
// llms.txt and offer.md are mirrors of the same offer contract. A fact must
// appear in BOTH (case-insensitively, since one file may head it while the
Expand Down
71 changes: 71 additions & 0 deletions scripts/test-agent-worker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,77 @@ test("signup handler accepts a bare-domain website with a test email and stores
assert.ok(insert, "signup handler must persist through the existing email_signups path");
assert.equal(insert.values[0], "audit-check+test@example.com");
assert.equal(insert.values[7], "https://example.com");
// The current appraisal intake must label its rows with the current offer,
// never the retired self-serve Agent Desk surface name.
assert.equal(insert.values[1], "website-appraisal", "current intake signups must carry the current-offer source label");
assert.notEqual(insert.values[1], "agent-self-serve", "current intake signups must not carry the retired Agent Desk source label");
});

test("worker /health names the current Website Appraisal surface, not the retired Agent Desk", async () => {
class HealthStatement extends FakeStatement {
async all() {
this.db.calls.push({ method: "all", sql: this.sql, values: this.values });
return { results: [{ name: "email_signups" }, { name: "agent_runs" }, { name: "agent_usage_limits" }] };
}
}

class HealthDB extends FakeDB {
prepare(sql) {
return new HealthStatement(this, sql);
}
}

const res = await worker.fetch(new Request("https://tinystudio.io/health"), { DB: new HealthDB(), AI: {} });
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.service, "tinystudio-io-public");
assert.equal(body.surface, "website-appraisal", "health surface must name the current offer");
assert.notEqual(body.surface, "agent-desk", "health surface must not name the retired Agent Desk");
assert.equal(body.ok, true);
});

test("worker /health verdict keys off the current intake path, not the retired Agent Desk machinery", async () => {
// The current product depends on the D1 email_signups table behind
// /api/signups. The retired Agent Desk's AI binding and agent tables must
// not gate the current product's readiness verdict: a green /health while
// the signup path is broken would be a false positive, and a red /health
// when the appraisal intake is healthy would be a false alarm. The env
// deliberately carries no AI binding — the current product has no model
// dependency.
class HealthStatement extends FakeStatement {
async all() {
this.db.calls.push({ method: "all", sql: this.sql, values: this.values });
return { results: [{ name: "email_signups" }] };
}
}

class HealthDB extends FakeDB {
prepare(sql) {
return new HealthStatement(this, sql);
}
}

const res = await worker.fetch(new Request("https://tinystudio.io/health"), { DB: new HealthDB() });
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.ok, true, "the current product is ready even without the retired Agent Desk machinery");
assert.equal(body.checks.signupsTable, true, "the intake table is present");
assert.equal(body.checks.ai, false, "the retired AI binding is absent");
assert.equal(body.checks.agentRunsTable, false, "the retired agent_runs table is absent");
assert.equal(body.checks.usageLimitsTable, false, "the retired usage-limits table is absent");
});

test("legacy /api/agent-audit still labels its rows with the retired self-serve source", async () => {
const db = new FakeDB();
const ai = new FakeAI(VALID_AGENT_OUTPUT);
const res = await worker.fetch(agentRequest(validBody()), { DB: db, AI: ai });
assert.equal(res.status, 200);
const signupInsert = db.calls.find((call) => call.sql.includes("INSERT INTO email_signups"));
assert.ok(signupInsert, "agent audit must persist through the existing email_signups path");
assert.equal(signupInsert.values[1], "agent-self-serve", "the legacy surface keeps its own source label");
const runInsert = db.calls.find((call) => call.sql.includes("INSERT INTO agent_runs"));
assert.ok(runInsert, "agent audit must record the legacy run");
assert.equal(runInsert.values[2], "agent-self-serve", "the legacy run keeps its own source label");
});

test("worker serves the same-origin font promotion script (render-blocking fix b8f6046e942a)", async () => {
Expand Down
26 changes: 20 additions & 6 deletions src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ const MAX_FIELD_LENGTH = 1800;
const MAX_REQUEST_BYTES = 24000;
const SOFT_AGENT_RUNS_PER_EMAIL_PER_DAY = 5;
const MAX_AGENT_RUNS_PER_IP_PER_DAY = 20;
// The current product's public intake (homepage and /audit) posts to
// /api/signups. Its rows and the public /health surface must be labeled with
// the current offer — The Website Appraisal — never the retired self-serve
// Agent Desk, which keeps its own "agent-self-serve" labels on the legacy
// /api/agent-audit path.
const APPRAISAL_SURFACE = "website-appraisal";
const CURRENCY_AMOUNT_PATTERN = String.raw`(?:(?:₹|\$|€|£|inr|usd|us\$|aud|cad|sgd|gbp|eur|rs\.?|rupees?)\s*\d[\d,.]*(?:\s*(?:k|lakh|lakhs|l|cr))?|\d[\d,.]*\s*(?:inr|usd|aud|cad|sgd|gbp|eur|rupees?))`;
const METRIC_VALUE_PATTERN = String.raw`(?:${CURRENCY_AMOUNT_PATTERN}|\b\d[\d,.]*\b)`;
const WEEKLY_METRIC_LABELS = [
Expand Down Expand Up @@ -359,7 +365,7 @@ async function signupResponse(request, env, url) {
return jsonResponse({ ok: false, error: "invalid_email" }, { status: 400 });
}

await saveEmailSignup(request, env, url, email, "agent-self-serve", website);
await saveEmailSignup(request, env, url, email, APPRAISAL_SURFACE, website);

if (wantsHtmlRedirect(request)) {
return htmlRedirect(url, "saved");
Expand Down Expand Up @@ -1206,34 +1212,42 @@ async function agentAuditResponse(request, env, url) {
}

async function healthResponse(env) {
// The current product (The Website Appraisal) depends on exactly one
// backend: the D1 `email_signups` table behind /api/signups. The retired
// self-serve Agent Desk's machinery (the AI binding and its own agent_runs /
// agent_usage_limits tables) is reported as legacy state, never as the
// current product's readiness: /health must not go red when the appraisal
// intake is healthy, nor green when the signup path is broken.
const checks = {
ai: Boolean(env.AI),
db: Boolean(env.DB),
signupsTable: false,
ai: Boolean(env.AI),
agentRunsTable: false,
usageLimitsTable: false
};

if (env.DB) {
try {
const tableResult = await env.DB.prepare(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('agent_runs', 'agent_usage_limits')"
"SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('email_signups', 'agent_runs', 'agent_usage_limits')"
).all();
const tables = new Set((tableResult.results || []).map((row) => row.name));
checks.signupsTable = tables.has("email_signups");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify the intake schema before returning healthy

When email_signups exists but migration 0004_signup_website.sql has not been applied, this name-only check sets signupsTable to true and /health returns 200, although saveEmailSignup immediately fails because its insert references the missing website column. This recreates the green-health/broken-intake state the change is intended to prevent; validate the required columns or migration version rather than only the table name.

Useful? React with 👍 / 👎.

checks.agentRunsTable = tables.has("agent_runs");
checks.usageLimitsTable = tables.has("agent_usage_limits");
} catch (error) {
console.warn("tinystudio_health_check_failed", error.message || "health check failed");
}
}

const ok = checks.ai && checks.db && checks.agentRunsTable && checks.usageLimitsTable;
// The current-product readiness verdict keys off the intake path only.
const ok = checks.db && checks.signupsTable;

return jsonResponse(
{
ok,
service: "tinystudio-io-public",
surface: "agent-desk",
ai: checks.ai ? "configured" : "missing",
surface: APPRAISAL_SURFACE,
db: checks.db ? "configured" : "missing",
checks,
routes: ["tinystudio.io", "www.tinystudio.io", "app.tinystudio.io", "api.tinystudio.io"]
Expand Down
Loading