fix(smoke): use scoped PostHog live bindings

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-20 21:20:02 +00:00
1 parent eb962ef283
commit 0b2759fcca
4 files changed
+99 -27

No files matched your search

+9 -7
View File
@@ -1032,15 +1032,17 @@ uses a live vendor OAuth flow and creates a short-lived connection plus one
fresh-run proof issue.
```sh
PAPERCLIP_E2E_BASE_URL=https://paperclip.example.test \
PAPERCLIP_E2E_EMAIL=operator@example.test \
PAPERCLIP_DEV_LOGIN_PASSWORD='<environment-delivered>' \
POSTHOG_PROJECT_ID=483530 \
pnpm smoke:posthog-live
INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL=operator@example.test \
INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD='<environment-delivered>' \
INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID=483530 \
pnpm smoke:posthog-live https://paperclip.example.test
```
The command fails before browser launch unless all four variables are present,
and never prints their values. It creates no trace, video, or HAR, begins
The command fails before browser launch unless all three integration bindings
are present, and never prints their values. A Paperclip heartbeat derives the
target origin from its injected `PAPERCLIP_API_URL`; the positional URL (or
`--base-url <url>`) selects the running instance for a manual invocation. It
creates no trace, video, or HAR, begins
screenshots only after OAuth returns to Paperclip, enables read actions only,
installs the connection on `CodexCoderPro` only, runs `project-get` with `{}`
from the board Test panel and a fresh agent run, then removes the connection.
+36 -10
View File
@@ -1,9 +1,9 @@
const REQUIRED_ENVIRONMENT = [
"PAPERCLIP_E2E_BASE_URL",
"PAPERCLIP_E2E_EMAIL",
"PAPERCLIP_DEV_LOGIN_PASSWORD",
"POSTHOG_PROJECT_ID",
"INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL",
"INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD",
"INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID",
];
const REQUIRED_PROJECT_ID = "483530";
export class PosthogLivePreflightError extends Error {
constructor(code, details = {}) {
@@ -19,15 +19,31 @@ function requiredValue(environment, key) {
return typeof value === "string" ? value.trim() : "";
}
export function preflightPosthogLive(environment = process.env) {
export function parsePosthogLiveArguments(args = []) {
if (args.length === 0) return {};
if (args.length === 1 && !args[0].startsWith("-")) return { baseUrl: args[0] };
if (args.length === 2 && args[0] === "--base-url" && args[1].trim()) {
return { baseUrl: args[1] };
}
throw new PosthogLivePreflightError("invalid_arguments");
}
export function preflightPosthogLive(environment = process.env, options = {}) {
const missing = REQUIRED_ENVIRONMENT.filter((key) => requiredValue(environment, key) === "");
if (missing.length > 0) {
throw new PosthogLivePreflightError("missing_environment", { missing });
}
const baseUrlValue = typeof options.baseUrl === "string" && options.baseUrl.trim()
? options.baseUrl.trim()
: requiredValue(environment, "PAPERCLIP_API_URL");
if (!baseUrlValue) {
throw new PosthogLivePreflightError("missing_base_url");
}
let baseUrl;
try {
baseUrl = new URL(requiredValue(environment, "PAPERCLIP_E2E_BASE_URL"));
baseUrl = new URL(baseUrlValue);
} catch {
throw new PosthogLivePreflightError("invalid_base_url");
}
@@ -41,11 +57,14 @@ export function preflightPosthogLive(environment = process.env) {
}
baseUrl.pathname = baseUrl.pathname.replace(/\/+$/, "") || "/";
const projectId = requiredValue(environment, "POSTHOG_PROJECT_ID");
const projectId = requiredValue(environment, "INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID");
if (!/^\d+$/.test(projectId)) {
throw new PosthogLivePreflightError("invalid_project_id");
}
const email = requiredValue(environment, "PAPERCLIP_E2E_EMAIL");
if (projectId !== REQUIRED_PROJECT_ID) {
throw new PosthogLivePreflightError("unexpected_project_id");
}
const email = requiredValue(environment, "INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL");
if (!email.includes("@")) {
throw new PosthogLivePreflightError("invalid_email");
}
@@ -53,17 +72,18 @@ export function preflightPosthogLive(environment = process.env) {
return {
baseUrl: baseUrl.origin,
email,
password: environment.PAPERCLIP_DEV_LOGIN_PASSWORD,
password: environment.INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD,
projectId,
};
}
export async function preparePosthogLiveSmoke({
environment = process.env,
baseUrl,
fetchImpl = globalThis.fetch,
loadBrowser,
}) {
const config = preflightPosthogLive(environment);
const config = preflightPosthogLive(environment, { baseUrl });
let response;
try {
response = await fetchImpl(new URL("/api/health", config.baseUrl), {
@@ -217,5 +237,11 @@ export function preflightFailureMessage(error) {
if (error.code === "health_http_error") {
return `PostHog live smoke preflight failed: /api/health returned HTTP ${error.details.status}.`;
}
if (error.code === "missing_base_url") {
return "PostHog live smoke preflight failed: pass the target Paperclip URL or run inside a Paperclip heartbeat.";
}
if (error.code === "invalid_arguments") {
return "PostHog live smoke preflight failed: expected an optional Paperclip URL or --base-url <url>.";
}
return `PostHog live smoke preflight failed: ${error.code}.`;
}
+3
View File
@@ -5,6 +5,7 @@ import path from "node:path";
import {
assertSanitizedEvidence,
extractProjectSummary,
parsePosthogLiveArguments,
parseSanitizedAgentProof,
PosthogLivePreflightError,
preflightFailureMessage,
@@ -776,7 +777,9 @@ async function runSmoke({ config, chromium }) {
async function main() {
let prepared;
try {
const options = parsePosthogLiveArguments(process.argv.slice(2));
prepared = await preparePosthogLiveSmoke({
baseUrl: options.baseUrl,
loadBrowser: () => import("@playwright/test"),
});
} catch (error) {
+51 -10
View File
@@ -3,6 +3,7 @@ import test from "node:test";
import {
assertSanitizedEvidence,
extractProjectSummary,
parsePosthogLiveArguments,
parseSanitizedAgentProof,
PosthogLivePreflightError,
preflightPosthogLive,
@@ -10,22 +11,24 @@ import {
} from "./posthog-live-lib.mjs";
const COMPLETE_ENV = {
PAPERCLIP_E2E_BASE_URL: "https://paperclip.example.test",
PAPERCLIP_E2E_EMAIL: "operator@example.test",
PAPERCLIP_DEV_LOGIN_PASSWORD: "not-a-real-password",
POSTHOG_PROJECT_ID: "483530",
PAPERCLIP_API_URL: "https://paperclip.example.test/api",
INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL: "operator@example.test",
INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD: "not-a-real-password",
INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID: "483530",
};
test("preflight reports only missing binding names", () => {
assert.throws(
() => preflightPosthogLive({ PAPERCLIP_DEV_LOGIN_PASSWORD: "present" }),
() => preflightPosthogLive({
PAPERCLIP_API_URL: "https://paperclip.example.test/api",
INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD: "present",
}),
(error) => {
assert.ok(error instanceof PosthogLivePreflightError);
assert.equal(error.code, "missing_environment");
assert.deepEqual(error.details.missing, [
"PAPERCLIP_E2E_BASE_URL",
"PAPERCLIP_E2E_EMAIL",
"POSTHOG_PROJECT_ID",
"INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL",
"INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID",
]);
assert.doesNotMatch(error.message, /present/);
return true;
@@ -40,16 +43,54 @@ test("preflight rejects credential-bearing and non-HTTPS remote URLs", () => {
"http://example.test",
]) {
assert.throws(
() => preflightPosthogLive({ ...COMPLETE_ENV, PAPERCLIP_E2E_BASE_URL: baseUrl }),
() => preflightPosthogLive({ ...COMPLETE_ENV, PAPERCLIP_API_URL: baseUrl }),
(error) => error instanceof PosthogLivePreflightError && error.code === "unsafe_base_url",
);
}
assert.equal(
preflightPosthogLive({ ...COMPLETE_ENV, PAPERCLIP_E2E_BASE_URL: "http://127.0.0.1:3100" }).baseUrl,
preflightPosthogLive(COMPLETE_ENV, { baseUrl: "http://127.0.0.1:3100" }).baseUrl,
"http://127.0.0.1:3100",
);
});
test("preflight derives the current Paperclip origin and accepts an explicit target", () => {
assert.equal(preflightPosthogLive(COMPLETE_ENV).baseUrl, "https://paperclip.example.test");
assert.equal(
preflightPosthogLive(COMPLETE_ENV, { baseUrl: "https://other-paperclip.example.test" }).baseUrl,
"https://other-paperclip.example.test",
);
assert.throws(
() => preflightPosthogLive({ ...COMPLETE_ENV, PAPERCLIP_API_URL: "" }),
(error) => error instanceof PosthogLivePreflightError && error.code === "missing_base_url",
);
});
test("preflight fails closed unless the PostHog project is exactly 483530", () => {
assert.throws(
() => preflightPosthogLive({
...COMPLETE_ENV,
INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID: "42",
}),
(error) => error instanceof PosthogLivePreflightError && error.code === "unexpected_project_id",
);
});
test("live smoke arguments accept a target URL without another environment binding", () => {
assert.deepEqual(parsePosthogLiveArguments([]), {});
assert.deepEqual(
parsePosthogLiveArguments(["https://paperclip.example.test"]),
{ baseUrl: "https://paperclip.example.test" },
);
assert.deepEqual(
parsePosthogLiveArguments(["--base-url", "https://paperclip.example.test"]),
{ baseUrl: "https://paperclip.example.test" },
);
assert.throws(
() => parsePosthogLiveArguments(["--unknown"]),
(error) => error instanceof PosthogLivePreflightError && error.code === "invalid_arguments",
);
});
test("browser loading happens only after binding and health preflight", async () => {
let fetchCalled = false;
let browserLoaded = false;