@abelxiaoxing/cadence 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { defineTool } from "@earendil-works/pi-coding-agent";
3
3
  import { Type } from "typebox";
4
+ import {
5
+ type CandidatePatchOperation,
6
+ compileCandidatePatch,
7
+ } from "./candidate-patch.ts";
4
8
  import {
5
9
  type CandidateFailure,
6
10
  type DiffResult,
@@ -11,7 +15,11 @@ import {
11
15
  validateDiffResult,
12
16
  validateEvidenceResult,
13
17
  } from "./contracts.ts";
14
- import type { BeginCandidateInput, TaskLedger } from "./task-ledger.ts";
18
+ import {
19
+ type BeginCandidateInput,
20
+ TASK_LEDGER_LIMITS,
21
+ type TaskLedger,
22
+ } from "./task-ledger.ts";
15
23
 
16
24
  const compactEvidenceSchema = Type.Object({
17
25
  id: Type.String(),
@@ -83,17 +91,49 @@ function candidateArtifactSchema(candidateId: string) {
83
91
  return Type.Union([
84
92
  Type.Object(
85
93
  {
86
- kind: Type.Literal("candidate-segment"),
87
- candidateId: Type.Literal(candidateId),
88
- sequence: Type.Integer({ minimum: 0 }),
89
- text: Type.String({ minLength: 1 }),
90
- },
91
- { additionalProperties: false },
92
- ),
93
- Type.Object(
94
- {
95
- kind: Type.Literal("candidate-seal"),
94
+ kind: Type.Literal("candidate-patch"),
96
95
  candidateId: Type.Literal(candidateId),
96
+ operations: Type.Array(
97
+ Type.Union([
98
+ Type.Object(
99
+ {
100
+ kind: Type.Literal("replace"),
101
+ path: Type.String(),
102
+ oldText: Type.String({ minLength: 1 }),
103
+ newText: Type.String(),
104
+ },
105
+ { additionalProperties: false },
106
+ ),
107
+ Type.Object(
108
+ {
109
+ kind: Type.Literal("rewrite"),
110
+ path: Type.String(),
111
+ content: Type.String(),
112
+ },
113
+ { additionalProperties: false },
114
+ ),
115
+ Type.Object(
116
+ {
117
+ kind: Type.Literal("create"),
118
+ path: Type.String(),
119
+ content: Type.String({ minLength: 1 }),
120
+ mode: Type.Union([
121
+ Type.Literal("regular"),
122
+ Type.Literal("executable"),
123
+ ]),
124
+ },
125
+ { additionalProperties: false },
126
+ ),
127
+ Type.Object(
128
+ {
129
+ kind: Type.Literal("delete"),
130
+ path: Type.String(),
131
+ },
132
+ { additionalProperties: false },
133
+ ),
134
+ ]),
135
+ { minItems: 1, maxItems: 128 },
136
+ ),
97
137
  },
98
138
  { additionalProperties: false },
99
139
  ),
@@ -188,6 +228,9 @@ export interface CandidateArtifactSubmission {
188
228
  "beginCandidate" | "appendCandidateSegment" | "sealCandidate"
189
229
  >;
190
230
  identity: BeginCandidateInput;
231
+ workspaceRoot: string;
232
+ writePaths: readonly string[];
233
+ deletePaths: readonly string[];
191
234
  }
192
235
 
193
236
  export function createCandidateArtifactTool(
@@ -199,10 +242,7 @@ export function createCandidateArtifactTool(
199
242
  let failure: CandidateFailure | undefined;
200
243
  let attempts = 0;
201
244
  let schema: SubmitSchemaState = "not-submitted";
202
- let segmentCount = 0;
203
- let totalBytes = 0;
204
- const candidateHash = createHash("sha256");
205
- const identity: IdentityOutcome = {
245
+ let identity: IdentityOutcome = {
206
246
  request: true,
207
247
  role: true,
208
248
  task: true,
@@ -212,7 +252,7 @@ export function createCandidateArtifactTool(
212
252
  name: "abel_submit_result",
213
253
  label: "Submit Abel Candidate Artifact",
214
254
  description:
215
- "Submit ordered raw-text candidate segments, atomically seal the complete candidate, or request bounded approved context. Encoding and hashes are computed by this trusted tool.",
255
+ "Submit one complete structured candidate patch or request bounded approved context. Diff generation, chunking, encoding, hashes, and atomic sealing are owned by this trusted tool.",
216
256
  executionMode: "sequential",
217
257
  parameters,
218
258
  async execute(_toolCallId, params) {
@@ -220,6 +260,22 @@ export function createCandidateArtifactTool(
220
260
  throw new Error("candidate terminal result already submitted");
221
261
  }
222
262
  attempts++;
263
+ if (attempts > 2) {
264
+ schema = "invalid";
265
+ failure = {
266
+ kind: "artifact",
267
+ code: "invalid-structural-result",
268
+ stage: "structural-submit",
269
+ };
270
+ throw new Error("structural submission correction limit exceeded");
271
+ }
272
+ identity = {
273
+ request: true,
274
+ role: true,
275
+ task: true,
276
+ phase: true,
277
+ };
278
+ failure = undefined;
223
279
  const value = params as unknown as Record<string, unknown>;
224
280
  if (value.candidateId !== input.identity.candidateId) {
225
281
  identity.request = false;
@@ -232,18 +288,15 @@ export function createCandidateArtifactTool(
232
288
  throw new Error("candidate identity mismatch");
233
289
  }
234
290
  schema = "valid";
235
- if (value.kind === "candidate-segment") {
236
- const bytes = Buffer.from(value.text as string, "utf8");
237
- const segmentHash = createHash("sha256").update(bytes).digest("hex");
238
- let accepted: ReturnType<
239
- CandidateArtifactSubmission["ledger"]["appendCandidateSegment"]
240
- >;
291
+ if (value.kind === "candidate-patch") {
292
+ let diff: string;
241
293
  try {
242
- accepted = input.ledger.appendCandidateSegment({
243
- ...input.identity,
244
- sequence: value.sequence as number,
245
- bytes,
246
- segmentHash,
294
+ diff = compileCandidatePatch({
295
+ root: input.workspaceRoot,
296
+ writePaths: input.writePaths,
297
+ deletePaths: input.deletePaths,
298
+ operations: value.operations as CandidatePatchOperation[],
299
+ maxBytes: TASK_LEDGER_LIMITS.maxCandidateBytes,
247
300
  });
248
301
  } catch (error) {
249
302
  schema = "invalid";
@@ -254,34 +307,59 @@ export function createCandidateArtifactTool(
254
307
  };
255
308
  throw error;
256
309
  }
257
- if (!accepted.ok) {
258
- failure = {
259
- kind: "result-limit",
260
- limitBytes: accepted.limitBytes,
261
- };
262
- } else {
263
- candidateHash.update(bytes);
264
- segmentCount = accepted.nextSequence;
265
- totalBytes = accepted.totalBytes;
310
+ const bytes = Buffer.from(diff, "utf8");
311
+ const candidateHash = createHash("sha256").update(bytes).digest("hex");
312
+ let sequence = 0;
313
+ for (
314
+ let offset = 0;
315
+ offset < bytes.length;
316
+ offset += TASK_LEDGER_LIMITS.maxSegmentBytes
317
+ ) {
318
+ const segment = bytes.subarray(
319
+ offset,
320
+ Math.min(offset + TASK_LEDGER_LIMITS.maxSegmentBytes, bytes.length),
321
+ );
322
+ let accepted: ReturnType<
323
+ CandidateArtifactSubmission["ledger"]["appendCandidateSegment"]
324
+ >;
325
+ try {
326
+ accepted = input.ledger.appendCandidateSegment({
327
+ ...input.identity,
328
+ sequence,
329
+ bytes: segment,
330
+ segmentHash: createHash("sha256").update(segment).digest("hex"),
331
+ });
332
+ } catch (error) {
333
+ schema = "invalid";
334
+ failure = {
335
+ kind: "artifact",
336
+ code: "invalid-diff",
337
+ stage: "candidate-diff",
338
+ };
339
+ throw error;
340
+ }
341
+ if (!accepted.ok) {
342
+ failure = {
343
+ kind: "result-limit",
344
+ limitBytes: accepted.limitBytes,
345
+ };
346
+ return {
347
+ content: [],
348
+ details: accepted,
349
+ terminate: true,
350
+ };
351
+ }
352
+ sequence = accepted.nextSequence;
266
353
  }
267
- return {
268
- content: [
269
- { type: "text" as const, text: "Candidate segment accepted." },
270
- ],
271
- details: accepted,
272
- ...(!accepted.ok ? { terminate: true } : {}),
273
- };
274
- }
275
- if (value.kind === "candidate-seal") {
276
354
  let sealed: ReturnType<
277
355
  CandidateArtifactSubmission["ledger"]["sealCandidate"]
278
356
  >;
279
357
  try {
280
358
  sealed = input.ledger.sealCandidate({
281
359
  ...input.identity,
282
- segmentCount,
283
- totalBytes,
284
- candidateHash: candidateHash.digest("hex"),
360
+ segmentCount: sequence,
361
+ totalBytes: bytes.length,
362
+ candidateHash,
285
363
  });
286
364
  } catch (error) {
287
365
  schema = "invalid";
@@ -318,6 +396,12 @@ export function createCandidateArtifactTool(
318
396
  refs.some((relative) => !isValidRelativePath(relative)) ||
319
397
  new Set(refs).size !== refs.length
320
398
  ) {
399
+ schema = "invalid";
400
+ failure = {
401
+ kind: "artifact",
402
+ code: "invalid-structural-result",
403
+ stage: "structural-submit",
404
+ };
321
405
  throw new Error("context request refs are invalid");
322
406
  }
323
407
  result = {
@@ -377,7 +461,7 @@ export function createSubmitTool(input: {
377
461
  let failure: CandidateFailure | undefined;
378
462
  let attempts = 0;
379
463
  let schema: SubmitSchemaState = "not-submitted";
380
- const identity: IdentityOutcome = {
464
+ let identity: IdentityOutcome = {
381
465
  request: true,
382
466
  role: true,
383
467
  task: true,
@@ -398,6 +482,22 @@ export function createSubmitTool(input: {
398
482
  : diffSchema,
399
483
  async execute(_toolCallId, params) {
400
484
  attempts++;
485
+ if (attempts > 2) {
486
+ schema = "invalid";
487
+ failure = {
488
+ kind: "artifact",
489
+ code: "invalid-structural-result",
490
+ stage: "structural-submit",
491
+ };
492
+ throw new Error("structural submission correction limit exceeded");
493
+ }
494
+ identity = {
495
+ request: true,
496
+ role: true,
497
+ task: true,
498
+ phase: true,
499
+ };
500
+ failure = undefined;
401
501
  const value = params as unknown as Record<string, unknown>;
402
502
  if (value === null || typeof value !== "object") {
403
503
  identity.request = false;
@@ -113,7 +113,6 @@ class RouteAttemptStop extends Error {
113
113
  function isRole(value: unknown): value is WorkerRole {
114
114
  return [
115
115
  "design-explorer",
116
- "contract-reviewer",
117
116
  "implementation-worker",
118
117
  "diagnosis-worker",
119
118
  ].includes(String(value));
@@ -2833,13 +2833,14 @@ class DurableWorkflowComposition
2833
2833
  const phase = request.task.phases[request.phase];
2834
2834
  if (!phase) throw new Error("workflow-task-phase-invalid");
2835
2835
  const allPhases = Object.values(request.task.phases);
2836
+ const approvedWritePaths = [
2837
+ ...new Set(allPhases.flatMap((boundary) => boundary.write)),
2838
+ ].sort();
2839
+ const approvedDeletePaths = [
2840
+ ...new Set(allPhases.flatMap((boundary) => boundary.delete)),
2841
+ ].sort();
2836
2842
  const approvedPaths = [
2837
- ...new Set(
2838
- allPhases.flatMap((boundary) => [
2839
- ...boundary.write,
2840
- ...boundary.delete,
2841
- ]),
2842
- ),
2843
+ ...new Set([...approvedWritePaths, ...approvedDeletePaths]),
2843
2844
  ].sort();
2844
2845
  const boundPaths = [
2845
2846
  ...new Set(
@@ -2915,7 +2916,13 @@ class DurableWorkflowComposition
2915
2916
  task: structuredClone(request.task),
2916
2917
  workspaceRoot: proposalRoot,
2917
2918
  ledgerProjection,
2918
- candidateArtifact: { ledger, identity },
2919
+ candidateArtifact: {
2920
+ ledger,
2921
+ identity,
2922
+ workspaceRoot: proposalRoot,
2923
+ writePaths: approvedWritePaths,
2924
+ deletePaths: approvedDeletePaths,
2925
+ },
2919
2926
  route: attempt.route,
2920
2927
  repair,
2921
2928
  signal: attempt.signal,
@@ -3684,7 +3691,13 @@ class DurableWorkflowComposition
3684
3691
  task: structuredClone(input.task),
3685
3692
  workspaceRoot: proposalRoot,
3686
3693
  ledgerProjection,
3687
- candidateArtifact: { ledger, identity },
3694
+ candidateArtifact: {
3695
+ ledger,
3696
+ identity,
3697
+ workspaceRoot: proposalRoot,
3698
+ writePaths: phase.write,
3699
+ deletePaths: phase.delete,
3700
+ },
3688
3701
  route: attempt.route,
3689
3702
  signal: attempt.signal,
3690
3703
  onHeaders: attempt.onHeaders,
@@ -4811,21 +4824,6 @@ export class WorkflowEngine {
4811
4824
  )?.run_id;
4812
4825
  }
4813
4826
 
4814
- #lookupProvisionalRun(provisionalKey: string): string | undefined {
4815
- return (
4816
- this.#database
4817
- .prepare(
4818
- `SELECT run_id FROM runs
4819
- WHERE root_hash = ? AND stage = 'abel-design'
4820
- AND lookup_key = ?`,
4821
- )
4822
- .get(
4823
- this.#stateRoot.consumerRootHash,
4824
- `provisional:${provisionalKey}`,
4825
- ) as { run_id: string } | undefined
4826
- )?.run_id;
4827
- }
4828
-
4829
4827
  #ensureEngineRun(runId: string): void {
4830
4828
  this.#database
4831
4829
  .prepare(
@@ -6846,118 +6844,7 @@ export class WorkflowEngine {
6846
6844
  signal?: AbortSignal,
6847
6845
  onActivity?: (event: WorkflowActivityUpdate) => void,
6848
6846
  ) {
6849
- if (!("change" in command)) {
6850
- const existingRun = this.#lookupProvisionalRun(command.provisionalKey);
6851
- if (existingRun) {
6852
- this.#ensureEngineRun(existingRun);
6853
- const replay = this.#operationReplay(
6854
- existingRun,
6855
- command.operationId,
6856
- command.command,
6857
- );
6858
- if (replay) return replay;
6859
- const restarted = this.#runStore.startRun({
6860
- stage: "abel-design",
6861
- provisionalKey: command.provisionalKey,
6862
- operationId: command.operationId,
6863
- });
6864
- const begun = this.#beginOperation(
6865
- existingRun,
6866
- command.operationId,
6867
- command.command,
6868
- );
6869
- if (begun) return begun;
6870
- if (restarted.state === "created") {
6871
- const lease = this.#operationLease(existingRun, command.operationId);
6872
- this.#transition(
6873
- existingRun,
6874
- "paused",
6875
- `${command.operationId}:awaiting-gate-a`,
6876
- "design-awaiting-gate-a",
6877
- lease,
6878
- );
6879
- }
6880
- return this.#commitOperation(
6881
- existingRun,
6882
- command.operationId,
6883
- this.#statusByRun(existingRun),
6884
- );
6885
- }
6886
- const created = this.#runStore.startRun({
6887
- stage: "abel-design",
6888
- provisionalKey: command.provisionalKey,
6889
- operationId: command.operationId,
6890
- });
6891
- this.#ensureEngineRun(created.runId);
6892
- const begun = this.#beginOperation(
6893
- created.runId,
6894
- command.operationId,
6895
- command.command,
6896
- );
6897
- if (begun) return begun;
6898
- const lease = this.#operationLease(created.runId, command.operationId);
6899
- this.#transition(
6900
- created.runId,
6901
- "paused",
6902
- `${command.operationId}:awaiting-gate-a`,
6903
- "design-awaiting-gate-a",
6904
- lease,
6905
- );
6906
- return this.#commitOperation(
6907
- created.runId,
6908
- command.operationId,
6909
- this.#statusByRun(created.runId),
6910
- );
6911
- }
6912
- let existingRun = this.#lookupRun(command.stage, command.change);
6913
- const requestedProvisionalRun =
6914
- command.stage === "abel-design" && command.provisionalKey
6915
- ? this.#lookupProvisionalRun(command.provisionalKey)
6916
- : undefined;
6917
- if (command.provisionalKey && !requestedProvisionalRun) {
6918
- throw new Error("provisional-design-run-not-found");
6919
- }
6920
- if (
6921
- existingRun &&
6922
- requestedProvisionalRun &&
6923
- existingRun !== requestedProvisionalRun
6924
- ) {
6925
- throw new Error("provisional-design-run-conflict");
6926
- }
6927
- if (
6928
- existingRun &&
6929
- command.stage === "abel-design" &&
6930
- ["completed", "discarded", "rejected"].includes(
6931
- this.#runStore.status(existingRun).state,
6932
- )
6933
- ) {
6934
- const restarted = this.#runStore.startRun({
6935
- stage: command.stage,
6936
- change: command.change,
6937
- operationId: command.operationId,
6938
- });
6939
- existingRun = restarted.runId;
6940
- this.#ensureEngineRun(existingRun);
6941
- const begun = this.#beginOperation(
6942
- existingRun,
6943
- command.operationId,
6944
- command.command,
6945
- );
6946
- if (begun) return begun;
6947
- const lease = this.#operationLease(existingRun, command.operationId);
6948
- this.#transition(
6949
- existingRun,
6950
- "paused",
6951
- `${command.operationId}:awaiting-design-evidence`,
6952
- "design-awaiting-evidence",
6953
- lease,
6954
- );
6955
- return this.#commitOperation(
6956
- existingRun,
6957
- command.operationId,
6958
- this.#statusByRun(existingRun),
6959
- );
6960
- }
6847
+ const existingRun = this.#lookupRun(command.stage, command.change);
6961
6848
  if (existingRun) {
6962
6849
  this.#ensureEngineRun(existingRun);
6963
6850
  const replay = this.#operationReplay(
@@ -6986,17 +6873,6 @@ export class WorkflowEngine {
6986
6873
  }
6987
6874
  }
6988
6875
 
6989
- if (requestedProvisionalRun) {
6990
- this.#runStore.bindChange({
6991
- runId: requestedProvisionalRun,
6992
- change: command.change,
6993
- operationId: `bind-change-${hash(
6994
- requestedProvisionalRun,
6995
- command.change,
6996
- ).slice(0, 40)}`,
6997
- });
6998
- existingRun = requestedProvisionalRun;
6999
- }
7000
6876
  const runId =
7001
6877
  existingRun ??
7002
6878
  this.#runStore.startRun({
@@ -7012,20 +6888,6 @@ export class WorkflowEngine {
7012
6888
  );
7013
6889
  if (begun) return begun;
7014
6890
  const lease = this.#operationLease(runId, command.operationId);
7015
- if (command.stage === "abel-design") {
7016
- this.#transition(
7017
- runId,
7018
- "paused",
7019
- `${command.operationId}:awaiting-design-evidence`,
7020
- "design-awaiting-evidence",
7021
- lease,
7022
- );
7023
- return this.#commitOperation(
7024
- runId,
7025
- command.operationId,
7026
- this.#statusByRun(runId),
7027
- );
7028
- }
7029
6891
  this.#transition(
7030
6892
  runId,
7031
6893
  "validating-delivery",
@@ -7087,17 +6949,6 @@ export class WorkflowEngine {
7087
6949
  if (replay) return replay;
7088
6950
  const lease = this.#operationLease(runId, command.operationId);
7089
6951
  const current = this.#runStore.status(runId);
7090
- if (current.stage === "abel-design") {
7091
- if (current.state !== "paused") {
7092
- this.#interruptOperation(runId, command.operationId);
7093
- throw new Error("resume-not-allowed");
7094
- }
7095
- return this.#commitOperation(
7096
- runId,
7097
- command.operationId,
7098
- this.#statusByRun(runId),
7099
- );
7100
- }
7101
6952
  if (current.state === "recovering") {
7102
6953
  emitWorkflowActivity(onActivity, {
7103
6954
  state: "recovering",
@@ -1,11 +0,0 @@
1
- # Role: contract-reviewer
2
-
3
- You are a package-owned read-only professional Agent for terminal and checkpoint review of approved design delivery.
4
-
5
- You inspect the approved contracts, canonical `ImplementPlan`, receipts, raw and canonical hashes, verification closure, traceability, and task DAG ONLY through the scoped `read`, `grep`, `find`, and `ls` tools.
6
- You never write, mutate, or run validation.
7
-
8
- Your output is a compact structured evidence object returned through `abel_submit_result`, containing the review identity, the exact artifact hashes you verified, suite-evidence status, and typed `delivery-invalid` or `approval-boundary` facts (empty when the delivery is sound).
9
-
10
- You never patch product code, AGENTS files, or task state.
11
- Review evidence is read-only; the parent resolves it without a stage selection, Gate decision, or recovery recommendation from you.