fix(evals): prepare prerequisite people through public membership routes

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-28 08:15:26 -05:00
1 parent 81987af644
commit dea44755b0
16 files changed
+177 -32

No files matched your search

+18
View File
@@ -563,3 +563,21 @@ Runner hook performs it. Only synthetic source bytes and proof JSON enter the
existing private attempt evidence and registered-artifact verification pipeline.
A failed attempt remains retained. These development checks are not staging or
Slack qualification, and they do not publish trusted archives or credentials.
## Authenticated prerequisite people
Twenty explicitly selected OpenCode/local chat journeys use ordinary public
signup and instance-admin company-access management to prepare a fresh second
person. The setup verifies the administrator's real session, assigns only the
requested company and its existing operator/viewer role grants, and independently
reads the saved membership and grant attribution. The person's own session must
prove company access and denial of instance administration. Setup consumes the
original observation clock and neither dispatches native work nor performs the
human action under evaluation. Private-resource and other-person boundaries
remain part of each journey's independent oracle.
Actual invitation journeys retain the invitation/acceptance path and its logger
qualification prerequisite. This alternative does not qualify that path, create
an instance administrator, bypass route authorization, or change retained failed
outcomes. Passwords and cookies remain in the existing private session registry;
the setup receipt contains only public identity, role, grant keys and statuses.
@@ -3,7 +3,7 @@ import { expect } from "@playwright/test";
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
import type { McpConnection } from "../../../packages/shared/src/public-mcp.js";
import { connect, mcp, type ToolResult } from "../public-mcp-client.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { prepareAssistantSetup } from "./external-assistant-connections.js";
import { definition, externalObservation, observeExternal, root, checks, verifyRenderedReply } from "./external-support.js";
import { waitForReply } from "./identity-support.js";
@@ -38,7 +38,7 @@ export const assistantConsentExternalCases = [definition("assistant-connections.
const viewer = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets, name: "Viewer Casey" }); people.set(ctx, viewer);
try {
const caller = await ctx.input.api.get<{ user: { id: string } }>("/api/auth/get-session");
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, viewer, secrets, "viewer");
await enrollPrerequisitePerson(ctx, viewer, "viewer");
const marker = `Viewer allowed fixture ${randomBytes(12).toString("hex")}`;
const task = await ctx.input.api.post<{ id: string; title: string; description: string }>(`${root(ctx)}/issues`, { title: "Viewer consent sample task", description: marker, status: "backlog" });
return { viewerId: viewer.id, chatCallerId: caller.user.id, phaseBoundary: "operator-chat-to-viewer-browser-consent", taskId: task.id, taskBefore: task, marker, forbiddenTitle: `Forbidden viewer task ${ctx.input.nonce}` };
@@ -16,7 +16,7 @@ import {
} from "./identity-support.js";
import {
createAuthenticatedPerson,
enrollAuthenticatedPerson,
enrollPrerequisitePerson,
} from "./identity-auth.js";
import { privateBrowserEntry } from "./external-private-browser.js";
import { secretTestEndpoint } from "./external-secret-endpoint.js";
@@ -204,12 +204,7 @@ export const personalSecretExternalCases = [
});
let endpoint: PrivateFixture["endpoint"] | undefined;
try {
await enrollAuthenticatedPerson(
ctx.input.api,
ctx.input.fixtures.company.id,
other,
secrets,
);
await enrollPrerequisitePerson(ctx, other);
const definition = await ctx.input.api.post<UserSecretDefinition>(
`${root(ctx)}/user-secret-definitions`,
{
@@ -3,7 +3,7 @@ import {expect} from "@playwright/test";
import type {ChatCatalogContext} from "../chat-catalog-contract.js";
import type {ConnectionIntentInteraction} from "../../../packages/shared/src/types/issue.js";
import {pollUntil} from "../api.js";
import {createAuthenticatedPerson,enrollAuthenticatedPerson} from "./identity-auth.js";
import {createAuthenticatedPerson,enrollPrerequisitePerson} from "./identity-auth.js";
import {openStandingChat,waitForReply} from "./identity-support.js";
import {definition,externalObservation,observeExternal,checks,root,verifyRenderedReply} from "./external-support.js";
import type {Entity} from "./internal-support.js";
@@ -27,7 +27,7 @@ export const wrongResolverExternalCases=[definition("connection-setup.wrong-pers
const session=await ctx.input.api.get<{user:{id:string}}>("/api/auth/get-session");if(!session.user?.id||session.user.id==="local-board")throw new Error("Actual signed-in other member is required");
const alex=await createAuthenticatedPerson({api:ctx.input.api,browser,secrets,name:"Alex"});people.set(ctx,alex);
try{
await enrollAuthenticatedPerson(ctx.input.api,ctx.input.fixtures.company.id,alex,secrets);
await enrollPrerequisitePerson(ctx, alex);
for(const scope of [{scopeType:"company",scopeId:ctx.input.fixtures.company.id},{scopeType:"agent",scopeId:ctx.input.fixtures.agent.id}])await ctx.input.api.post(`${root(ctx)}/budgets/policies`,{...scope,amount:500,hardStopEnabled:true,isActive:true,unpricedUsagePolicy:"allow"});
if((await ctx.input.api.get<{connections:unknown[]}>(`${root(ctx)}/tools/connections`)).connections.length)throw new Error("Wrong resolver requires no existing Notion account");
const response=await alex.request(`${root(ctx)}/issues`,"POST",{title:"Alex's launch notes",status:"todo",assigneeAgentId:ctx.input.fixtures.agent.id,description:"Read my Notion and summarize the current launch notes. I have not connected my Notion account yet."});
@@ -1,3 +1,6 @@
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
import type { CompanyMemberRecord, CompanyMembersResponse, UserCompanyAccessResponse } from "../../../packages/shared/src/types/access.js";
import { grantsForHumanRole } from "../../../server/src/services/company-member-roles.js";
import { mkdtemp, writeFile, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
@@ -99,3 +102,52 @@ export async function enrollAuthenticatedPerson(api: RunnerApi, companyId: strin
if (!companiesResponse.ok || !(await companiesResponse.json() as Array<{ id: string }>).some(row => row.id === companyId)) throw new Error("Approved person cannot read the intended company");
return { userId: person.id, companyId, inviteId: invite.id, joinRequestId: pending[0]!.id };
}
export const prerequisiteMembershipScenarioIds = Object.freeze([
"assistant-connections.consent-read-only", "connection-setup.wrong-person-resolves",
"documents-artifacts.private-download", "documents-artifacts.restore-document",
"inbox-search.archive-done", "inbox-search.notification-audience", "inbox-search.restore-archive", "inbox-search.show-decisions",
"navigation-preferences.broken-link", "navigation-preferences.dismiss-announcement", "navigation-preferences.edit-profile", "navigation-preferences.quick-search", "navigation-preferences.sidebar-order",
"projects.private-project", "projects.share-project", "questions-and-approvals.wrong-reviewer", "secrets.personal-secret",
"tasks.completion-with-gate", "tasks.private-task", "tasks.share-private-task",
]);
export async function enrollPrerequisitePerson(ctx: ChatCatalogContext, person: Awaited<ReturnType<typeof createAuthenticatedPerson>>, role: "operator" | "viewer" = "operator") {
if (!prerequisiteMembershipScenarioIds.includes(ctx.scenario.id) || ctx.input.execution.id !== `chat-features.runner-opencode.local.${ctx.scenario.id}` || !["operator", "viewer"].includes(role))
throw new Error("Public prerequisite membership is not admitted for this scenario or role");
const deadline = ctx.observationDeadlineAt;
const remaining = () => { const now = Date.now(); if (!Number.isFinite(deadline) || deadline! <= now) throw new Error("Original prerequisite observation budget exhausted or missing"); return Math.min(15000, deadline! - now); };
const { api, fixtures } = ctx.input, companyId = fixtures.company.id;
const read = async <T>(route: string) => { const value = await api.get<T>(route, { timeout: remaining() }); remaining(); return value; };
const personal = async (route: string) => { remaining(); const response = await person.request(route); remaining(); return response; };
const health = await read<{ deploymentMode: string }>("/api/health");
if (health.deploymentMode !== "authenticated") throw new Error("Public membership requires real authenticated sessions");
const session = await read<{ user: { id: string } }>("/api/auth/get-session");
if (!session.user?.id || session.user.id === person.id || session.user.id === "local-board") throw new Error("Distinct real first-admin authority is required");
const admin = await read<UserCompanyAccessResponse>(`/api/admin/users/${session.user.id}/company-access`);
if (admin.user?.id !== session.user.id || admin.user.isInstanceAdmin !== true) throw new Error("Authenticated fixture administrator authority was not verified");
const targetBefore = await read<UserCompanyAccessResponse>(`/api/admin/users/${person.id}/company-access`);
if (targetBefore.user?.id !== person.id || targetBefore.user.isInstanceAdmin !== false || targetBefore.companyAccess.length !== 0) throw new Error("Prerequisite person must be a fresh non-admin user");
const own = await personal("/api/auth/get-session");
if (own.status !== 200) { await own.body?.cancel(); throw new Error("Prerequisite person's real session unavailable"); }
if (((await own.json()) as { user?: { id?: string } }).user?.id !== person.id) throw new Error("Prerequisite session belongs to another person");
const before = await personal(`/api/companies/${companyId}`), beforeStatus = before.status; await before.body?.cancel();
if (![403, 404].includes(beforeStatus)) throw new Error("Fresh person already has company access or denial proof failed");
remaining(); const assigned = await api.putSensitive<UserCompanyAccessResponse>(`/api/admin/users/${person.id}/company-access`, { companyIds: [companyId] }); remaining();
const membership = assigned.companyAccess.find(row => row.companyId === companyId);
if (assigned.user?.id !== person.id || assigned.user.isInstanceAdmin !== false || assigned.companyAccess.length !== 1 || membership?.principalId !== person.id || membership.principalType !== "user" || membership.status !== "active" || membership.membershipRole !== "operator") throw new Error("Public company access response did not establish the exact prerequisite member");
const grants = grantsForHumanRole(role);
remaining(); await api.patchSensitive<CompanyMemberRecord>(`/api/companies/${companyId}/members/${membership.id}/role-and-grants`, { membershipRole: role, status: "active", grants }); remaining();
const members = await read<CompanyMembersResponse>(`/api/companies/${companyId}/members`), actual = members.members.find(row => row.id === membership.id);
const keys = (rows: Array<{ permissionKey: string; scope?: Record<string, unknown> | null }>) => rows.map(row => `${row.permissionKey}:${JSON.stringify(row.scope ?? null)}`).sort().join("\n");
if (!actual || actual.companyId !== companyId || actual.principalId !== person.id || actual.principalType !== "user" || actual.membershipRole !== role || actual.status !== "active" || keys(actual.grants) !== keys(grants) || actual.grants.some(row => row.companyId !== companyId || row.principalId !== person.id || row.principalType !== "user" || row.grantedByUserId !== session.user.id)) throw new Error("Persisted prerequisite role or grants differ from the public requested contract");
const targetAfter = await read<UserCompanyAccessResponse>(`/api/admin/users/${person.id}/company-access`);
if (targetAfter.user?.id !== person.id || targetAfter.user.isInstanceAdmin !== false || targetAfter.companyAccess.length !== 1 || targetAfter.companyAccess[0]?.id !== membership.id || targetAfter.companyAccess[0]?.companyId !== companyId || targetAfter.companyAccess[0]?.membershipRole !== role || targetAfter.companyAccess[0]?.status !== "active") throw new Error("Prerequisite membership or non-admin identity changed after role assignment");
const allowed = await personal(`/api/companies/${companyId}`);
if (allowed.status !== 200) { await allowed.body?.cancel(); throw new Error("Real prerequisite person cannot read its company"); }
if (((await allowed.json()) as { id?: string }).id !== companyId) throw new Error("Real prerequisite person read another company");
const denied = await personal("/api/admin/users"), deniedStatus = denied.status; await denied.body?.cancel();
if (deniedStatus !== 403) throw new Error("Prerequisite person acquired instance authority or denial proof failed");
const receipt = { userId: person.id, companyId, membershipId: membership.id, role, grantKeys: grants.map(row => row.permissionKey).sort(), authentication: "real-person-session", administrator: "verified-instance-admin", enrollment: "public-company-access", beforeStatus, companyReadStatus: 200, instanceAdminReadStatus: deniedStatus };
remaining(); await ctx.input.evidence("identity-public-prerequisite-membership.json", receipt); remaining();
return receipt;
}
@@ -1,7 +1,7 @@
import { expect } from "@playwright/test";
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
import { RunnerApiHttpError } from "../api.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { checks, entity, project, sameTask, task, type Entity } from "./internal-support.js";
import { identityDefinition, identityObservation, observeIdentity, openStandingChat, probeAgentPermission, waitForReply } from "./identity-support.js";
@@ -64,7 +64,7 @@ export const navigationBoundaryIdentityCases: readonly ChatCatalogCase[] = [
const browser = ctx.input.page.context().browser(); if (!browser || !ctx.input.privateSecrets) throw new Error("Missing independent real-user setup");
const observer = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" }); observers.set(ctx, observer);
try {
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, observer, ctx.input.privateSecrets, "viewer");
await enrollPrerequisitePerson(ctx, observer, "viewer");
const denial = await observer.request(`/api/issues/${restricted.id}`); const observerRestrictedStatus = denial.status; await denial.body?.cancel();
return { project: p, stale, similar, restricted, restrictedMarker, stalePath: path, deletedStatus: await status(ctx, `/api/issues/${stale.id}`), observerRestrictedStatus };
} catch (error) { await observer.dispose(); observers.delete(ctx); throw error; }
@@ -97,7 +97,7 @@ export const navigationBoundaryIdentityCases: readonly ChatCatalogCase[] = [
const observer = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" }); observers.set(ctx, observer);
try {
const otherCompany = await ctx.input.api.post<Entity & { issuePrefix: string }>("/api/companies", { name: `Birch announcements ${ctx.input.nonce}` }); otherCompanies.set(ctx, otherCompany.id);
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, observer, ctx.input.privateSecrets, "viewer");
await enrollPrerequisitePerson(ctx, observer, "viewer");
const before = await observer.request("/api/announcements/current"); const observerPublication = await before.json() as Publication | null;
const invalid = await observer.request(`/api/announcements/${encodeURIComponent(publication.id)}/dismiss`, "POST", { companyId: otherCompany.id });
const invalidAuditCompanyStatus = invalid.status; await invalid.body?.cancel();
@@ -1,7 +1,7 @@
import { expect } from "@playwright/test";
import { checks, entity, hasLink, project, task, verifyInternalResult } from "./internal-support.js";
import { identityDefinition, identityObservation, waitForReply, probeAgentPermission } from "./identity-support.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
const searchObservers = new WeakMap<ChatCatalogContext, Awaited<ReturnType<typeof createAuthenticatedPerson>>>();
@@ -65,7 +65,7 @@ export const navigationIdentityCases = [
if (!browser || !ctx.input.privateSecrets) throw new Error("Quick search audience requires actual authenticated browser identities");
const observer = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" }); searchObservers.set(ctx, observer);
try {
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, observer, ctx.input.privateSecrets, "viewer");
await enrollPrerequisitePerson(ctx, observer, "viewer");
const privateTask = await task(ctx, "Launch private acquisition", state.project.id, { visibility: "private", description: `Private launch note ${ctx.input.nonce}` });
return { ...state, privateTask, observerId: observer.id };
} catch (error) { await observer.dispose(); searchObservers.delete(ctx); throw error; }
@@ -0,0 +1,80 @@
import { afterEach, expect, it, vi } from "vitest";
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
import { grantsForHumanRole } from "../../../server/src/services/company-member-roles.js";
import { enrollPrerequisitePerson, prerequisiteMembershipScenarioIds } from "./identity-auth.js";
afterEach(() => vi.useRealTimers());
function fixture(role: "viewer" | "operator" = "operator") {
const membership = { id: "membership", companyId: "company", principalId: "person", principalType: "user", status: "active", membershipRole: "operator" };
const state = { assigned: false, administrator: true, targetAdministrator: false, preexisting: false, personalId: "person", companyReadId: "company", beforeStatus: 403, adminStatus: 403, readRole: role, extraGrant: false, foreignGrant: false, grantedBy: "admin", slowPut: false };
const writes: Array<{ route: string; body: unknown }> = [], reads: string[] = [], personalReads: string[] = [];
const api = {
get: vi.fn(async (route: string) => {
reads.push(route);
if (route === "/api/health") return { deploymentMode: "authenticated" };
if (route === "/api/auth/get-session") return { user: { id: "admin" } };
if (route === "/api/admin/users/admin/company-access") return { user: { id: "admin", isInstanceAdmin: state.administrator }, companyAccess: [] };
if (route === "/api/admin/users/person/company-access") return { user: { id: "person", isInstanceAdmin: state.targetAdministrator }, companyAccess: state.assigned ? [{ ...membership, membershipRole: state.readRole }] : state.preexisting ? [membership] : [] };
if (route === "/api/companies/company/members") return { members: [{ ...membership, membershipRole: state.readRole, grants: [...grantsForHumanRole(role), ...(state.extraGrant ? [{ permissionKey: "users:manage_permissions", scope: null }] : [])].map(grant => ({ ...grant, companyId: state.foreignGrant ? "foreign" : "company", principalId: "person", principalType: "user", grantedByUserId: state.grantedBy })) }] };
throw new Error("Unexpected fixture read");
}),
putSensitive: vi.fn(async (route: string, body: unknown) => { writes.push({ route, body }); state.assigned = true; if (state.slowPut) vi.setSystemTime(100000); return { user: { id: "person", isInstanceAdmin: state.targetAdministrator }, companyAccess: [membership] }; }),
patchSensitive: vi.fn(async (route: string, body: unknown) => { writes.push({ route, body }); return membership; }),
};
const person = { id: "person", request: vi.fn(async (route: string) => {
personalReads.push(route);
if (route === "/api/auth/get-session") return new Response(JSON.stringify({ user: { id: state.personalId } }));
if (route === "/api/admin/users") return new Response("closed", { status: state.adminStatus });
if (route === "/api/companies/company") return new Response(JSON.stringify({ id: state.companyReadId }), { status: state.assigned ? 200 : state.beforeStatus });
throw new Error("Unexpected person request");
}) } as unknown as Parameters<typeof enrollPrerequisitePerson>[1];
const evidence = vi.fn();
const ctx = { scenario: { id: "inbox-search.show-decisions" }, observationDeadlineAt: 100000, input: { execution: { id: "chat-features.runner-opencode.local.inbox-search.show-decisions" }, api, fixtures: { company: { id: "company" } }, evidence } } as unknown as ChatCatalogContext;
vi.useFakeTimers(); vi.setSystemTime(0);
return { ctx, person, api, state, writes, reads, personalReads, evidence };
}
it.each(["operator", "viewer"] as const)("establishes only the real public %s prerequisite and proves the person's own scope", async role => {
const f = fixture(role); const receipt = await enrollPrerequisitePerson(f.ctx, f.person, role);
expect(f.writes).toEqual([{ route: "/api/admin/users/person/company-access", body: { companyIds: ["company"] } }, { route: "/api/companies/company/members/membership/role-and-grants", body: { membershipRole: role, status: "active", grants: grantsForHumanRole(role) } }]);
expect(f.personalReads).toEqual(["/api/auth/get-session", "/api/companies/company", "/api/companies/company", "/api/admin/users"]);
expect(receipt).toMatchObject({ userId: "person", companyId: "company", role, beforeStatus: 403, companyReadStatus: 200, instanceAdminReadStatus: 403 });
expect(f.evidence).toHaveBeenCalledOnce(); expect([...f.reads, ...f.writes.map(row => row.route)]).not.toEqual(expect.arrayContaining([expect.stringContaining("invites")]));
});
it.each(["access.invite-teammate", "access.manage-membership", "activity.agent-feedback", "unknown"])("does not substitute public membership for unapproved/invitation journey %s", async scenarioId => {
const f = fixture(); Object.assign(f.ctx.scenario, { id: scenarioId }); f.ctx.input.execution.id = `chat-features.runner-opencode.local.${scenarioId}`;
await expect(enrollPrerequisitePerson(f.ctx, f.person)).rejects.toThrow("not admitted"); expect(f.api.get).not.toHaveBeenCalled(); expect(f.writes).toHaveLength(0);
});
it("requires the exact OpenCode local execution rather than authorizing by scenario name alone", async () => {
const f = fixture(); f.ctx.input.execution.id = "chat-features.runner-codex.local.inbox-search.show-decisions";
await expect(enrollPrerequisitePerson(f.ctx, f.person)).rejects.toThrow("not admitted"); expect(f.writes).toHaveLength(0);
});
it.each(["administrator", "target administrator", "preexisting", "wrong person", "initial access"])("refuses %s before altering access", async invalid => {
const f = fixture();
if (invalid === "administrator") f.state.administrator = false;
if (invalid === "target administrator") f.state.targetAdministrator = true;
if (invalid === "preexisting") f.state.preexisting = true;
if (invalid === "wrong person") f.state.personalId = "other";
if (invalid === "initial access") f.state.beforeStatus = 200;
await expect(enrollPrerequisitePerson(f.ctx, f.person)).rejects.toThrow(); expect(f.writes).toHaveLength(0); expect(f.evidence).not.toHaveBeenCalled();
});
it.each(["wrong role", "extra grant", "foreign grant", "wrong author", "instance authority", "wrong company"])("rejects persisted %s or unauthorized own-session proof", async invalid => {
const f = fixture();
if (invalid === "wrong role") f.state.readRole = "viewer";
if (invalid === "extra grant") f.state.extraGrant = true;
if (invalid === "foreign grant") f.state.foreignGrant = true;
if (invalid === "wrong author") f.state.grantedBy = "other";
if (invalid === "instance authority") f.state.adminStatus = 200;
if (invalid === "wrong company") f.state.companyReadId = "other";
await expect(enrollPrerequisitePerson(f.ctx, f.person)).rejects.toThrow(); expect(f.evidence).not.toHaveBeenCalled();
});
it.each([0, -1, Number.NaN, undefined])("will not start setup without a finite positive original clock (%s)", async deadline => {
const f = fixture(); Object.assign(f.ctx, { observationDeadlineAt: deadline });
await expect(enrollPrerequisitePerson(f.ctx, f.person)).rejects.toThrow("budget"); expect(f.api.get).not.toHaveBeenCalled(); expect(f.writes).toHaveLength(0);
});
it("a slow public access write cannot reset the original clock or start role/grant mutation after expiry", async () => {
const f = fixture(); f.state.slowPut = true;
await expect(enrollPrerequisitePerson(f.ctx, f.person)).rejects.toThrow("budget"); expect(f.writes).toHaveLength(1); expect(f.api.patchSensitive).not.toHaveBeenCalled(); expect(f.evidence).not.toHaveBeenCalled();
});
it("the closed allowlist contains no access/invitation scenarios", () => {
expect(prerequisiteMembershipScenarioIds).toHaveLength(20);
expect(prerequisiteMembershipScenarioIds.some(id => id.startsWith("access."))).toBe(false);
});
@@ -1,7 +1,7 @@
import { expect } from "@playwright/test";
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
import { checks } from "./internal-support.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { identityDefinition, identityObservation, observeIdentity, probeAgentPermission, waitForReply } from "./identity-support.js";
interface Session { user: { id: string; name: string; image?: string | null } }
@@ -28,7 +28,7 @@ export const profileIdentityCases: readonly ChatCatalogCase[] = [identityDefinit
const observer = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" });
observers.set(ctx, observer);
try {
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, observer, ctx.input.privateSecrets);
await enrollPrerequisitePerson(ctx, observer);
await updateOwnProfile(ctx, "Alex Former");
await ctx.input.page.reload({ waitUntil: "domcontentloaded" });
const company = await ctx.input.api.get<{ name: string }>(`/api/companies/${ctx.input.fixtures.company.id}`);
@@ -4,7 +4,7 @@ import { pollUntil } from "../api.js";
import { checks, project, entity, hasLink, putDocument } from "./internal-support.js";
import { identityDefinition, identityObservation, observeIdentity, openStandingChat, readAgentResource, waitForReply, type Interaction } from "./identity-support.js";
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
const audiencePeople = new WeakMap<ChatCatalogContext, Awaited<ReturnType<typeof createAuthenticatedPerson>>>();
async function cards(ctx: ChatCatalogContext) {
@@ -144,7 +144,7 @@ export const questionIdentityCases = [
const renamed = await ctx.input.page.context().request.patch(`${ctx.input.api.baseURL}/api/auth/profile`, { data: { name: "Alex" }, headers: { Origin: ctx.input.api.baseURL } });
if (!renamed.ok()) throw new Error("Synthetic Alex profile setup failed");
const riley = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets, name: "Riley" }); audiencePeople.set(ctx, riley);
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, riley, secrets);
await enrollPrerequisitePerson(ctx, riley);
const source = await openStandingChat(ctx);
const original = await ctx.input.api.post<Interaction>(`/api/issues/${source.id}/interactions`, createIssueThreadInteractionSchema.parse({
kind: "request_confirmation", title: "Riley's website proposal", resolverPolicy: "human_only", addresseeUserId: riley.id,
@@ -1,7 +1,7 @@
import { expect } from "@playwright/test";
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
import { checks, entity, project } from "./internal-support.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { identityDefinition, identityObservation, probeAgentPermission, waitForReply } from "./identity-support.js";
type Person = Awaited<ReturnType<typeof createAuthenticatedPerson>>;
const observers = new WeakMap<ChatCatalogContext, Person>();
@@ -40,7 +40,7 @@ export const sidebarOrderIdentityCases: readonly ChatCatalogCase[] = [identityDe
const endpoint = `/api/companies/${ctx.input.fixtures.company.id}/sidebar-preferences/me`;
await ctx.input.api.putSensitive(endpoint, { orderedIds: [first.id, launch.id] });
const observer = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" }); observers.set(ctx, observer);
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, observer, ctx.input.privateSecrets);
await enrollPrerequisitePerson(ctx, observer);
const saved = await observer.request(endpoint, "PUT", { orderedIds: [first.id, launch.id] }); if (!saved.ok) throw new Error("Riley's public sidebar fixture failed"); await saved.body?.cancel();
const before = await observer.request(endpoint); if (!before.ok) throw new Error("Riley sidebar read failed");
const observerBefore = (await before.json() as Order).orderedIds;
@@ -2,7 +2,7 @@ import { expect } from "@playwright/test";
import { createIssueThreadInteractionSchema } from "../../../packages/shared/src/validators/issue.js";
import type { ChatCatalogCase } from "../chat-catalog-contract.js";
import { makeDefinition } from "./internal-definition.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { mainPerson } from "./internal-privacy.js";
import { checks, entity, hasLink, observation, observeInternal, project, sameTask, task, type Entity, type InternalObservation } from "./internal-support.js";
interface Request extends Entity { addresseeUserId?: string; resolvedByUserId?: string; result?: unknown; payload?: unknown; decidedByUserId?: string | null }
@@ -53,7 +53,7 @@ function showDecisions(): ChatCatalogCase {
if (!browser || !ctx.input.privateSecrets) throw new Error("Decision audiences require an independent signed-in human and private credential redaction");
riley = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" });
try {
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, riley, ctx.input.privateSecrets, "operator");
await enrollPrerequisitePerson(ctx, riley, "operator");
const p = await project(ctx); const t = await task(ctx, "Riley launch question", p.id);
const history = await task(ctx, "Completed launch information", p.id);
const question = (title: string, addresseeUserId: string) => createIssueThreadInteractionSchema.parse({ kind: "ask_user_questions", title, resolverPolicy: "human_only", addresseeUserId, continuationPolicy: "none",
@@ -3,7 +3,7 @@ import { createIssueThreadInteractionSchema } from "../../../packages/shared/src
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
import { pollUntil } from "../api.js";
import { RunnerApiHttpError } from "../api.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { makeDefinition } from "./internal-definition.js";
import { checks, entity, hasLink, observation, observeInternal, project, sameTask, task, type Entity, type InternalObservation } from "./internal-support.js";
@@ -97,7 +97,7 @@ function triageCase(scenarioId: string): ChatCatalogCase {
if (!browser || !ctx.input.privateSecrets) throw new Error("Personal triage requires authenticated independent people and private credential redaction");
other = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" });
try {
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, other, ctx.input.privateSecrets, "operator");
await enrollPrerequisitePerson(ctx, other, "operator");
const session = await ctx.input.api.get<{ user?: { id: string } }>("/api/auth/get-session");
if (!session.user?.id) throw new Error("Personal inbox target is not a real authenticated human");
const p = await project(ctx); const originalTasks: Entity[] = [];
@@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { makeDefinition, fixtureWorker } from "./internal-definition.js";
import { project, task, entity, observation, checks, observeInternal, hasLink, sameTask, type Entity, type InternalObservation } from "./internal-support.js";
import { pollUntil } from "../api.js";
@@ -21,7 +21,7 @@ export async function observerPerson(ctx: ChatCatalogContext) {
const browser = ctx.input.page.context().browser();
if (!browser || !ctx.input.privateSecrets) throw new Error("Independent authenticated observer requires a browser and attempt-private redaction sink");
const observer = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" });
try { await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, observer, ctx.input.privateSecrets, "viewer"); }
try { await enrollPrerequisitePerson(ctx, observer, "viewer"); }
catch (error) { await observer.dispose(); throw error; }
return observer;
}
@@ -2,7 +2,7 @@ import { expect } from "@playwright/test";
import { randomUUID } from "node:crypto";
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
import { pollUntil } from "../api.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { checks, entity, observation, observeInternal, sameTask, hasLink, type Entity, type InternalObservation } from "./internal-support.js";
interface Revision extends Entity { createdByUserId?: string | null }
interface ConcurrentRestore extends InternalObservation { phaseReply: string }
@@ -64,7 +64,7 @@ export function withRestoreConcurrency(primary: ChatCatalogCase): ChatCatalogCas
sourceFiles: [...primary.sourceFiles, "chat-catalog-domains/internal-restore-concurrency.ts", "chat-catalog-domains/identity-auth.ts"],
async setup(ctx) { const browser = ctx.input.page.context().browser(); if (!browser || !ctx.input.privateSecrets) throw new Error("Concurrent editor context requires actual authenticated people and private credential redaction");
editor = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Casey" });
try { await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, editor, ctx.input.privateSecrets, "operator"); return { ...await primary.setup(ctx), editorId: editor.id }; }
try { await enrollPrerequisitePerson(ctx, editor, "operator"); return { ...await primary.setup(ctx), editorId: editor.id }; }
catch (error) { await editor.dispose(); editor = undefined; throw error; } },
grade: value => gradeConcurrentRestore(value, primary.grade),
async act(ctx, state) {
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
import { expect } from "@playwright/test";
import type { ChatCatalogCase } from "../chat-catalog-contract.js";
import { pollUntil } from "../api.js";
import { createAuthenticatedPerson, enrollAuthenticatedPerson } from "./identity-auth.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import { fixtureWorker, makeDefinition } from "./internal-definition.js";
import { checks, entity, observation, observeInternal, project, sameTask, task, type Entity, type InternalObservation } from "./internal-support.js";
@@ -51,7 +51,7 @@ function completionGate(): ChatCatalogCase {
const browser = ctx.input.page.context().browser();
if (!browser || !ctx.input.privateSecrets) throw new Error("Independent reviewer fixture requires a real authenticated browser and secret redactor");
reviewer = await createAuthenticatedPerson({ api: ctx.input.api, browser, secrets: ctx.input.privateSecrets, name: "Riley" });
await enrollAuthenticatedPerson(ctx.input.api, ctx.input.fixtures.company.id, reviewer, ctx.input.privateSecrets, "operator");
await enrollPrerequisitePerson(ctx, reviewer, "operator");
const worker = await fixtureWorker(ctx); const p = await project(ctx);
const root = `/api/companies/${ctx.input.fixtures.company.id}`;
for (const scope of [{ scopeType: "company", scopeId: ctx.input.fixtures.company.id }, { scopeType: "agent", scopeId: worker.id }])