@williambeto/ai-workflow 2.8.0 → 2.8.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  ## [unreleased]
2
2
 
3
+ ## [2.8.1] - 2026-07-14
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - Route natural workspace mutations to Astra instead of stopping after a
8
+ specialist prints code
9
+ - Keep Phoenix limited to bounded remediation of observed findings
10
+ - Require execution-scoped repository changes and proportional validation for
11
+ successful workspace delivery
12
+ - Preserve explicit read-only, preview, already-satisfied, clarification, and
13
+ non-delivery outcomes without manufacturing changes
14
+ - Prevent natural phrases such as `next step` from being interpreted as a
15
+ Next.js stack request
16
+ - Propagate observed OpenCode commands to the evidence ledger and derive test
17
+ actions from the delivered diff
18
+ - Prevent duplicate finalization and require exactly one observed Astra
19
+ delegation in the OpenCode fallback path
20
+ - Route mixed inspection-and-change requests to Astra whenever the request has
21
+ write intent
22
+ - Allow read-only Git, `sed`, and `awk` inspection while continuing to block
23
+ mutations, and exclude ignored build artifacts from delivery deltas
24
+ - Keep validation proportional for documentation, simple code, backend scripts,
25
+ and React applications while retaining blocking build requirements for UI
26
+
27
+ ### 🧪 Testing
28
+
29
+ - Cover routing ownership, delivery outcomes, answer-only confinement, project
30
+ context compatibility, execution delta attribution, and generated OpenCode
31
+ permissions through reusable invariants
32
+ - Keep pre-existing dirty work from satisfying delivery or test-action evidence
33
+ - Validate the corrected workflow with a real GPT-5.6 Sol OpenCode delivery and
34
+ record GPT-5.3 Codex Spark medium as experimental after observed incomplete
35
+ delivery was correctly blocked
36
+
37
+ ### 📚 Documentation
38
+
39
+ - Align Atlas, quickstart, ownership, runtime-flow, evidence, and skill
40
+ governance documentation with the natural-request delivery contract
41
+ - Document the distinction between CLI-enforced gates and model-qualified
42
+ direct Atlas sessions
43
+
3
44
  ## [2.8.0] - 2026-07-14
4
45
 
5
46
  ### 🚀 Features
