@abelxiaoxing/cadence 1.3.0 → 1.4.0

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.
Files changed (45) hide show
  1. package/README.md +115 -10
  2. package/package.json +5 -3
  3. package/prompts/abel-design.md +71 -17
  4. package/prompts/abel-diagnose.md +10 -1
  5. package/prompts/abel-implement.md +118 -20
  6. package/prompts/abel-init.md +9 -2
  7. package/src/apply-transaction.ts +16 -14
  8. package/src/artifact-store.ts +17 -0
  9. package/src/candidate-patch.ts +2 -1
  10. package/src/canonical.ts +16 -0
  11. package/src/change-contract.ts +268 -0
  12. package/src/child-session.ts +4 -3
  13. package/src/contracts.ts +141 -33
  14. package/src/control-contracts.ts +51 -1
  15. package/src/delivery-compiler.ts +148 -31
  16. package/src/design-control.ts +69 -11
  17. package/src/design-journal.ts +216 -72
  18. package/src/durable-workflow.ts +4385 -0
  19. package/src/implement-graph.ts +11 -16
  20. package/src/index.ts +380 -981
  21. package/src/isolation-backend.ts +84 -23
  22. package/src/package-delivery.ts +486 -0
  23. package/src/package-verification.ts +509 -0
  24. package/src/route-policy.ts +1 -11
  25. package/src/run-state.ts +4 -16
  26. package/src/run-store.ts +5 -144
  27. package/src/scoped-grep-worker.mjs +23 -0
  28. package/src/scoped-tools.ts +312 -116
  29. package/src/sqlite-schema.ts +171 -0
  30. package/src/storage-schema.ts +352 -0
  31. package/src/subagent-activity.ts +14 -1
  32. package/src/submit-tool.ts +20 -2
  33. package/src/task-ledger.ts +14 -55
  34. package/src/verification-capability.ts +298 -102
  35. package/src/verification-environment.ts +213 -0
  36. package/src/verification-identity.ts +77 -0
  37. package/src/worker-broker.ts +97 -34
  38. package/src/workflow-engine.ts +28 -8285
  39. package/src/workflow-policy.ts +1427 -0
  40. package/src/workflow-state-machine.ts +4478 -0
  41. package/src/workspace-io-entry.mjs +58 -0
  42. package/src/workspace-io-worker.mjs +984 -0
  43. package/src/workspace-io.ts +84 -0
  44. package/src/workspace-store.ts +125 -19
  45. package/skills/abel-workflow/SKILL.md +0 -205
