Files
zopu-code/packages/primitives/src/work-definition.ts
T
-Puter a8d946b6a9 Slice 4 control-plane repairs, lint/catalog cleanup, Flue adapter fixes
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
2026-07-28 02:53:05 +05:30

111 lines
3.3 KiB
TypeScript

import { Effect, Schema } from "effect";
const Text = Schema.String.check(
Schema.makeFilter((value) => value.trim().length > 0, {
expected: "a non-empty string",
})
);
export const DefinitionRisk = Schema.Literals(["low", "medium", "high"]);
export type DefinitionRisk = typeof DefinitionRisk.Type;
export const QuestionImpact = Schema.Literals(["low", "medium", "high"]);
export type QuestionImpact = typeof QuestionImpact.Type;
export const WorkQuestion = Schema.Struct({
alternatives: Schema.Array(Text),
answer: Schema.optional(Text),
id: Text,
impact: QuestionImpact,
prompt: Text,
recommendation: Schema.optional(Text),
status: Schema.Literals(["open", "answered", "withdrawn"]),
});
export type WorkQuestion = typeof WorkQuestion.Type;
export const WorkDefinition = Schema.Struct({
acceptanceCriteria: Schema.NonEmptyArray(Text),
affectedUsers: Schema.Array(Text),
assumptions: Schema.Array(Text),
constraints: Schema.Array(Text),
desiredOutcome: Text,
inScope: Schema.Array(Text),
outOfScope: Schema.Array(Text),
problem: Text,
questions: Schema.Array(WorkQuestion),
requiredArtifacts: Schema.Array(Text),
risk: DefinitionRisk,
rollback: Schema.optional(Text),
rollout: Schema.optional(Text),
version: Schema.Int,
});
export type WorkDefinition = typeof WorkDefinition.Type;
export const DefinitionApproval = Schema.Struct({
approvedAt: Schema.Number,
approvedBy: Text,
definitionVersion: Schema.Int,
});
export type DefinitionApproval = typeof DefinitionApproval.Type;
export const DefinitionProposal = WorkDefinition;
export type DefinitionProposal = typeof DefinitionProposal.Type;
export class DefinitionError extends Schema.TaggedErrorClass<DefinitionError>()(
"DefinitionError",
{
message: Schema.String,
reason: Schema.Literals(["Invalid", "OpenHighImpactQuestion"]),
}
) {}
export const decodeDefinition = Effect.fn("Work.decodeDefinition")(
function* decodeDefinition(input: unknown) {
const definition = yield* Schema.decodeUnknownEffect(WorkDefinition)(
input
).pipe(
Effect.mapError(
(cause) =>
new DefinitionError({ message: cause.message, reason: "Invalid" })
)
);
const questionIds = new Set<string>();
for (const question of definition.questions) {
if (questionIds.has(question.id)) {
return yield* Effect.fail(
new DefinitionError({
message: `Question ID must be unique: ${question.id}`,
reason: "Invalid",
})
);
}
questionIds.add(question.id);
}
return definition;
}
);
export const validateDefinition = Effect.fn("Work.validateDefinition")(
function* validateDefinition(input: unknown) {
const definition = yield* decodeDefinition(input);
const blocked = definition.questions.some(
(question) => question.status === "open" && question.impact === "high"
);
if (blocked) {
return yield* Effect.fail(
new DefinitionError({
message:
"High-impact open questions must be resolved before approval",
reason: "OpenHighImpactQuestion",
})
);
}
return definition;
}
);
export const canApproveDefinition = (definition: WorkDefinition): boolean =>
definition.questions.every(
(question) => question.status !== "open" || question.impact !== "high"
);