@abelxiaoxing/cadence 1.3.0 → 1.4.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.
Files changed (76) hide show
  1. package/README.md +172 -13
  2. package/agents/design-explorer.md +17 -11
  3. package/agents/diagnosis-worker.md +9 -1
  4. package/agents/implementation-worker.md +3 -1
  5. package/config/plan-draft.example.json +122 -0
  6. package/config/plan-draft.multiple-tasks.example.json +345 -0
  7. package/package.json +9 -5
  8. package/prompts/abel-design.md +141 -38
  9. package/prompts/abel-diagnose.md +10 -1
  10. package/prompts/abel-implement.md +118 -20
  11. package/prompts/abel-init.md +9 -2
  12. package/src/activity-contracts.ts +82 -0
  13. package/src/apply-transaction.ts +16 -14
  14. package/src/artifact-store.ts +17 -0
  15. package/src/candidate-artifact.ts +143 -0
  16. package/src/candidate-patch.ts +33 -12
  17. package/src/candidate-workspace.ts +74 -0
  18. package/src/canonical.ts +16 -0
  19. package/src/change-contract-schema.ts +191 -0
  20. package/src/change-contract.ts +273 -0
  21. package/src/change-verification.ts +896 -0
  22. package/src/child-model.ts +129 -0
  23. package/src/child-session.ts +296 -328
  24. package/src/contracts.ts +325 -109
  25. package/src/control-contracts.ts +51 -1
  26. package/src/delivery-compiler.ts +241 -141
  27. package/src/delivery-revision.ts +42 -0
  28. package/src/design-control.ts +182 -15
  29. package/src/design-diagnostics.ts +150 -0
  30. package/src/design-journal.ts +216 -72
  31. package/src/durable-contracts.ts +210 -0
  32. package/src/durable-workflow.ts +963 -0
  33. package/src/evidence-draft.ts +122 -0
  34. package/src/implement-graph.ts +14 -16
  35. package/src/implement-plan.ts +70 -0
  36. package/src/index.ts +341 -1405
  37. package/src/isolation-backend.ts +84 -23
  38. package/src/model-source.ts +22 -0
  39. package/src/package-candidate.ts +224 -0
  40. package/src/package-delivery.ts +486 -0
  41. package/src/package-verification.ts +509 -0
  42. package/src/package-workflow.ts +253 -0
  43. package/src/packet-runtime.ts +27 -48
  44. package/src/parent-provider.ts +86 -198
  45. package/src/phase-execution.ts +2248 -0
  46. package/src/pi-adapter.ts +19 -0
  47. package/src/plan-draft-summary.ts +146 -0
  48. package/src/plan-draft.ts +493 -0
  49. package/src/route-policy.ts +1 -11
  50. package/src/run-state.ts +4 -16
  51. package/src/run-store.ts +5 -144
  52. package/src/scoped-grep-worker.mjs +23 -0
  53. package/src/scoped-tools.ts +312 -116
  54. package/src/sqlite-schema.ts +171 -0
  55. package/src/storage-schema.ts +352 -0
  56. package/src/subagent-activity.ts +46 -67
  57. package/src/submit-tool.ts +40 -61
  58. package/src/task-ledger.ts +14 -55
  59. package/src/transport-budget.ts +32 -0
  60. package/src/verification-capability.ts +298 -102
  61. package/src/verification-environment.ts +213 -0
  62. package/src/verification-identity.ts +77 -0
  63. package/src/worker-broker.ts +134 -91
  64. package/src/workflow-engine.ts +28 -8285
  65. package/src/workflow-policy.ts +1388 -0
  66. package/src/workflow-recovery-policy.ts +95 -0
  67. package/src/workflow-scheduling.ts +105 -0
  68. package/src/workflow-state-machine.ts +4086 -0
  69. package/src/workflow-status.ts +384 -0
  70. package/src/workspace-io-entry.mjs +58 -0
  71. package/src/workspace-io-worker.mjs +991 -0
  72. package/src/workspace-io.ts +84 -0
  73. package/src/workspace-store.ts +125 -19
  74. package/skills/abel-workflow/SKILL.md +0 -205
  75. package/src/empty-resource-loader.ts +0 -38
  76. package/src/parent-payload-bridge.ts +0 -523
