@deksden-com/dd-flow-cli 0.9.0-beta.114 → 0.9.0-beta.115

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,11 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.115
4
+
5
+ ### Patch Changes
6
+
7
+ - Freeze the reviewed source check profile for MERGE after verifying that it only adds aliases to the integration baseline; keep existing check policy immutable and verify the post-apply profile against that frozen source.
8
+
3
9
  ## 0.9.0-beta.114
4
10
 
5
11
  ### Patch Changes
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.114",
4
- "cli_commit": "b2fd68b574186d2202266ad9856455cc59ce7ed9",
5
- "built_at": "2026-09-27T17:40:23.830Z",
3
+ "cli_version": "0.9.0-beta.115",
4
+ "cli_commit": "c9825bc3272340c5583cab59ccdaab47c9968b10",
5
+ "built_at": "2026-09-27T17:57:38.414Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.1",
8
8
  "commit": "678daa038287c948ada5b2d785a6dcc925c7b891",
@@ -3,6 +3,7 @@ import { isGitlessWorkspace, ownedWorkspaceHookPath, projectServicePathFilter, w
3
3
  import { spawn, spawnSync } from "node:child_process";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
+ import { isDeepStrictEqual } from "node:util";
6
7
  import { parse } from "shell-quote";
7
8
  import { assertRunMutationAllowed } from "./run-recovery.js";
8
9
  import { AppError } from "../shared/errors.js";
@@ -66,6 +67,32 @@ export function codeCheckProfileHash(workspaceRoot, checks = []) {
66
67
  };
67
68
  return crypto.createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
68
69
  }