@@ -21,7 +21,7 @@ import {
21
21
  isTerminalFailure,
22
22
  readPackageFile,
23
23
  resolveWorkflowProfile
24
- } from "../chunk-LI76KI7C.js";
24
+ } from "../chunk-4FI5ODAM.js";
25
25
  import {
26
26
  EvidenceValidator,
27
27
  require_ajv,
@@ -30,9 +30,12 @@ import {
30
30
  import {
31
31
  EvidenceCollector,
32
32
  QualityGuard,
33
- ValidationPlanner
34
- } from "../chunk-Y4RLP6ZM.js";
35
- import "../chunk-XW747GIG.js";
33
+ ValidationPlanner,
34
+ parseDeliveryOutcomeClaim
35
+ } from "../chunk-GLYX3HEY.js";
36
+ import {
37
+ DeliveryDecisionEngine
38
+ } from "../chunk-H7GIKXFO.js";
36
39
  import {
37
40
  createManagedBackup,
38
41
  createManagedPathBackup
@@ -726,34 +729,34 @@ async function readJsonc(filePath) {
726
729
  import path3 from "path";
727
730
  var LEGACY_MARKER = "_aiWorkflowManaged";
728
731
  var AGENTS = {
729
- Atlas: { description: "Router and workflow coordinator for AI Workflow Kit" },
730
- Nexus: { description: "Discovery, requirement, scope, and specification owner" },
731
- Orion: { description: "Planning, PR sequencing, release, and deployment strategy owner" },
732
- Astra: { description: "Implementation owner for incremental, scoped changes" },
733
- Sage: { description: "Audit, validation, evidence, and quality gate owner" },
734
- Phoenix: { description: "Remediation and regression recovery owner" }
732
+ Atlas: { description: "Router and workflow coordinator for AI Workflow Kit", mode: "primary" },
733
+ Nexus: { description: "Discovery, requirement, scope, and specification owner", mode: "all" },
734
+ Orion: { description: "Planning, PR sequencing, release, and deployment strategy owner", mode: "all" },
735
+ Astra: { description: "Implementation owner for incremental, scoped workspace changes", mode: "all" },
736
+ Sage: { description: "Audit, validation, evidence, and quality gate owner", mode: "all" },
737
+ Phoenix: { description: "Bounded remediation and regression recovery owner", mode: "all" }
735
738
  };
736
739
  var SUBAGENTS = {
737
- "Architecture-Specialist": { description: "Select the simplest architecture that safely satisfies current requirements", skill: "architecture" },
738
- "Backend-Engineer": { description: "Support safe PHP/Node/Python backend changes for APIs and persistence", skill: "backend-development" },
739
- "Deployment-Specialist": { description: "Plan, review, or validate deployment, release, and production-readiness", skill: "deployment" },
740
- "Design-Specialist": { description: "Apply practical design principles to keep solutions simple and maintainable", skill: "design-principles" },
741
- "Docs-Engineer": { description: "Write or improve project documentation, READMEs, guides, and prompts", skill: "documentation" },
742
- "Frontend-Engineer": { description: "Implement or review frontend changes involving UI, state, routing, and accessibility", skill: "frontend-development" },
743
- "Frontend-Design-System-Specialist": { description: "Enforce CSS/HTML tokens, semantic elements, typography, and premium atmosphere constraints for frontend quality", skill: "frontend-design-system" },
744
- "Full-Stack-Engineer": { description: "Coordinate frontend and backend changes as one coherent, testable increment", skill: "full-stack-development" },
745
- "Token-Economist": { description: "Reduce context size while preserving information required for safe execution", skill: "optimize-tokens" },
746
- "PR-Manager": { description: "Plan, implement, review, or validate small pull requests while preserving scope", skill: "pr-workflow" },
747
- "Discovery-Analyst": { description: "Clarify ambiguous requests into actionable problem statements and success outcomes", skill: "product-discovery" },
748
- "Product-Planner": { description: "Turn clarified needs into scoped requirements with acceptance-ready outcomes", skill: "product-planning" },
749
- "Memory-Guardian": { description: "Manage discoverable project memory for durable decisions and recurring corrections", skill: "project-memory" },
750
- "Prompt-Engineer": { description: "Expert in creating and refining prompts for AI agents and workflows", skill: "prompt-engineer" },
751
- "QA-Engineer": { description: "Design, review, or improve tests, acceptance criteria, and validation evidence", skill: "qa-workflow" },
752
- "Refactoring-Specialist": { description: "Improve code/document structure safely while preserving behavior", skill: "refactoring" },
753
- "Release-Specialist": { description: "Prepare release readiness decisions with explicit gates and evidence", skill: "release-workflow" },
754
- "SDD-Specialist": { description: "Convert approved requirements into explicit, testable specification artifacts", skill: "spec-driven-development" },
755
- "Technical-Leader": { description: "Make technical decisions, review architecture, and identify trade-offs", skill: "technical-leadership" },
756
- "UI-UX-Engineer": { description: "Improve usability and interface clarity without introducing unnecessary complexity", skill: "ui-ux-design" }
740
+ "Architecture-Specialist": { description: "Capability only: advise on the simplest safe architecture for current requirements", skill: "architecture" },
741
+ "Backend-Engineer": { description: "Capability only: advise on PHP, Node, or Python APIs and persistence", skill: "backend-development" },
742
+ "Deployment-Specialist": { description: "Capability only: analyze deployment, release, and production readiness", skill: "deployment" },
743
+ "Design-Specialist": { description: "Capability only: advise on simple and maintainable design principles", skill: "design-principles" },
744
+ "Docs-Engineer": { description: "Capability only: advise on documentation, READMEs, guides, and prompts", skill: "documentation" },
745
+ "Frontend-Engineer": { description: "Capability only: advise on UI, state, routing, and accessibility", skill: "frontend-development" },
746
+ "Frontend-Design-System-Specialist": { description: "Capability only: review CSS and HTML tokens, semantics, typography, and visual quality", skill: "frontend-design-system" },
747
+ "Full-Stack-Engineer": { description: "Capability only: analyze frontend and backend integration as one testable increment", skill: "full-stack-development" },
748
+ "Token-Economist": { description: "Capability only: analyze context size while preserving safe execution information", skill: "optimize-tokens" },
749
+ "PR-Manager": { description: "Capability only: plan or review small pull requests while preserving scope", skill: "pr-workflow" },
750
+ "Discovery-Analyst": { description: "Capability only: clarify ambiguous requests into actionable outcomes", skill: "product-discovery" },
751
+ "Product-Planner": { description: "Capability only: shape clarified needs into scoped requirements", skill: "product-planning" },
752
+ "Memory-Guardian": { description: "Capability only: advise on durable project decisions and recurring corrections", skill: "project-memory" },
753
+ "Prompt-Engineer": { description: "Capability only: advise on prompts for agents and workflows", skill: "prompt-engineer" },
754
+ "QA-Engineer": { description: "Capability only: design or review tests, acceptance criteria, and validation evidence", skill: "qa-workflow" },
755
+ "Refactoring-Specialist": { description: "Capability only: review structure and behavior-preserving simplifications", skill: "refactoring" },
756
+ "Release-Specialist": { description: "Capability only: analyze release readiness with explicit gates and evidence", skill: "release-workflow" },
757
+ "SDD-Specialist": { description: "Capability only: shape approved requirements into testable specifications", skill: "spec-driven-development" },
758
+ "Technical-Leader": { description: "Capability only: analyze technical decisions, architecture, and trade-offs", skill: "technical-leadership" },
759
+ "UI-UX-Engineer": { description: "Capability only: review usability and interface clarity", skill: "ui-ux-design" }
757
760
  };
758
761
  var COMMANDS = {
759
762
  atlas: { description: "Decide safest next step and delegate to the appropriate workflow or agent", agent: "Atlas" },
@@ -773,18 +776,36 @@ var COMMANDS = {
773
776
  };
774
777
  function buildManagedConfig() {
775
778
  const agent = {};
779
+ const coreTaskPermissions = Object.fromEntries([
780
+ ["*", "deny"],
781
+ ...Object.keys(AGENTS).filter((name) => name !== "Atlas").map((name) => [name, "allow"])
782
+ ]);
783
+ const capabilityTaskPermissions = Object.fromEntries([
784
+ ["*", "deny"],
785
+ ...Object.keys(SUBAGENTS).map((name) => [name, "allow"])
786
+ ]);
776
787
  for (const [name, def] of Object.entries(AGENTS)) {
777
788
  agent[name] = {
778
- mode: "primary",
789
+ mode: def.mode,
779
790
  description: def.description,
780
- prompt: `{file:./.ai-workflow/opencode/agents/${name.toLowerCase()}.md}`
791
+ prompt: `{file:./.ai-workflow/opencode/agents/${name.toLowerCase()}.md}`,
792
+ permission: {
793
+ ...name === "Atlas" ? { edit: "deny", bash: "ask" } : {},
794
+ ...name === "Astra" ? { edit: "allow" } : {},
795
+ task: name === "Atlas" ? coreTaskPermissions : capabilityTaskPermissions
796
+ }
781
797
  };
782
798
  }
783
799
  for (const [name, def] of Object.entries(SUBAGENTS)) {
784
800
  agent[name] = {
785
801
  mode: "subagent",
786
802
  description: def.description,
787
- prompt: `{file:./.ai-workflow/opencode/skills/${def.skill}/SKILL.md}`
803
+ prompt: `{file:./.ai-workflow/opencode/skills/${def.skill}/SKILL.md}`,
804
+ permission: {
805
+ edit: "deny",
806
+ bash: "ask",
807
+ task: { "*": "deny" }
808
+ }
788
809
  };
789
810
  }
790
811
  const command = {};
@@ -2079,7 +2100,7 @@ function buildDeliverySummary({ evidence }) {
2079
2100
  return {
2080
2101
  Status: evidence?.status || "BLOCKED",
2081
2102
  Branch: evidence?.branch || "unknown",
2082
- Changes: (evidence?.changedFiles || []).join(", ") || "No changed files recorded",
2103
+ Changes: (evidence?.deliveryChangedFiles || evidence?.changedFiles || []).join(", ") || "No changed files recorded",
2083
2104
  Validation: commands,
2084
2105
  "Known limitations": (evidence?.limitations || []).join("; ") || "None recorded"
2085
2106
  };
@@ -2114,12 +2135,40 @@ async function runCollectEvidence({
2114
2135
  explicitApprovals = [],
2115
2136
  visualDist = null,
2116
2137
  port = null,
2117
- workflowEvidence = null
2138
+ workflowEvidence = null,
2139
+ claimedOutcome = null,
2140
+ deliveryChangedFiles = null,
2141
+ deliveryAddedFiles = null,
2142
+ deliveryDeletedFiles = null
2118
2143
  }) {
2144
+ if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
2145
+ console.log("[FINALIZATION DEFERRED] Parent ai-workflow execute process owns canonical finalization.");
2146
+ return { status: "DEFERRED_TO_ORCHESTRATOR", internalStatus: "DEFERRED", commands: [] };
2147
+ }
2119
2148
  const policies = resolvePoliciesFromLegacyMode(mode, evidencePolicy);
2120
- const qualityGuard = new QualityGuard({ cwd, taskSlug, evidencePolicy: policies.evidencePolicy, riskLevel: policies.riskLevel });
2149
+ const workspaceGuard = new QualityGuard({ cwd, taskSlug, evidencePolicy: policies.evidencePolicy, riskLevel: policies.riskLevel });
2150
+ const observedDeliveryFiles = deliveryChangedFiles ?? workspaceGuard.getChangedFiles();
2151
+ const qualityGuard = new QualityGuard({
2152
+ cwd,
2153
+ taskSlug,
2154
+ evidencePolicy: policies.evidencePolicy,
2155
+ riskLevel: policies.riskLevel,
2156
+ scopedChangedFiles: observedDeliveryFiles
2157
+ });
2121
2158
  const planner = new ValidationPlanner({ cwd, qualityGuard });
2122
- const tasks = await planner.plan(profile);
2159
+ const tasks = await planner.plan(profile, {
2160
+ requireObservedValidation: claimedOutcome === "ALREADY_SATISFIED" && observedDeliveryFiles.length === 0
2161
+ });
2162
+ const docs = observedDeliveryFiles.filter((file) => /(^|\/)(docs?|readme|changelog)|\.md$/i.test(file));
2163
+ if (tasks.length === 0 && docs.length > 0 && docs.length === observedDeliveryFiles.length) {
2164
+ tasks.push({
2165
+ name: "docs-integrity",
2166
+ command: ["aiwk", "internal", "docs-integrity"],
2167
+ kind: "lint",
2168
+ internal: "document-integrity",
2169
+ files: docs
2170
+ });
2171
+ }
2123
2172
  const persist = !dryRun && policies.evidencePolicy === "required";
2124
2173
  const collector = new EvidenceCollector({
2125
2174
  cwd,
@@ -2132,7 +2181,12 @@ async function runCollectEvidence({
2132
2181
  explicitApprovals,
2133
2182
  visualDist,
2134
2183
  port: port || 8080,
2135
- workflowEvidence
2184
+ workflowEvidence,
2185
+ claimedOutcome,
2186
+ deliveryChangedFiles: observedDeliveryFiles,
2187
+ deliveryAddedFiles,
2188
+ deliveryDeletedFiles,
2189
+ riskLevel: policies.riskLevel
2136
2190
  });
2137
2191
  const evidence = await collector.collect(tasks, { writeArtifact: persist });
2138
2192
  const summary = buildDeliverySummary({ evidence });
@@ -2592,6 +2646,9 @@ async function runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPa
2592
2646
  };
2593
2647
  }
2594
2648
  async function runMasterOrchestrator({ cwd, specPath, approvalPath, override, remediationExecutor = null }) {
2649
+ if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
2650
+ throw new Error("[NESTED EXECUTION BLOCKED] The parent execute process owns orchestration and finalization.");
2651
+ }
2595
2652
  console.log("\n[AI WORKFLOW] Starting evidence-backed orchestration...\n");
2596
2653
  const branchGate = new BranchGate({ memoryDir: path14.join(cwd, ".ai-workflow"), cwd });
2597
2654
  const taskSlug = specPath ? normalizeTaskSlugFromSpec(specPath) : "implementation";
@@ -2801,6 +2858,19 @@ Please inspect the code, identify the root cause of these failures (e.g. failing
2801
2858
  import fs14 from "fs/promises";
2802
2859
  import path16 from "path";
2803
2860
  import { execSync as execSync4 } from "child_process";
2861
+ var INTEGRITY_METADATA_CHANGES = /* @__PURE__ */ new Set(["stagedDiff", "unstagedDiff", "untrackedFiles"]);
2862
+ function extractDeliveryChangedFiles(integrity) {
2863
+ const files = /* @__PURE__ */ new Set();
2864
+ for (const file of integrity.changes.added) files.add(file);
2865
+ for (const file of integrity.changes.deleted) files.add(file);
2866
+ for (const change of integrity.changes.modified) {
2867
+ if (change.startsWith("branch:") || change.startsWith("head:")) continue;
2868
+ if (INTEGRITY_METADATA_CHANGES.has(change)) continue;
2869
+ if (change.startsWith("indexTree:")) continue;
2870
+ files.add(change);
2871
+ }
2872
+ return [...files].sort();
2873
+ }
2804
2874
  async function captureReadOnlyState(cwd, fastTrack = false) {
2805
2875
  const finalizer = new Finalizer({ cwd, fastTrack });
2806
2876
  const snapshot = await finalizer.snapshotManager.capture();
@@ -2888,6 +2958,13 @@ async function classifyAndPlanExecution(naturalRequest, cwd, taskSlug, delegatio
2888
2958
  const plan = planner.plan(classification, taskSlug);
2889
2959
  console.log(`[PLANNED] Owner: ${plan.owner}, Remediation limit: ${plan.remediationLimit}`);
2890
2960
  stateMachine.transitionTo("PLANNED");
2961
+ if (classification.requestUnderstanding.deliveryMode === "workspace") {
2962
+ const deliveryDecision = await new DeliveryDecisionEngine({ cwd }).determineDecision({ userRequest: naturalRequest });
2963
+ if (deliveryDecision.decision.startsWith("BLOCK_")) {
2964
+ stateMachine.transitionTo("BLOCKED");
2965
+ throw new Error(`[CONTEXT BLOCKED] NEEDS_CLARIFICATION: ${deliveryDecision.reason || deliveryDecision.decision}`);
2966
+ }
2967
+ }
2891
2968
  return { plan, classification };
2892
2969
  }
2893
2970
  function runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate) {
@@ -3026,7 +3103,21 @@ async function runImplementation(plan, classification, delegationController, sta
3026
3103
  if (plan.specPath) {
3027
3104
  promptMsg = `Please review and fill in the specification file at: ${plan.specPath}. Make sure to change the Status field to 'APPROVED' in the Metadata section, and then implement the behavior described.`;
3028
3105
  }
3029
- const runResult = await delegationController.implement(promptMsg, plan.owner, { readOnly: !plan.branchNeeded, fastTrack });
3106
+ if (plan.branchNeeded) {
3107
+ promptMsg = [
3108
+ promptMsg,
3109
+ "",
3110
+ "AIWK delivery contract:",
3111
+ "- Work in the active repository; printed code is not a substitute for workspace delivery.",
3112
+ "- Keep the diff scoped and run proportional validation.",
3113
+ "- The parent already satisfied the branch gate. Do not repeat branch/status checks unless a new safety concern makes them necessary.",
3114
+ "- Documentation, screenshots, Sage review, Phoenix remediation, and persisted evidence are conditional; do not create them unless the request, risk policy, or an observed finding requires them.",
3115
+ "- Do not run collect-evidence, create a handoff, or finalize this workflow; the parent execute process is the sole finalization owner.",
3116
+ "- End with exactly one machine-readable line: AIWK_DELIVERY_OUTCOME: CHANGED or AIWK_DELIVERY_OUTCOME: ALREADY_SATISFIED.",
3117
+ "- Use ALREADY_SATISFIED only when inspection and relevant validation prove that no repository change is needed."
3118
+ ].join("\n");
3119
+ }
3120
+ const runResult = await delegationController.implement(promptMsg, plan.owner, { readOnly: !plan.branchNeeded, fastTrack, orchestratedChild: true });
3030
3121
  if (!runResult.success) {
3031
3122
  console.error(`[EXECUTE] OpenCode adapter execution reported failure: ${runResult.error || "Unknown error"}`);
3032
3123
  stateMachine.transitionTo("BLOCKED");
@@ -3047,9 +3138,13 @@ async function runReadOnlyConfinementCheck(readOnlyStateBefore, cwd, stateMachin
3047
3138
  ${violations.join("\n")}`);
3048
3139
  }
3049
3140
  }
3050
- async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, delegationController, stateMachine) {
3141
+ async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, claimedOutcome, deliveryBaseline, deliveryFinalizer, delegationController, stateMachine) {
3051
3142
  stateMachine.transitionTo("VALIDATING");
3052
3143
  const validateWorkflow = async () => {
3144
+ const deliveryIntegrity = await deliveryFinalizer.verifyIntegrity(deliveryBaseline);
3145
+ const deliveryChangedFiles = extractDeliveryChangedFiles(deliveryIntegrity);
3146
+ const deliveryAddedFiles = deliveryIntegrity.changes.added.filter((file) => deliveryChangedFiles.includes(file));
3147
+ const deliveryDeletedFiles = deliveryIntegrity.changes.deleted.filter((file) => deliveryChangedFiles.includes(file));
3053
3148
  const evidence = await runCollectEvidence({
3054
3149
  cwd,
3055
3150
  exitOnError: false,
@@ -3058,7 +3153,11 @@ async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, de
3058
3153
  profile: plan.profile,
3059
3154
  branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED",
3060
3155
  userRequest: naturalRequest,
3061
- explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : []
3156
+ explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : [],
3157
+ claimedOutcome,
3158
+ deliveryChangedFiles,
3159
+ deliveryAddedFiles,
3160
+ deliveryDeletedFiles
3062
3161
  });
3063
3162
  const quality = await new QualityGuard({
3064
3163
  cwd,
@@ -3161,7 +3260,7 @@ async function runFidelityValidation(naturalRequest, result, finalizer) {
3161
3260
  userRequest: naturalRequest,
3162
3261
  projectContext: result.evidence?.deliveryDecision?.projectContext || null,
3163
3262
  deliveryDecision: result.evidence?.deliveryDecision || null,
3164
- changedFiles: result.evidence?.changedFiles || [],
3263
+ changedFiles: result.evidence?.deliveryChangedFiles || result.evidence?.changedFiles || [],
3165
3264
  evidence: result.evidence || null,
3166
3265
  explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : []
3167
3266
  });
@@ -3178,7 +3277,7 @@ function computeFinalState(finalStatus) {
3178
3277
  if (finalStatus === "PASS_WITH_NOTES") return "COMPLETED_WITH_NOTES";
3179
3278
  return "BLOCKED";
3180
3279
  }
3181
- async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack) {
3280
+ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack, deliveryOutcome) {
3182
3281
  await runReadOnlyConfinementCheck(readOnlyStateBefore, cwd, stateMachine, fastTrack);
3183
3282
  stateMachine.transitionTo("IMPLEMENTED");
3184
3283
  stateMachine.transitionTo("VALIDATING");
@@ -3192,7 +3291,11 @@ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, f
3192
3291
  overallStatus: "PASS",
3193
3292
  evidence: {
3194
3293
  internalStatus: "PASS",
3195
- commandsRun: []
3294
+ commandsRun: [],
3295
+ deliveryMode: "answer-only",
3296
+ deliveryOutcome,
3297
+ testAction: "none",
3298
+ testReason: "Read-only work does not change executable behavior."
3196
3299
  },
3197
3300
  quality: {
3198
3301
  overallStatus: "PASS"
@@ -3201,6 +3304,9 @@ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, f
3201
3304
  };
3202
3305
  }
3203
3306
  async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
3307
+ if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
3308
+ throw new Error("[NESTED EXECUTION BLOCKED] The parent execute process owns orchestration and finalization.");
3309
+ }
3204
3310
  if (!naturalRequest || !naturalRequest.trim()) {
3205
3311
  throw new Error("Missing request. Please provide a natural request string via positional arguments or --request flag.");
3206
3312
  }
@@ -3211,6 +3317,7 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
3211
3317
  const ledger = new EvidenceLedger({ cwd, workflowId: taskSlug });
3212
3318
  const delegationController = new DelegationController({ cwd, ledger, adapter: new OpenCodeAdapter({ cwd }) });
3213
3319
  let plan = null;
3320
+ let branchAuthorized = false;
3214
3321
  try {
3215
3322
  const stateMachine = new WorkflowStateMachine();
3216
3323
  const { plan: planObj, classification } = await classifyAndPlanExecution(naturalRequest, cwd, taskSlug, delegationController, stateMachine);
@@ -3218,6 +3325,7 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
3218
3325
  const fastTrack = plan.riskLevel === "low" && (!plan.branchNeeded || plan.evidencePolicy === "optional");
3219
3326
  const branchGate = new BranchGate({ memoryDir: path16.join(cwd, ".ai-workflow"), cwd });
3220
3327
  const gateResult = runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate);
3328
+ branchAuthorized = true;
3221
3329
  const checkBranchSafety = () => {
3222
3330
  if (!planObj.branchNeeded) return;
3223
3331
  const currentBranch = branchGate.getCurrentBranch();
@@ -3231,18 +3339,38 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
3231
3339
  }
3232
3340
  await createSpecIfRequired(plan, classification, taskSlug, cwd);
3233
3341
  let readOnlyStateBefore = null;
3342
+ let deliveryBaseline = null;
3343
+ const deliveryFinalizer = new Finalizer({ cwd, fastTrack: false });
3234
3344
  if (!plan.branchNeeded) {
3235
3345
  readOnlyStateBefore = await captureReadOnlyState(cwd, fastTrack);
3346
+ } else {
3347
+ deliveryBaseline = await deliveryFinalizer.snapshotManager.capture();
3236
3348
  }
3237
- await runImplementation(plan, classification, delegationController, stateMachine, fastTrack);
3349
+ const implementationResult = await runImplementation(plan, classification, delegationController, stateMachine, fastTrack);
3350
+ const claimedOutcome = parseDeliveryOutcomeClaim(implementationResult.output || "");
3238
3351
  if (!plan.branchNeeded && readOnlyStateBefore) {
3239
- return await executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack);
3352
+ const readOnlyOutcome = classification.requestUnderstanding.mutationIntent === "readonly" ? "READ_ONLY_RESULT" : "PREVIEW_ONLY";
3353
+ return await executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack, readOnlyOutcome);
3354
+ }
3355
+ if (!deliveryBaseline) {
3356
+ throw new Error("[WORKFLOW BLOCKED] Missing delivery baseline for a write workflow.");
3240
3357
  }
3241
3358
  stateMachine.transitionTo("IMPLEMENTED");
3242
3359
  checkBranchSafety();
3243
3360
  const finalizer = new Finalizer({ cwd, fastTrack });
3244
3361
  const snapshotRef = { current: await finalizer.snapshotManager.capture() };
3245
- const { result: valResult, validateWorkflow } = await runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, delegationController, stateMachine);
3362
+ const { result: valResult, validateWorkflow } = await runValidation(
3363
+ plan,
3364
+ taskSlug,
3365
+ cwd,
3366
+ gateResult,
3367
+ naturalRequest,
3368
+ claimedOutcome,
3369
+ deliveryBaseline,
3370
+ deliveryFinalizer,
3371
+ delegationController,
3372
+ stateMachine
3373
+ );
3246
3374
  let result = valResult;
3247
3375
  result = await runBoundedRemediation(
3248
3376
  plan,
@@ -3292,10 +3420,26 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
3292
3420
  }
3293
3421
  const finalState = computeFinalState(finalStatus);
3294
3422
  stateMachine.transitionTo(finalState);
3423
+ ledger.logEvent({
3424
+ actor: "Atlas",
3425
+ actorType: "control-plane",
3426
+ observed: true,
3427
+ eventType: "finalization_started",
3428
+ provenance: "execute",
3429
+ data: { owner: "execute", status: finalState }
3430
+ });
3295
3431
  await generateFinalHandoff(plan, taskSlug, cwd, finalState, result.evidence);
3432
+ ledger.logEvent({
3433
+ actor: "Atlas",
3434
+ actorType: "control-plane",
3435
+ observed: true,
3436
+ eventType: "finalization_completed",
3437
+ provenance: "execute",
3438
+ data: { owner: "execute", status: finalState }
3439
+ });
3296
3440
  return { ...result, overallStatus: finalStatus, stateHistory: stateMachine.getHistory() };
3297
3441
  } finally {
3298
- if (plan && !plan.branchNeeded) {
3442
+ if (!branchAuthorized || plan && !plan.branchNeeded) {
3299
3443
  console.log(`
3300
3444
  [READ-ONLY LEDGER] (In-Memory/Stdout)
3301
3445
  ${JSON.stringify(ledger.getEvents(), null, 2)}
@@ -3499,7 +3643,7 @@ var commandMap = {
3499
3643
  });
3500
3644
  },
3501
3645
  validate: async (_, flags) => {
3502
- const { runValidate } = await import("../validate-IEHLAVZ6.js");
3646
+ const { runValidate } = await import("../validate-DDKN6ORS.js");
3503
3647
  await runValidate({
3504
3648
  cwd: process.cwd(),
3505
3649
  taskSlug: flags.taskSlug,