@@ -9,13 +9,21 @@ import path from "node:path";
9
9
  import {
10
10
  type AtomicVerificationContract,
11
11
  type StructuredVerificationContract,
12
+ VERIFICATION_CONFIGURATION_PATHS,
12
13
  type VerificationAdapterCode,
13
14
  validateVerificationContract,
15
+ verificationBoundInputPaths,
14
16
  verificationInputPaths,
15
17
  verificationSteps,
16
18
  } from "./contracts.ts";
17
- import { type Bound, isCurrent, snapshotFiles } from "./file-snapshot.ts";
19
+ import {
20
+ type Bound,
21
+ isCurrent,
22
+ snapshotFile,
23
+ snapshotFiles,
24
+ } from "./file-snapshot.ts";
18
25
  import { isSafeRegularFile, observeSafePath } from "./safe-path.ts";
26
+ import { verificationEnvironmentDigest } from "./verification-identity.ts";
19
27
 
20
28
  export type VerificationCapabilityDiagnostic =
21
29
  | {
@@ -43,6 +51,7 @@ export interface CurrentVerificationCapability {
43
51
  verificationId: string;
44
52
  runnerBindings: VerificationRunnerBinding[];
45
53
  inputSnapshot: Bound;
54
+ runnerSnapshot: { paths: string[]; digest: string };
46
55
  }
47
56
 
48
57
  export type CurrentVerificationCapabilityResult =
@@ -64,14 +73,12 @@ export interface VerificationRunnerEnvironment {
64
73
  }
65
74
 
66
75
  export interface VerificationCapabilityOptions {
76
+ /** Trusted plan write/delete authority, supplied only for isolated execution. */
77
+ executionWritePaths?: readonly string[];
67
78
  dependencyOwner?: string;
68
79
  runnerEnvironment?: VerificationRunnerEnvironment;
69
80
  }
70
81
 
71
- const SHELL_OPERATOR = /[;&|`$<>\n\r\0]/u;
72
- const SIMPLE_TOKEN =
73
- /^(?:[a-z0-9][a-z0-9._:@/=-]*|--?[a-z0-9][a-z0-9._:@/=-]*)$/iu;
74
- const NETWORKING_RUNNERS = new Set(["bunx", "pnpx"]);
75
82
  const WINDOWS_EXECUTABLE_EXTENSIONS = new Set([".exe", ".cmd"]);
76
83
  const DEFAULT_WINDOWS_PATH_EXT = ".COM;.EXE;.BAT;.CMD";
77
84
  const SYSTEM_MOUNT_ROOTS = ["/usr", "/bin", "/lib", "/lib64", "/etc"];
@@ -633,95 +640,13 @@ function packageManifest(root: string): Record<string, unknown> | null {
633
640
  }
634
641
  }
635
642
 
636
- function validateScriptCommand(
637
- root: string,
638
- verificationId: string,
639
- command: string,
640
- dependencyOwner: string,
641
- runnerEnvironment: VerificationRunnerEnvironment,
642
- ): VerificationCapabilityResult {
643
- if (
644
- command.length === 0 ||
645
- command.trim() !== command ||
646
- SHELL_OPERATOR.test(command)
647
- ) {
648
- return adapterFailure(
649
- verificationId,
650
- "script-unsafe",
651
- "approved package script contains unsupported shell syntax",
652
- );
653
- }
654
- const tokens = command.split(/\s+/u);
655
- if (!SIMPLE_TOKEN.test(tokens[0] ?? "")) {
656
- return adapterFailure(
657
- verificationId,
658
- "script-unsafe",
659
- "approved package script cannot be represented as safe tokens",
660
- );
661
- }
662
- const executable = tokens[0] ?? "";
663
- const subcommands = new Set(tokens.slice(1));
664
- if (
665
- NETWORKING_RUNNERS.has(executable) ||
666
- (executable === "bun" && subcommands.has("x")) ||
667
- (executable === "npm" &&
668
- (subcommands.has("exec") || subcommands.has("x"))) ||
669
- (executable === "pnpm" && subcommands.has("dlx")) ||
670
- (executable === "yarn" && subcommands.has("dlx"))
671
- ) {
672
- return adapterFailure(
673
- verificationId,
674
- "script-unsafe",
675
- "package script uses a runner that can download implicitly",
676
- );
677
- }
678
- if (executable === "npx") {
679
- const local = tokens[1] === "--no-install" ? tokens[2] : undefined;
680
- if (
681
- !local ||
682
- !localExecutable(root, local, dependencyOwner, runnerEnvironment)
683
- ) {
684
- return adapterFailure(
685
- verificationId,
686
- "local-executable-missing",
687
- "npx requires --no-install and an installed local executable",
688
- );
689
- }
690
- const runner = resolveVerificationRunner("npx", runnerEnvironment);
691
- return runner
692
- ? capabilitySuccess(verificationId, [runner])
693
- : adapterFailure(
694
- verificationId,
695
- "runner-missing",
696
- "npx is unavailable or cannot be mounted safely",
697
- );
698
- }
699
- if (["node", "bun"].includes(executable)) {
700
- const runner = resolveVerificationRunner(executable, runnerEnvironment);
701
- return runner
702
- ? capabilitySuccess(verificationId, [runner])
703
- : adapterFailure(
704
- verificationId,
705
- "runner-missing",
706
- `approved runner ${executable} is unavailable or cannot be mounted safely`,
707
- );
708
- }
709
- return localExecutable(root, executable, dependencyOwner, runnerEnvironment)
710
- ? capabilitySuccess(verificationId)
711
- : adapterFailure(
712
- verificationId,
713
- "local-executable-missing",
714
- `local executable ${executable} is unavailable`,
715
- );
716
- }
717
-
718
643
  function validatePackageScript(
719
644
  root: string,
720
645
  verificationId: string,
721
646
  packageManager: string,
722
647
  script: string,
723
648
  approvedCommand: string,
724
- dependencyOwner = root,
649
+ _dependencyOwner = root,
725
650
  runnerEnvironment: VerificationRunnerEnvironment = {},
726
651
  ): VerificationCapabilityResult {
727
652
  const packageRunner = resolveVerificationRunner(
@@ -755,19 +680,91 @@ function validatePackageScript(
755
680
  `package.json script ${script} differs from the Gate B contract`,
756
681
  );
757
682
  }
758
- const scriptCapability = validateScriptCommand(
759
- root,
760
- verificationId,
761
- actual,
762
- dependencyOwner,
763
- runnerEnvironment,
683
+ // Approved scripts are opaque shell programs and may invoke nested scripts
684
+ // or hooks. Supply the available supported runtimes without parsing shell
685
+ // syntax or rejecting composition; isolation still owns their execution.
686
+ const runtimes = ["node", "bun", "npm", "npx", "pnpm", "yarn"].flatMap(
687
+ (command) => {
688
+ const binding = resolveVerificationRunner(command, runnerEnvironment);
689
+ return binding ? [binding] : [];
690
+ },
764
691
  );
765
- return scriptCapability.ok
766
- ? capabilitySuccess(verificationId, [
767
- packageRunner,
768
- ...scriptCapability.runnerBindings,
769
- ])
770
- : scriptCapability;
692
+ return capabilitySuccess(verificationId, [packageRunner, ...runtimes]);
693
+ }
694
+
695
+ /** Inspect active directives, never comments or arbitrary words in values. */
696
+ function unsupportedExecutionConfiguration(source: string): boolean {
697
+ const restricted = new Set([
698
+ "scriptshell",
699
+ "userconfig",
700
+ "globalconfig",
701
+ "yarnpath",
702
+ "plugins",
703
+ "auth",
704
+ "authtoken",
705
+ "password",
706
+ "token",
707
+ "npmauthtoken",
708
+ "npmauthident",
709
+ ]);
710
+ for (const line of source.split(/\r?\n/u)) {
711
+ let quote = "";
712
+ let escaped = false;
713
+ let active = "";
714
+ const parts: string[] = [];
715
+ let flowDepth = 0;
716
+ for (const character of line) {
717
+ if (escaped) {
718
+ active += character;
719
+ escaped = false;
720
+ continue;
721
+ }
722
+ if (character === "\\" && quote === '"') {
723
+ active += character;
724
+ escaped = true;
725
+ continue;
726
+ }
727
+ if (quote) {
728
+ active += character;
729
+ if (character === quote) quote = "";
730
+ } else if (character === '"' || character === "'") {
731
+ quote = character;
732
+ active += character;
733
+ } else if (
734
+ character === "#" ||
735
+ (character === ";" && active.trim() === "")
736
+ ) {
737
+ break;
738
+ } else if (
739
+ character === "{" ||
740
+ character === "}" ||
741
+ (character === "," && flowDepth > 0)
742
+ ) {
743
+ if (character === "{") flowDepth++;
744
+ if (character === "}") flowDepth = Math.max(0, flowDepth - 1);
745
+ parts.push(active);
746
+ active = "";
747
+ } else active += character;
748
+ }
749
+ parts.push(active);
750
+ for (const part of parts) {
751
+ if (part.includes("${")) return true;
752
+ const directive = part.match(
753
+ /^\s*(?:"((?:\\.|[^"\\])*)"|'([^']*)'|([^\s=]+))\s*(.*)$/u,
754
+ );
755
+ if (!directive) continue;
756
+ const rawKey = directive[1] ?? directive[2] ?? directive[3];
757
+ if (!rawKey.endsWith(":") && !directive[4]) continue;
758
+ const key = rawKey
759
+ .replace(/:$/u, "")
760
+ .split(":")
761
+ .at(-1)
762
+ ?.replace(/[-_]/gu, "")
763
+ .toLowerCase();
764
+ if (key && restricted.has(key)) return true;
765
+ }
766
+ }
767
+ return false;
771
768
  }
772
769
 
773
770
  function validateAtomicCapability(
@@ -775,7 +772,73 @@ function validateAtomicCapability(
775
772
  step: AtomicVerificationContract,
776
773
  dependencyOwner: string,
777
774
  runnerEnvironment: VerificationRunnerEnvironment,
775
+ executionWritePaths: readonly string[],
778
776
  ): VerificationCapabilityResult {
777
+ if (
778
+ step.kind === "vitest" &&
779
+ [
780
+ ...step.args,
781
+ ...(step.runner.kind === "package-script"
782
+ ? step.runner.command.split(/\s+/u)
783
+ : []),
784
+ ].some((arg) => /^--(?:reporters?|outputFile)(?:[.=]|$)/u.test(arg))
785
+ ) {
786
+ return adapterFailure(
787
+ step.id,
788
+ "report-arguments-conflict",
789
+ "Vitest report arguments are owned by the adapter",
790
+ );
791
+ }
792
+ if (step.executionBindings) {
793
+ for (const [relative, expected] of Object.entries(step.executionBindings)) {
794
+ if (executionWritePaths.includes(relative)) continue;
795
+ const observation = observeSafePath(root, relative);
796
+ if (
797
+ (expected === null && observation.kind !== "absent") ||
798
+ (expected !== null && snapshotFile(root, relative)?.sha256 !== expected)
799
+ ) {
800
+ return adapterFailure(
801
+ step.id,
802
+ "verification-config-mismatch",
803
+ "Bound execution inputs changed; recompile the verification contract",
804
+ );
805
+ }
806
+ }
807
+ }
808
+ for (const relative of verificationBoundInputPaths(step).filter((file) =>
809
+ VERIFICATION_CONFIGURATION_PATHS.includes(file),
810
+ )) {
811
+ const observation = observeSafePath(root, relative);
812
+ if (!["file", "absent"].includes(observation.kind))
813
+ return adapterFailure(
814
+ step.id,
815
+ "verification-config-unsafe",
816
+ "Execution configuration must be a regular file",
817
+ );
818
+ if (
819
+ observation.kind !== "file" ||
820
+ ![
821
+ ".npmrc",
822
+ ".yarnrc",
823
+ ".yarnrc.yml",
824
+ "bunfig.toml",
825
+ "pnpm-workspace.yaml",
826
+ ].includes(relative)
827
+ )
828
+ continue;
829
+ if (
830
+ lstatSync(path.join(root, relative)).size > 64 * 1024 ||
831
+ unsupportedExecutionConfiguration(
832
+ readFileSync(path.join(root, relative), "utf8"),
833
+ )
834
+ ) {
835
+ return adapterFailure(
836
+ step.id,
837
+ "verification-config-unsafe",
838
+ "Execution configuration requires unsupported credentials or host configuration",
839
+ );
840
+ }
841
+ }
779
842
  if (step.kind === "package-script") {
780
843
  return validatePackageScript(
781
844
  root,
@@ -862,6 +925,57 @@ function validateAtomicCapability(
862
925
  return capabilitySuccess(step.id);
863
926
  }
864
927
 
928
+ /** Enrich only a new design draft, never rewrite proof-bound canonical delivery bytes. */
929
+ export function bindDraftVerificationInputs(
930
+ root: string,
931
+ draft: unknown,
932
+ ): unknown {
933
+ const visit = (value: unknown): unknown => {
934
+ if (Array.isArray(value)) return value.map(visit);
935
+ if (!value || typeof value !== "object") return value;
936
+ const item = Object.fromEntries(
937
+ Object.entries(value).map(([key, child]) => [
938
+ key,
939
+ // Gate A is immutable approval authority, not an execution contract.
940
+ key === "changeContract" ? structuredClone(child) : visit(child),
941
+ ]),
942
+ );
943
+ if (
944
+ item.kind === "package-script" &&
945
+ item.command === undefined &&
946
+ typeof item.script === "string"
947
+ ) {
948
+ const scripts = packageManifest(root)?.scripts as
949
+ | Record<string, unknown>
950
+ | undefined;
951
+ item.command = scripts?.[item.script];
952
+ }
953
+ if (
954
+ ["vitest", "package-script", "static-check"].includes(
955
+ String(item.kind),
956
+ ) &&
957
+ typeof item.id === "string"
958
+ ) {
959
+ const validation = validateVerificationContract(item);
960
+ if (
961
+ validation.ok &&
962
+ verificationBoundInputPaths(validation.value).some((file) =>
963
+ VERIFICATION_CONFIGURATION_PATHS.includes(file),
964
+ )
965
+ ) {
966
+ item.executionBindings = Object.fromEntries(
967
+ VERIFICATION_CONFIGURATION_PATHS.map((relative) => [
968
+ relative,
969
+ snapshotFile(root, relative)?.sha256 ?? null,
970
+ ]),
971
+ );
972
+ }
973
+ }
974
+ return item;
975
+ };
976
+ return visit(draft);
977
+ }
978
+
865
979
  export function validateVerificationCapability(
866
980
  root: string,
867
981
  value: unknown,
@@ -882,6 +996,26 @@ export function bindCurrentVerificationCapability(
882
996
  if (!validation.ok) {
883
997
  throw new Error("verification-capability-invariant");
884
998
  }
999
+ let paths: string[];
1000
+ let runnerDigest: string;
1001
+ try {
1002
+ paths = verificationRunnerFiles(
1003
+ options.dependencyOwner ?? root,
1004
+ validation.value,
1005
+ capability.runnerBindings,
1006
+ );
1007
+ runnerDigest = verificationEnvironmentDigest(paths);
1008
+ } catch {
1009
+ return {
1010
+ ok: false,
1011
+ diagnostic: {
1012
+ kind: "verification-adapter",
1013
+ verificationId: capability.verificationId,
1014
+ code: "runner-missing",
1015
+ message: "Installed verification runner changed or is unavailable",
1016
+ },
1017
+ };
1018
+ }
885
1019
  return {
886
1020
  ok: true,
887
1021
  value: {
@@ -892,8 +1026,9 @@ export function bindCurrentVerificationCapability(
892
1026
  })),
893
1027
  inputSnapshot: snapshotFiles(
894
1028
  root,
895
- verificationInputPaths(validation.value),
1029
+ verificationBoundInputPaths(validation.value),
896
1030
  ),
1031
+ runnerSnapshot: { paths, digest: runnerDigest },
897
1032
  },
898
1033
  };
899
1034
  }
@@ -902,7 +1037,65 @@ export function isVerificationCapabilityCurrent(
902
1037
  root: string,
903
1038
  capability: CurrentVerificationCapability,
904
1039
  ): boolean {
905
- return isCurrent(root, capability.inputSnapshot);
1040
+ try {
1041
+ return (
1042
+ isCurrent(root, capability.inputSnapshot) &&
1043
+ verificationEnvironmentDigest(capability.runnerSnapshot.paths) ===
1044
+ capability.runnerSnapshot.digest
1045
+ );
1046
+ } catch {
1047
+ return false;
1048
+ }
1049
+ }
1050
+
1051
+ export function verificationRunnerFiles(
1052
+ dependencyOwner: string,
1053
+ verification: StructuredVerificationContract,
1054
+ bindings: readonly VerificationRunnerBinding[],
1055
+ ): string[] {
1056
+ const files = bindings.flatMap((binding) => [
1057
+ binding.executablePath,
1058
+ ...(binding.fixedArgs ?? []).filter((arg) => path.isAbsolute(arg)),
1059
+ ]);
1060
+ for (const file of [...new Set(files)]) {
1061
+ // Keep the launcher identity and explicitly include its resolved bytes:
1062
+ // the digest intentionally does not follow arbitrary dependency symlinks.
1063
+ const resolved = realpathSync(file);
1064
+ files.push(resolved);
1065
+ // System runners have no mountSource. Bind their installed package too,
1066
+ // including implementation modules and bundled dependencies beyond bin/.
1067
+ for (
1068
+ let directory = path.dirname(resolved);
1069
+ directory !== path.dirname(directory);
1070
+ directory = path.dirname(directory)
1071
+ ) {
1072
+ if (
1073
+ lstatSync(path.join(directory, "package.json"), {
1074
+ throwIfNoEntry: false,
1075
+ })?.isFile()
1076
+ ) {
1077
+ files.push(directory);
1078
+ break;
1079
+ }
1080
+ }
1081
+ }
1082
+ for (const step of verificationSteps(verification)) {
1083
+ if (
1084
+ !("runner" in step) ||
1085
+ step.runner.kind === "node" ||
1086
+ step.runner.kind === "package-script"
1087
+ )
1088
+ continue;
1089
+ const executable =
1090
+ step.kind === "vitest" ? "vitest" : step.runner.executable;
1091
+ const file = path.join(dependencyOwner, "node_modules/.bin", executable);
1092
+ try {
1093
+ files.push(file, realpathSync(file));
1094
+ } catch {
1095
+ /* Admission owns missing runner diagnostics. */
1096
+ }
1097
+ }
1098
+ return [...new Set(files)];
906
1099
  }
907
1100
 
908
1101
  export function validateVerificationAdapterCapability(
@@ -945,6 +1138,7 @@ function validateCapability(
945
1138
  dependencyOwner,
946
1139
  options.runnerEnvironment ?? {},
947
1140
  requireInputAvailability,
1141
+ options.executionWritePaths ?? [],
948
1142
  );
949
1143
  }
950
1144
 
@@ -954,6 +1148,7 @@ function validateAcceptedVerificationCapability(
954
1148
  dependencyOwner: string,
955
1149
  runnerEnvironment: VerificationRunnerEnvironment,
956
1150
  requireInputAvailability: boolean,
1151
+ executionWritePaths: readonly string[],
957
1152
  ): VerificationCapabilityResult {
958
1153
  if (requireInputAvailability) {
959
1154
  for (const input of verificationInputPaths(verification)) {
@@ -973,6 +1168,7 @@ function validateAcceptedVerificationCapability(
973
1168
  step,
974
1169
  dependencyOwner,
975
1170
  runnerEnvironment,
1171
+ executionWritePaths,
976
1172
  );
977
1173
  if (!capability.ok) return capability;
978
1174
  runnerBindings.push(...capability.runnerBindings);