@@ -0,0 +1,84 @@
1
+ import { Worker } from "node:worker_threads";
2
+
3
+ export interface WorkspaceIoMetrics {
4
+ operation: "captureBaseline" | "materialize" | "verificationIdentity";
5
+ elapsedMs: number;
6
+ files: number;
7
+ bytes: number;
8
+ }
9
+
10
+ /** Trusted local I/O worker; it has no model, tools, or workflow authority. */
11
+ export async function runWorkspaceIo<T>(input: {
12
+ root: string;
13
+ artifactRoot: string;
14
+ operation: WorkspaceIoMetrics["operation"];
15
+ args: unknown[];
16
+ signal?: AbortSignal;
17
+ onMetrics?: (metrics: WorkspaceIoMetrics) => void;
18
+ }): Promise<T> {
19
+ input.signal?.throwIfAborted();
20
+ const cancelled = new SharedArrayBuffer(4);
21
+ const flag = new Int32Array(cancelled);
22
+ const started = performance.now();
23
+ const worker = new Worker(
24
+ new URL("./workspace-io-worker.mjs", import.meta.url),
25
+ {
26
+ workerData: {
27
+ root: input.root,
28
+ artifactRoot: input.artifactRoot,
29
+ operation: input.operation,
30
+ args: input.args,
31
+ cancelled,
32
+ },
33
+ // The shipped worker is pure JavaScript, including when installed under node_modules.
34
+ execArgv: [],
35
+ },
36
+ );
37
+ const abort = () => {
38
+ Atomics.store(flag, 0, 1);
39
+ worker.postMessage("cancel");
40
+ };
41
+ input.signal?.addEventListener("abort", abort, { once: true });
42
+ if (input.signal?.aborted) abort();
43
+ try {
44
+ return await new Promise<T>((resolve, reject) => {
45
+ let message:
46
+ | {
47
+ ok: boolean;
48
+ result?: T;
49
+ error?: string;
50
+ files: number;
51
+ bytes: number;
52
+ }
53
+ | undefined;
54
+ let failure: Error | undefined;
55
+ worker.once("message", (value) => {
56
+ message = value;
57
+ });
58
+ worker.once("error", (error) => {
59
+ failure = error;
60
+ });
61
+ // Wait for exit so no mutation or Git process survives command settlement.
62
+ worker.once("exit", (code) => {
63
+ try {
64
+ input.onMetrics?.({
65
+ operation: input.operation,
66
+ elapsedMs: performance.now() - started,
67
+ files: message?.files ?? 0,
68
+ bytes: message?.bytes ?? 0,
69
+ });
70
+ } catch {
71
+ /* Metrics cannot change workflow facts. */
72
+ }
73
+ if (input.signal?.aborted) reject(input.signal.reason);
74
+ else if (failure || code !== 0 || !message)
75
+ reject(new Error("workspace-io-unavailable"));
76
+ else if (!message.ok)
77
+ reject(new Error(message.error ?? "workspace-io-failed"));
78
+ else resolve(message.result as T);
79
+ });
80
+ });
81
+ } finally {
82
+ input.signal?.removeEventListener("abort", abort);
83
+ }
84
+ }
@@ -1,4 +1,4 @@
1
- import { execFileSync } from "node:child_process";
1
+ import { execFile, execFileSync } from "node:child_process";
2
2
  import { createHash, randomUUID } from "node:crypto";
