Ctrl K
ComunidadGuías…

tests/contract/harness.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { createApiKey, organizationStatement } from "../../src/core/auth";
import { newId } from "../../src/core/ids";
import type { Db, Param } from "../../src/core/db";
import type { Trust } from "../../src/core/validate";
import {
  embeddingPolicyFingerprint,
  type VectorCapability,
} from "../../src/core/vectors";
import { createSecretCipher } from "../../src/core/secrets";
import type { WebhookSecurity } from "../../src/core/webhook-security";

export const TEST_SECRET_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
export const TEST_SECRET_CIPHER = createSecretCipher({
  active: "test-v1",
  keys: { "test-v1": TEST_SECRET_KEY },
});
export const TEST_WEBHOOK_HOST = "hooks.example.com";
export const TEST_WEBHOOK_SECURITY: WebhookSecurity = {
  allowedHostnames: [TEST_WEBHOOK_HOST],
  resolve: async () => ["93.184.216.34"],
  dispatch: async ({ addresses }) => ({
    response: new Response("test transport refusal", { status: 503 }),
    connectedAddress: addresses[0]!,
  }),
};

export interface Res {
  status: number;
  body: any;
  headers: Headers;
}

export interface ProvisionOptions {
  orgId?: string;
  orgName?: string;
  principalId?: string;
  principalKind?: "human" | "agent" | "service";
  scopes?: string[];
  maxTrust?: Trust;
}

export interface Provisioned {
  orgId: string;
  keyId: string;
  key: string;
  principalId: string;
}

/**
 * The only surface a contract case may use. Both runtimes implement it, so a
 * case cannot accidentally depend on Bun or Cloudflare behavior.
 */
export interface Fixture {
  runtime: string;
  call(
    method: string,
    path: string,
    options?: { body?: unknown; key?: string; headers?: Record<string, string> },
  ): Promise<Res>;
  /** Sends an already-serialized body, e.g. NDJSON import payloads. */
  callRaw(
    method: string,
    path: string,
    options: { body: string; key?: string; contentType?: string },
  ): Promise<Res>;
  provision(options?: ProvisionOptions): Promise<Provisioned>;
  revoke(keyId: string): Promise<void>;
  query<Row>(sql: string, params?: Param[]): Promise<Row[]>;
  /** Restart the process/isolate against the same persisted storage. */
  restart(): Promise<void>;
}

export const DEFAULT_SCOPES = [
  "projects:resolve",
  "projects:create",
  "observations:write",
  "claims:write",
  "context:compile",
  "feedback:write",
  "evidence:read",
  "checkpoints:read",
  "checkpoints:write",
];

export async function provisionWith(db: Db, options: ProvisionOptions = {}): Promise<Provisioned> {
  const orgId = options.orgId ?? newId("org");
  const key = await createApiKey({
    orgId,
    principalId: options.principalId ?? newId("agent"),
    principalKind: options.principalKind ?? "agent",
    label: "contract test key",
    scopes: options.scopes ?? DEFAULT_SCOPES,
    maxTrust: options.maxTrust ?? "verified",
    // Contract suites deliberately move the app clock across decades. Keep
    // fixture credentials valid throughout that simulated timeline.
    notBefore: new Date(0),
  });
  const statements = options.orgId
    ? [key.statement]
    : [organizationStatement(orgId, options.orgName ?? "Contract Org"), key.statement];
  await db.batch(statements);
  return {
    orgId,
    keyId: key.id,
    key: key.key,
    principalId: key.statement.params[2] as string,
  };
}

export async function revokeWith(db: Db, keyId: string): Promise<void> {
  await db.batch([
    {
      sql: `UPDATE api_keys SET revoked_at = ? WHERE id = ?`,
      params: [new Date().toISOString(), keyId],
    },
  ]);
}

