Convex control plane (Slices 1-4 prerequisites for Slice 5): - Versioned Definition/Design persistence: revisions preserve history instead of deleting prior slices/approvals - Fenced conversation turn queue: attempt-numbered leases prevent stale worker overwrites; expired turns reconciled by cron - Fenced Run/Attempt claiming: only queued attempts from running Runs can be claimed; lease expiry checks on every finish/checkpoint - Multi-slice progression: successful slice marks next slice ready instead of completing the whole Work; completed Runs blocked from retry - Typed AttemptClassification in schema and resolver decisions table - Durable resolver decisions for normal outcomes, cancellation, and lease-expiry recovery - Persistent artifacts and delivery metadata with provenance, source revision, verification status, and controlled delivery transitions - High-impact open questions block definition approval - Question submission gated to defining/awaiting-approval states only - Delivery status updates validate JSON metadata before persisting - Planner failures recorded as durable blocked events - Approval identity uses canonical tokenIdentifier Primitives: - decodeDefinition separates decode from validate - Design validation enforces 1-4 slices with unique IDs and valid deps - Artifact and delivery draft schemas with verified-revision binding - Delivery status transition table Package management: - Consolidated shared deps into single catalog (hono, valibot, streamdown, @types/react, @types/node, @tailwindcss/*, react-native) - Removed cross-version Hono boundary: flue() exported as Fetchable - Deleted bun.lock and regenerated from clean catalog resolution Lint/format: - Removed .eslintignore; oxc uses native ignorePatterns in oxlint.config.ts - Deleted redundant packages/backend/.oxlintrc.json - Added repos/** and scripts/** to ignore patterns - Convex-specific rule overrides for ES2022 target constraints - Fixed all oxc errors in fluePersistence.ts and agents/src/db.ts - Fixed all formatting across changed files
109 lines
3.3 KiB
TypeScript
109 lines
3.3 KiB
TypeScript
import { env } from "@code/env/convex";
|
|
import { convexTest } from "convex-test";
|
|
import { anyApi } from "convex/server";
|
|
import { describe, expect, test } from "vitest";
|
|
|
|
import schema from "./schema";
|
|
|
|
declare global {
|
|
interface ImportMeta {
|
|
readonly glob: (pattern: string) => Record<string, () => Promise<unknown>>;
|
|
}
|
|
}
|
|
|
|
const modules = import.meta.glob("./**/*.ts");
|
|
const api = anyApi;
|
|
|
|
describe("Work artifacts and delivery", () => {
|
|
test("records exact idempotent artifact and delivery metadata", async () => {
|
|
const t = convexTest({ modules, schema });
|
|
const workId = await t.run(async (ctx) => {
|
|
const organizationId = await ctx.db.insert("organizations", {
|
|
createdAt: 1,
|
|
createdBy: "user",
|
|
kind: "personal",
|
|
name: "Personal",
|
|
});
|
|
const projectId = await ctx.db.insert("projects", {
|
|
createdAt: 1,
|
|
name: "Zopu",
|
|
normalizedSourceUrl: "https://example.com/zopu",
|
|
organizationId,
|
|
repositoryPath: "puter/zopu",
|
|
sourceHost: "example.com",
|
|
sourceUrl: "https://example.com/zopu",
|
|
updatedAt: 1,
|
|
});
|
|
return await ctx.db.insert("works", {
|
|
createdAt: 1,
|
|
objective: "Persist evidence",
|
|
organizationId,
|
|
projectId,
|
|
status: "executing",
|
|
title: "Artifact proof",
|
|
updatedAt: 1,
|
|
});
|
|
});
|
|
|
|
const draft = {
|
|
idempotencyKey: "attempt-1:test-report",
|
|
kind: "test-report" as const,
|
|
metadataJson: "{}",
|
|
producer: "fake-harness",
|
|
provenanceJson: "{}",
|
|
sourceRevision: "abc123",
|
|
title: "Focused tests",
|
|
verificationStatus: "verified" as const,
|
|
};
|
|
const first = await t.mutation(api.workArtifacts.recordArtifact, {
|
|
draft,
|
|
token: env.FLUE_DB_TOKEN,
|
|
workId,
|
|
});
|
|
const replay = await t.mutation(api.workArtifacts.recordArtifact, {
|
|
draft,
|
|
token: env.FLUE_DB_TOKEN,
|
|
workId,
|
|
});
|
|
expect(first.created).toBe(true);
|
|
expect(replay).toEqual({ artifactId: first.artifactId, created: false });
|
|
|
|
const delivery = await t.mutation(api.workArtifacts.recordDelivery, {
|
|
artifactId: first.artifactId,
|
|
draft: {
|
|
externalId: "42",
|
|
idempotencyKey: "pr:42",
|
|
kind: "pull-request",
|
|
metadataJson: "{}",
|
|
provider: "gitea",
|
|
sourceRevision: "abc123",
|
|
status: "ready",
|
|
target: "main",
|
|
url: "https://git.example/pulls/42",
|
|
},
|
|
token: env.FLUE_DB_TOKEN,
|
|
workId,
|
|
});
|
|
expect(delivery.created).toBe(true);
|
|
const updated = await t.mutation(api.workArtifacts.updateDeliveryStatus, {
|
|
deliveryId: delivery.deliveryId,
|
|
metadataJson: '{"approvedBy":"user"}',
|
|
status: "approved",
|
|
token: env.FLUE_DB_TOKEN,
|
|
});
|
|
expect(updated).toEqual({ changed: true, status: "approved" });
|
|
const stored = await t.run(async (ctx) => ({
|
|
artifacts: await ctx.db.query("workArtifacts").collect(),
|
|
deliveries: await ctx.db.query("workDeliveries").collect(),
|
|
events: await ctx.db.query("workEvents").collect(),
|
|
}));
|
|
expect(stored.artifacts).toHaveLength(1);
|
|
expect(stored.deliveries).toHaveLength(1);
|
|
expect(stored.events.map((event) => event.kind)).toEqual([
|
|
"artifact.recorded",
|
|
"delivery.recorded",
|
|
"delivery.updated",
|
|
]);
|
|
});
|
|
});
|