3
3
  import {
4
4
  chmodSync,
@@ -14,10 +14,12 @@ import {
14
14
  writeFileSync,
15
15
  } from "node:fs";
16
16
  import path from "node:path";
17
-
17
+ import { promisify } from "node:util";
18
18
  import type { ArtifactStore } from "./artifact-store.ts";
19
+ import { compareCanonicalStrings } from "./canonical.ts";
19
20
  import { isValidRelativePath } from "./contracts.ts";
20
21
  import { observeSafePath } from "./safe-path.ts";
22
+ import { runWorkspaceIo, type WorkspaceIoMetrics } from "./workspace-io.ts";
21
23
 
22
24
  const SHA256 = /^[a-f0-9]{64}$/u;
23
25
 
@@ -43,6 +45,7 @@ export interface WorkspaceRevision {
43
45
  }
44
46
 
45
47
  export interface CaptureBaselineInput {
48
+ gitTimeoutMs?: number;
46
49
  consumerRoot: string;
47
50
  approvedUntracked?: string[];
48
51
  absent?: string[];
@@ -80,7 +83,7 @@ function hash(...values: Array<string | Uint8Array>): string {
80
83
  function stableEntries(entries: WorkspaceEntries): WorkspaceEntries {
81
84
  return Object.fromEntries(
82
85
  Object.entries(entries)
83
- .sort(([left], [right]) => left.localeCompare(right))
86
+ .sort(([left], [right]) => compareCanonicalStrings(left, right))
84
87
  .map(([relative, entry]) => [relative, { ...entry }]),
85
88
  );
86
89
  }
@@ -258,7 +261,19 @@ export class WorkspaceStore {
258
261
  readonly #revisions: string;
259
262
  readonly #artifacts: ArtifactStore;
260
263
 
261
- constructor(root: string, artifacts: ArtifactStore) {
264
+ readonly #checkCancelled: () => void;
265
+ readonly #onMetrics?: (metrics: WorkspaceIoMetrics) => void;
266
+
267
+ constructor(
268
+ root: string,
269
+ artifacts: ArtifactStore,
270
+ options: {
271
+ checkCancelled?: () => void;
272
+ onMetrics?: (metrics: WorkspaceIoMetrics) => void;
273
+ } = {},
274
+ ) {
275
+ this.#checkCancelled = options.checkCancelled ?? (() => {});
276
+ this.#onMetrics = options.onMetrics;
262
277
  if (!path.isAbsolute(root)) throw new Error("workspace-store-root-invalid");
263
278
  this.root = path.resolve(root);
264
279
  this.#revisions = path.join(this.root, "revisions");
@@ -296,8 +311,12 @@ export class WorkspaceStore {
296
311
  }
297
312
 
298
313
  writeAtomic(target, Buffer.from(`${JSON.stringify(revision)}\n`));
299
- for (const entry of Object.values(entries)) {
300
- if (entry.kind === "file") this.#artifacts.retain(entry.hash);
314
+ const inherited = parentRevisionId
315
+ ? this.getRevision(parentRevisionId).entries
316
+ : {};
317
+ for (const [relative, entry] of Object.entries(entries)) {
318
+ if (entry.kind === "file" && !sameEntry(entry, inherited[relative]))
319
+ this.#artifacts.retain(entry.hash);
301
320
  }
302
321
  return structuredClone(revision);
303
322
  }
@@ -327,12 +346,27 @@ export class WorkspaceStore {
327
346
  const excluded = [".git", "node_modules", ...(input.excludedPaths ?? [])];
328
347
  for (const relative of excluded) requireSafeRelative(relative);
329
348
 
349
+ return this.#captureBaselineFiles(
350
+ input,
351
+ this.#trackedFiles(input),
352
+ consumerRoot,
353
+ excluded,
354
+ );
355
+ }
356
+
357
+ #trackedFiles(input: CaptureBaselineInput): string[] {
358
+ const timeout = input.gitTimeoutMs ?? 30_000;
359
+ if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30_000)
360
+ throw new Error("workspace-git-timeout-invalid");
361
+ const consumerRoot = path.resolve(input.consumerRoot);
330
362
  let tracked: string[];
331
363
  try {
332
364
  tracked = parseNullSeparated(
333
365
  execFileSync("git", ["ls-files", "-z", "--cached"], {
334
366
  cwd: consumerRoot,
335
367
  maxBuffer: 32 * 1024 * 1024,
368
+ timeout,
369
+ killSignal: "SIGKILL",
336
370
  }),
337
371
  );
338
372
  } catch (error) {
@@ -342,11 +376,21 @@ export class WorkspaceStore {
342
376
  throw new Error("workspace-git-baseline-unavailable");
343
377
  }
344
378
 
379
+ return tracked;
380
+ }
381
+
382
+ #captureBaselineFiles(
383
+ input: CaptureBaselineInput,
384
+ tracked: string[],
385
+ consumerRoot: string,
386
+ excluded: string[],
387
+ ): WorkspaceRevision {
345
388
  const approved = input.approvedUntracked ?? [];
346
389
  const absent = input.absent ?? [];
347
390
  const selected = [...new Set([...tracked, ...approved])].sort();
348
391
  const entries: WorkspaceEntries = {};
349
392
  for (const relative of selected) {
393
+ this.#checkCancelled();
350
394
  requireSafeRelative(relative);
351
395
  if (isExcluded(relative, excluded)) continue;
352
396
  const observation = observeSafePath(consumerRoot, relative);
@@ -393,18 +437,13 @@ export class WorkspaceStore {
393
437
  const revision = requireRevisionShape(parsed);
394
438
  if (
395
439
  revision.revisionId !== id ||
396
- manifestHash(revision.entries) !== revision.manifestHash ||
440
+ (manifestHash(revision.entries) !== revision.manifestHash &&
441
+ hash("cadence-workspace-manifest", JSON.stringify(revision.entries)) !==
442
+ revision.manifestHash) ||
397
443
  revisionId(revision.parentRevisionId, revision.manifestHash) !== id
398
444
  ) {
399
445
  throw new Error("workspace-revision-integrity-invalid");
400
446
  }
401
- for (const entry of Object.values(revision.entries)) {
402
- if (entry.kind !== "file") continue;
403
- const bytes = this.#artifacts.read(entry.hash);
404
- if (bytes.byteLength !== entry.bytes) {
405
- throw new Error("workspace-revision-integrity-invalid");
406
- }
407
- }
408
447
  return structuredClone(revision);
409
448
  }
410
449
 
@@ -412,7 +451,7 @@ export class WorkspaceStore {
412
451
  const parent = this.getRevision(input.parentRevisionId);
413
452
  const entries = stableEntries(parent.entries);
414
453
  for (const [relative, change] of Object.entries(input.changes).sort(
415
- ([left], [right]) => left.localeCompare(right),
454
+ ([left], [right]) => compareCanonicalStrings(left, right),
416
455
  )) {
417
456
  requireSafeRelative(relative);
418
457
  if (
@@ -498,6 +537,7 @@ export class WorkspaceStore {
498
537
  const root = path.resolve(destination);
499
538
  ensureDirectory(root);
500
539
  for (const [relative, entry] of Object.entries(revision.entries)) {
540
+ this.#checkCancelled();
501
541
  requireSafeRelative(relative);
502
542
  const observation = observeSafePath(root, relative);
503
543
  if (entry.kind === "absent") {
@@ -525,12 +565,78 @@ export class WorkspaceStore {
525
565
  if (observation.kind !== "absent") {
526
566
  throw new Error("workspace-materialize-conflict");
527
567
  }
528
- const bytes = this.#artifacts.read(entry.hash);
529
- if (bytes.byteLength !== entry.bytes) {
568
+ const bytes = this.#artifacts.copyVerified(entry.hash, target);
569
+ if (bytes !== entry.bytes)
530
570
  throw new Error("workspace-revision-integrity-invalid");
531
- }
532
- writeFileSync(target, bytes, { flag: "wx", mode: entry.mode });
533
571
  chmodSync(target, entry.mode);
534
572
  }
535
573
  }
574
+ /** Used by the trusted I/O worker; Git is bounded and cancellable. */
575
+ async captureBaselineWithGit(
576
+ input: CaptureBaselineInput,
577
+ signal?: AbortSignal,
578
+ ): Promise<WorkspaceRevision> {
579
+ signal?.throwIfAborted();
580
+ const consumerRoot = path.resolve(input.consumerRoot);
581
+ const stat = lstatSync(consumerRoot, { throwIfNoEntry: false });
582
+ if (!stat?.isDirectory() || stat.isSymbolicLink())
583
+ throw new Error("unsafe-workspace-path");
584
+ const excluded = [".git", "node_modules", ...(input.excludedPaths ?? [])];
585
+ for (const relative of excluded) requireSafeRelative(relative);
586
+ const timeout = input.gitTimeoutMs ?? 30_000;
587
+ if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30_000)
588
+ throw new Error("workspace-git-timeout-invalid");
589
+ let tracked: string[];
590
+ try {
591
+ const { stdout } = await promisify(execFile)(
592
+ "git",
593
+ ["ls-files", "-z", "--cached"],
594
+ {
595
+ cwd: consumerRoot,
596
+ encoding: "buffer",
597
+ maxBuffer: 32 * 1024 * 1024,
598
+ timeout,
599
+ killSignal: "SIGKILL",
600
+ signal,
601
+ },
602
+ );
603
+ tracked = parseNullSeparated(stdout);
604
+ } catch (error) {
605
+ signal?.throwIfAborted();
606
+ if (error instanceof Error && error.message === "unsafe-workspace-path")
607
+ throw error;
608
+ throw new Error("workspace-git-baseline-unavailable");
609
+ }
610
+ this.#checkCancelled();
611
+ return this.#captureBaselineFiles(input, tracked, consumerRoot, excluded);
612
+ }
613
+
614
+ captureBaselineAsync(
615
+ input: CaptureBaselineInput,
616
+ signal?: AbortSignal,
617
+ ): Promise<WorkspaceRevision> {
618
+ return runWorkspaceIo({
619
+ root: this.root,
620
+ artifactRoot: this.#artifacts.root,
621
+ operation: "captureBaseline",
622
+ args: [input],
623
+ signal,
624
+ onMetrics: this.#onMetrics,
625
+ });
626
+ }
627
+
628
+ materializeAsync(
629
+ revisionId: string,
630
+ destination: string,
631
+ signal?: AbortSignal,
632
+ ): Promise<void> {
633
+ return runWorkspaceIo({
634
+ root: this.root,
635
+ artifactRoot: this.#artifacts.root,
636
+ operation: "materialize",
637
+ args: [revisionId, destination],
638
+ signal,
639
+ onMetrics: this.#onMetrics,
640
+ });
641
+ }
536
642
  }
@@ -1,205 +0,0 @@
1
- ---
2
- name: abel-workflow
3
- description: Shared contract loaded only after an explicit /abel-init, /abel-design, /abel-implement, or /abel-diagnose invocation; never activate it for ordinary engineering work
4
- ---
5
-
6
- # Abel workflow
7
-
8
- This Skill applies only after the user explicitly invokes `/abel-init`, `/abel-design`, `/abel-implement`, or `/abel-diagnose`.
9
- Merely finding these files, an OpenSpec change, an AGENTS index entry, or the word “Abel” does not activate a workflow.
10
- Load this Skill only for the invoked stage and do not carry stage authority into an ordinary engineering request.
11
-
12
- Stage prompts own their input and procedure.
13
- This file owns the cross-stage integrity, recovery, verification, and authority rules.
14
-
15
- ## Gates and trusted delivery
16
-
17
- Gate A approves the complete observable behavior contract.
18
- Gate B separately approves the complete technical implementation contract.
19
- Neither Gate grants tool permission, repository permission, identity, or authority outside its recorded scope.
20
-
21
- Design and Implement may run in fresh contexts.
22
- Trusted delivery requires:
23
-
24
- - canonical Gate A and Gate B proofs for the same root/change/schema, each binding its approval revision, canonical contract hash, and owner-private journal record hash;
25
- - an owner-private finalization fact binding the exact delivery revision, ready-receipt raw hash, both Gate proofs, and canonical plan hash;
26
- - safe relative artifact paths and exact raw SHA-256 bindings;
27
- - a canonical `implement-plan.json` with raw hash and canonical hash;
28
- - OpenSpec strict validity and planning completeness;
29
- - Requirement → Scenario → Verification → Task traceability exactly once;
30
- - executable verification closure and complete output/path/dependency/AGENTS contracts.
31
-
32
- The loader aggregates all safe delivery diagnostics as `delivery-invalid` before a Worker starts.
33
- Only tracked task checkbox markers for sealed task ids may normalize `[x]`/`[X]` to `[ ]`; every other byte remains hash-bound.
34
-
35
- A fresh context validates existing approvals and never asks the user to approve unchanged decisions.
36
- A new receipt revision is needed only when approved authority changes.
37
-
38
- ## Stage separation
39
-
40
- - **Init** is deterministic local setup/repair.
41
- It uses no Subagent dispatch, preserves dirty/human content and nested repositories, and is idempotent.
42
- - **Design** is evidence and delivery compilation only.
43
- Product code, tests, implementation candidates, and AGENTS files are read-only.
44
- The parent sees only its previously active `read`, `grep`, `find`, and `ls` tools plus `abel_dispatch` while Design is active.
45
- Design may use bounded read-only `design-explorer` packets.
46
- - **Implement** uses only the durable change command surface.
47
- It executes the sealed plan in private immutable revisions and applies the complete change once.
48
- - **Diagnose** independently follows reproduce → falsify → failing regression → minimum repair.
49
- It does not inherit Implement state, retry policy, or approval classification.
50
-
51
- Design and Diagnose bounded packets use `action: "run"` with their own stage; every Design packet also binds the durable Design `runId` that requested it.
52
- Implement never uses that packet protocol.
53
- Cross-stage calls fail closed.
54
-
55
- ## Durable Design control
56
-
57
- Design uses one closed private `action: "design"` family from its first operation onward: `start`, `status`, `bind-change`, `record-decision`, `approve-gate`, `write-artifact`, `delete-artifact`, `validate-plan-draft`, `compile-plan`, and `finalize-delivery`.
58
- `start` accepts either the complete transient requirement or an existing change name; `status` uses the returned run id; `bind-change` names a provisional run only after current Gate A approval.
59
- The control plane normalizes and hashes transient requirement, decision-contract, and Gate-A contract text itself, and Gate B binds a plan compiled after the current Gate A approval and latest substantive decisions without a caller-supplied hash.
60
- The owner-private SQLite journal retains only normalized evidence identities/hashes, versioned decision hashes and refs, Gate proof facts, canonical compiled-plan identity/bytes, finalization identity, and idempotent operation outcomes.
61
- It never retains prompts, transcripts, hidden reasoning, credentials, environment values, or raw model output.
62
-
63
- Accepted evidence is recorded only after structural validation.
64
- Same packet/result replay is idempotent; conflicting evidence for one packet id fails closed.
65
- A later behavior decision invalidates both Gates; a later technical decision or plan compilation invalidates Gate B only.
66
-
67
- The extension snapshots the exact pre-Design non-dispatch tool set; during Design it retains only the previously active `read`, `grep`, `find`, and `ls` tools plus `abel_dispatch`.
68
- It removes every write-capable or unknown tool and restores the snapshot on finalization, explicit finish, verified stage switch, session start, or shutdown.
69
- Private artifact mutation accepts bounded UTF-8 only at `.openspec.yaml`, `proposal.md`, `design.md`, `tasks.md`, `plan-draft.json`, and `specs/<safe-segments>/spec.md` relative to the active change root.
70
- It creates missing directories component-wise without following symlinks, writes atomically, deletes only an allowed regular file, journals only bounded path/hash outcomes, and replays idempotently.
71
- Code-owned `gate-a.yaml` and `ready.yaml` receipts remain forbidden; `implement-plan.json` remains unreachable except through compilation.
72
-
73
- All filesystem-mutating Design operations serialize against the durable expiring finalization lease.
74
- A competing or stale owner cannot inspect, replace, remove, or commit another operation's receipt bytes, and cleanup is fenced by the current operation's raw receipt hash.
75
- After current Gate A, the parent sends the fixed `plan-draft.json` input only through `write-artifact`.
76
- Read-only `validate-plan-draft` runs the same compiler checks without installing files or recording a plan revision and returns bounded task/phase/field diagnostics; unchanged invalid requests are not retried blindly.
77
- `compile-plan` owns canonical plan construction and installation.
78
- Gate B binds that exact stored canonical plan hash.
79
- `finalize-delivery` validates current proofs, OpenSpec, artifacts, traceability, and executable closure, writes Gate A and ready receipts atomically with `ready.yaml` last, rereads them, removes the draft, and completes Design.
80
-
81
- ## Canonical ImplementPlan
82
-
83
- `ImplementPlan` is the only machine source for implementation mechanics.
84
- It seals:
85
-
86
- - exact task ids, dependencies, objectives, context, paths, deletes, conflicts, resources, and locks;
87
- - structured Red, Green, optional Refactor verification plus verification inputs;
88
- - unique regular-file outputs and their producer task/phase;
89
- - task-affected and repair verification;
90
- - target/affected/full-suite baselines and normalized failure identity;
91
- - change affected/full-suite/post-apply verification;
92
- - an explicit `artifactCorrection.maxAttempts` of 2-3 total candidate launches per task phase and operation, including the initial launch;
93
- - bounded repair policy with `pre-existing | introduced | unresolved | environment` attribution;
94
- - parent-owned Markdown checkbox tracking;
95
- - approved managed-only AGENTS operations and verification.
96
-
97
- The compiler, not prose parsing or the caller, canonicalizes and validates the plan.
98
- A write set grants authority only; it never proves output existence.
99
- An absent input must resolve to one declared output from a transitive dependency.
100
- Unsafe paths, symlink components, missing local capability, cycles, conflicts without ordering, unsupported verification, and unbound outputs close readiness before Gate B.
101
-
102
- Impact closure must cover existing compatibility evidence, not only newly added tests.
103
- For public UI or API changes it seals the relevant route authorization, page state, API response and contract, public HTML/template/theme behavior, and approved browser E2E checks.
104
-
105
- Verification supports only shell-free `vitest`, `package-script`, `static-check`, and ordered `steps` contracts.
106
- Local runners/scripts, exact command bindings, args, classifications, verification inputs, `minTests`, and `noInstall` semantics are sealed.
107
- Arbitrary shell, `&&`, path escape, and implicit downloads are forbidden.
108
-
109
- ## Durable control
110
-
111
- Implement accepts only `start`, `status`, `resume`, `rebind`, `cancel`, and `discard` with stage, change, and operation identity where required.
112
- The internal-development command surface has no caller-supplied protocol version.
113
-
114
- - `start` is idempotent for one change run.
115
- - `status` is local and requires no Worker endpoint.
116
- - `resume` continues from durable facts; with a new approved `deliveryRevision` and `receiptHash`, it revalidates retained work and invalidates only incompatible tasks.
117
- - `rebind` selects another route already allowed by policy; it does not change the task contract.
118
- - `cancel` stops the active operation and preserves accepted progress.
119
- It is budget-neutral.
120
- - `discard` is the only explicit destructive terminal command.
121
- During apply it waits for safe recovery before cleanup.
122
-
123
- Repeated operation ids replay their committed outcomes.
124
- Private state retains run journals, plan bindings, normalized task evidence, artifacts, revisions, retry classification, and apply recovery facts; it never stores credentials, environment values, raw prompts, hidden reasoning, transcripts, or raw model output.
125
- Completed/discarded runs clean private change content after safe settlement.
126
-
127
- ## Red-Green-Refactor and attribution
128
-
129
- Before the first candidate, record target-contract, task-affected, and full-suite baselines.
130
- Pre-existing failures remain separate and never satisfy Red.
131
-
132
- 1. **Red:** the approved target must fail with its exact witness identity.
133
- Syntax/setup/no-test/wrong-identity failures are artifact defects.
134
- 2. **Green:** apply the minimum approved candidate in a disposable child revision and require target plus task-affected verification.
135
- 3. **Refactor:** optional in-boundary improvement with behavior and verification unchanged.
136
-
137
- The parent control plane alone applies accepted candidate bytes to the private cumulative revision, records `phase-verified`/`repair-verified`, advances one task checkbox, verifies outputs, and schedules dependents.
138
- A Worker submits one complete structured patch within the declared path set; trusted code validates its exact replacements and file operations, generates the unified diff, and seals the candidate.
139
- One rejected structural submission may be corrected once inside the same disposable child session; a second rejection ends that session without creating an unbounded model loop or accepting partial output.
140
-
141
- After all tasks, re-run every affected contract, compare the full suite with baseline, apply and verify sealed AGENTS operations, verify output postconditions, then prepare one currentness-checked journaled transaction and post-apply verification.
142
- The main workspace remains unchanged before final application eligibility.
143
-
144
- Attribution behavior:
145
-
146
- - `pre-existing`: baseline evidence; do not blame or speculatively edit.
147
- - `introduced`: reopen the owning task as `repairable` and perform bounded minimum repair inside its sealed boundary.
148
- - `unresolved`: pause with normalized identities and retained cumulative state until ownership evidence is available.
149
- - `environment`: pause before speculative edits and resume after capability restoration.
150
-
151
- A full-suite-only baseline failure outside affected contracts does not block completion unless the change introduces or worsens it.
152
-
153
- ## Recovery versus approval
154
-
155
- Artifact defects, wrong Red, stale revisions, transport errors, environment/adapter failures, capacity, conflicts, cancellation, process interruption, baseline failures, and introduced in-boundary repairs are recoverable Implement facts.
156
- They never select a workflow stage and never erase compatible progress.
157
-
158
- Automatic attempts are finite.
159
- Artifact correction uses the sealed `artifactCorrection.maxAttempts` independently for each task phase and operation; only typed artifact rejection consumes its 2-3 total-launch budget.
160
- Exhaustion pauses with the final safe code, scope, and last committed revision.
161
- A later `resume` starts a new operation budget while reusing durable baseline and phase facts.
162
- Oversized output becomes `needs-task-split`; partial patches are never accepted.
163
-
164
- Use `approval-needed` only when continuing requires new authority: observable behavior, architecture/policy, dependency, undeclared path, conflict/resource permission, verification contract, AGENTS target/impact/content, or another irreversible scope decision.
165
- Report the exact missing authority.
166
- After the user approves a new canonical delivery revision, resume the same run and preserve compatible work.
167
-
168
- An approval-needed status exposes the closed authority category, required Gate set, safe refs, retained run/revision, and `/abel-design --change <change>` as user guidance only.
169
- It never invokes Design automatically.
170
- Bare `resume` or `rebind` is not an immediately legal approval continuation; `conditionalCommands` exposes resume only with a newer `deliveryRevision` and matching `receiptHash`.
171
- Authority classification uses one exhaustive typed code map; an unknown approval code becomes an `approval-code-invalid` integrity pause rather than a guessed Gate.
172
- A later explicit Implement status may locally discover a newer receipt only when its change, revision/hash, both current Gate proofs, and owner-private finalization fact match; it exposes the exact pair without admitting it or mutating the run.
173
-
174
- Do not label an approved AGENTS checkpoint, documentation/test edit, endpoint outage, artifact error, stale snapshot, environment problem, baseline failure, or in-boundary repair as approval-needed.
175
-
176
- ## AGENTS indexes
177
-
178
- Design audits AGENTS read-only.
179
- Gate B seals `none | update-existing | create-index | remove-index`, exact target, owner task ids, complete managed block, and verification.
180
-
181
- Implement applies the sealed operation code-first in the private cumulative revision.
182
- It preserves every byte outside `<!-- ABEL:AGENTS-INDEX:START -->` and `<!-- ABEL:AGENTS-INDEX:END -->`; `remove-index` deletes the file only when no human content remains.
183
- A Worker never receives an AGENTS write path.
184
- Runtime/session ids, timestamps, credentials, approval state, and dirty-state ledgers never enter an index.
185
-
186
- ## Parent and Worker authority
187
-
188
- The parent owns user decisions, Gates, delivery compilation, candidate acceptance, verification classification, cumulative revisions, tracking, AGENTS, final apply, recovery, and truthful status.
189
-
190
- Evidence/diagnosis/implementation Agents receive only bounded context and scoped tools.
191
- They cannot approve a Gate, expand a boundary, apply a patch, edit AGENTS, advance a checkbox, choose a user recovery action, or report completion.
192
-
193
- `dev-browser` is required only by an explicitly approved browser-E2E contract.
194
- Its absence pauses that verification only and does not block unrelated tasks or stages.
195
-
196
- ## Truthful finish
197
-
198
- `queued`, `connecting`, `waiting-first-response`, `running`, `validating`, `retrying`, `verifying`, `paused`, `approval-needed`, `applying`, and `recovering` are nonterminal activity states.
199
- `operation-cancelled` ends only the current operation; `discarded` and `rejected` are non-success terminal run states.
200
- Tool settlement is not completion.
201
- Only durable `completed` after cumulative apply and postconditions receives success.
202
- Successful Design finalization, terminal Implement settlement, Diagnose `finish`, explicit stage finish, and session shutdown deactivate only `abel_dispatch`, drain packets, clear the parent bridge, and clear active stage identity.
203
- Design status separates `legalOperations` from top-level `packetActions`; `finish` is never a Design operation and is sent only as `{"action":"finish"}`.
204
- Gate waits and resumable pauses remain active for direct follow-up.
205
- Never archive, publish, release, stage, or commit implicitly.