/** Builds the client implementations from any raw request dispatcher. */
export function clientVia(  dispatch: (request: Request) => Promise<Response>,
  origin: string,
): Pick<Fixture, "call" | "callRaw"> {
  const send = async (
    method: string,
    path: string,
    headers: Record<string, string>,
    body: string | undefined,
  ): Promise<Res> => {
    const response = await dispatch(new Request(`${origin}${path}`, { method, headers, body }));
    const text = await response.text();
    let parsed: unknown = text;
    try {
      parsed = JSON.parse(text);
    } catch {
      /* non-JSON bodies stay raw so a case can assert on them */
    }
    return { status: response.status, body: parsed, headers: response.headers };
  };

  return {
    call(method, path, options = {}) {
      const headers: Record<string, string> = { ...options.headers };
      if (options.key) headers.authorization = `Bearer ${options.key}`;
      if (options.body !== undefined) headers["content-type"] = "application/json";
      return send(
        method,
        path,
        headers,
        options.body === undefined ? undefined : JSON.stringify(options.body),
      );
    },
    callRaw(method, path, options) {
      const headers: Record<string, string> = {
        "content-type": options.contentType ?? "application/x-ndjson",
      };
      if (options.key) headers.authorization = `Bearer ${options.key}`;
      return send(method, path, headers, options.body);
    },
  };
}

/**
 * An in-memory vector capability for exercising the hybrid retrieval path.
 *
 * Real vector retrieval needs a native extension (sqlite-vec) or a Cloudflare
 * binding (Vectorize) plus an embedding provider, none of which belong in a
 * contract test. This stands in for all three so the shared core's hybrid
 * branch actually executes and its effect on ranking is observable.
 */
export function fakeVectors(): VectorCapability & {
  /** Pin a similarity score for a record id, as a real index would return. */
  setScore(id: string, score: number, metadata?: Record<string, string>): void;
  /** Make the embedder throw, to prove retrieval degrades instead of failing. */
  breakEmbedder(): void;
  restoreEmbedder(): void;
  breakStore(): void;
  restoreStore(): void;
  embedCalls: () => number;
  metadataFor(id: string): Record<string, string> | undefined;
  lastFilter: () => Record<string, string> | undefined;
  drop(id: string): void;
} {
  const scores = new Map<string, number>();
  const metadata = new Map<string, Record<string, string>>();
  let broken = false;
  let storeBroken = false;
  let calls = 0;
  let filter: Record<string, string> | undefined;

  return {
    setScore: (id, score, scope) => {
      scores.set(id, score);
      if (scope) metadata.set(id, scope);
    },
    breakEmbedder: () => {
      broken = true;
    },
    restoreEmbedder: () => {
      broken = false;
    },
    breakStore: () => {
      storeBroken = true;
    },
    restoreStore: () => {
      storeBroken = false;
    },
    embedCalls: () => calls,
    metadataFor: (id) => metadata.get(id),
    lastFilter: () => filter,
    drop: (id) => {
      scores.delete(id);
      metadata.delete(id);
    },
    fingerprint: {
      provider: "contract",
      model: "contract-stub",
      revision: "v1",
      dimensions: 4,
      metric: "cosine",
      preprocessing: embeddingPolicyFingerprint("raw-unit-v1", 0.1),
      index_schema: "claims-scope-v1",
    },
    embedder: {
      dimensions: 4,
      model: "contract-stub",
      async embed(texts: string[]) {
        calls += 1;
        if (broken) throw new Error("embedding provider is unavailable");
        return texts.map(() => new Float32Array([1, 0, 0, 0]));
      },
    },
    store: {
      /**
       * Records what was indexed. A pinned score still wins, so a case can
       * control ranking, but an unpinned upsert is still observable — otherwise a
       * test cannot tell "indexed" from "silently dropped".
       */
      async upsert(records) {
        if (storeBroken) throw new Error("vector store is unavailable");
        for (const record of records) {
          metadata.set(record.id, { ...record.metadata });
          if (!scores.has(record.id)) scores.set(record.id, 0.5);
        }
      },
      async query(_vector, options) {
        filter = options.filter;
        return [...scores.entries()]
          .filter(([id]) =>
            Object.entries(options.filter ?? {}).every(
              ([key, value]) => metadata.get(id)?.[key] === value,
            ),
          )
          .map(([id, score]) => ({ id, score }))
          .sort((left, right) => right.score - left.score)
          .slice(0, options.topK);
      },
      async remove(ids: string[]) {
        for (const id of ids) {
          scores.delete(id);
          metadata.delete(id);
        }
      },
      async present(ids: string[]) {
        return new Set(ids.filter((id) => metadata.has(id)));
      },
    },
  };
}