70
+ /** The integration target starts at its baseline profile; the reviewed source
71
+ * may add aliases, but must not change existing check policy or bindings. */
72
+ export function mergeCheckProfileHash(sourceWorkspace, targetWorkspace, checks) {
73
+ const source = readCodeCheckProfile(sourceWorkspace).profile;
74
+ const target = readCodeCheckProfile(targetWorkspace).profile;
75
+ if (!source && !target)
76
+ return null;
77
+ if (!source || !target)
78
+ throw new AppError("check_profile_drift", "Source and integration check profiles differ", 1);
79
+ const retained = structuredClone(source);
80
+ for (const field of ["aliases", "ports_by_alias", "inputs_by_alias"]) {
81
+ const old = target[field];
82
+ if (!old) {
83
+ delete retained[field];
84
+ continue;
85
+ }
86
+ const current = source[field] ?? {};
87
+ retained[field] = Object.fromEntries(Object.keys(old).filter(key => key in current).map(key => [key, current[key]]));
88
+ }
89
+ const existingAliases = new Set(Object.keys(target.aliases));
90
+ const changedExistingMetadata = ["ports_by_alias", "inputs_by_alias"].some(field => Object.keys(source[field] ?? {}).some(alias => existingAliases.has(alias) && !(alias in (target[field] ?? {}))));
91
+ if (changedExistingMetadata || !isDeepStrictEqual(retained, target)) {
92
+ throw new AppError("check_profile_drift", "Source check profile changes existing integration policy", 1, { source_profile_hash: codeCheckProfileHash(sourceWorkspace, checks), target_profile_hash: codeCheckProfileHash(targetWorkspace, checks) });
93
+ }
94
+ return codeCheckProfileHash(sourceWorkspace, checks);
95
+ }
69
96
  export function validateCodeCheckCommands(workspaceRoot, commands) {
70
97
  const { profile, file } = readCodeCheckProfile(workspaceRoot);
71
98
  return commands.map((command) => {
@@ -17,7 +17,7 @@ import { runtimeCommand, checkRetryArgs, recoveryEvidenceInput } from "./runtime
17
17
  import { acceptStageReport, prepareStageReport, publishStageReport } from "./stage-report-renderer.js";
18
18
  import { bindStageCoordinatorWork, createChildWork, failWork, finishFanInWork, finishWork, refreshRunWorkProjection, startStageCoordinatorWork } from "./work-registry.js";
19
19
  import { addVnextCodeRepair } from "./vnext-code.js";
20
- import { checkReceiptEvidenceValid, checkReceipts, codeCheckProfileHash, effectiveCheckDeclarations, runCodeChecks, validateFailedCheckRetry, workspaceChangedPaths, workspaceFingerprint } from "./code-checks.js";
20
+ import { checkReceiptEvidenceValid, checkReceipts, effectiveCheckDeclarations, mergeCheckProfileHash, runCodeChecks, validateFailedCheckRetry, workspaceChangedPaths, workspaceFingerprint } from "./code-checks.js";
21
21
  import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
22
22
  import { ensureLaneWorkspace, prepareLaneWorkspace, heartbeatLaneLock, releaseLaneLock, waitAcquireLaneLock } from "./lanes.js";
23
23
  import { assertRunMutationAllowed } from "./run-recovery.js";
@@ -71,7 +71,8 @@ export function prepareVnextMergeRequest(context, input) {
71
71
  const freezeFile = path.join(freezeRoot, "source-freeze.json");
72
72
  const gateFile = path.join(freezeRoot, "merge-gate.json");
73
73
  fs.mkdirSync(freezeRoot, { recursive: true });
74
- freezeMergeGate(gateFile, { runId: run.id, protocols, checks: effective, acceptanceRefs, profileHash: codeCheckProfileHash(targetWorkspace, effective), now: context.now() });
74
+ const profileHash = mergeCheckProfileHash(run.workspace_root, targetWorkspace, effective);
75
+ freezeMergeGate(gateFile, { runId: run.id, protocols, checks: effective, acceptanceRefs, profileHash, now: context.now() });
75
76
  let sourceCommit;
76
77
  let sourcePaths;
77
78
  if (fs.existsSync(freezeFile)) {
@@ -109,7 +110,7 @@ export function prepareVnextMergeRequest(context, input) {
109
110
  return requestView(context, raced);
110
111
  const child = createChildWork(context, { parentWorkId: root.work_id, stage: "merge", slug: "merge", task: `Integrate frozen source ${sourceCommit} into ${targetBranch}, resolve material conflicts, and preserve accepted behavior.`, launchPolicy: route === "server" ? "fresh_agent_required" : "reuse_allowed", resultSchema: "dd-flow/merge-result@1", payload: { kind: "merge", checks: semantic, protocol_ids: protocols } });
111
112
  const requestId = nextMergeRequestId(context);
112
- context.db.run("INSERT INTO merge_requests (merge_request_id, project_id, run_id, executor_work_id, protocol_ids_json, source_workspace, source_branch, source_commit, target_workspace, target_branch, enqueue_target_head, execution_target_head, integration_commit, execution_route, status, dispatch_owner, dispatch_lease_token, dispatch_lease_expires_at, lock_acquired_at, checkpoint, profile_hash, replacement_of_merge_request_id, adapter_receipt_json, result_json, last_error_json, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, 'queued', NULL, NULL, NULL, NULL, 'queued', ?, ?, NULL, NULL, NULL, ?, ?, NULL)", [requestId, project.id, run.id, child.work_id, JSON.stringify(protocols), run.workspace_root, sourceBranch, sourceCommit, targetWorkspace, targetBranch, enqueueTarget, route, codeCheckProfileHash(targetWorkspace, effective), replacementOf(run), now, now]);
113
+ context.db.run("INSERT INTO merge_requests (merge_request_id, project_id, run_id, executor_work_id, protocol_ids_json, source_workspace, source_branch, source_commit, target_workspace, target_branch, enqueue_target_head, execution_target_head, integration_commit, execution_route, status, dispatch_owner, dispatch_lease_token, dispatch_lease_expires_at, lock_acquired_at, checkpoint, profile_hash, replacement_of_merge_request_id, adapter_receipt_json, result_json, last_error_json, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, 'queued', NULL, NULL, NULL, NULL, 'queued', ?, ?, NULL, NULL, NULL, ?, ?, NULL)", [requestId, project.id, run.id, child.work_id, JSON.stringify(protocols), run.workspace_root, sourceBranch, sourceCommit, targetWorkspace, targetBranch, enqueueTarget, route, profileHash, replacementOf(run), now, now]);
113
114
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "merge_request_queued", merge_request_id: requestId, work_id: child.work_id, route, source_commit: sourceCommit, target_branch: targetBranch });
114
115
  return requestView(context, requireRequest(context, requestId));
115
116
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.114",
3
+ "version": "0.9.0-beta.115",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {