@akagilnc/pi-workflow-roles 0.1.3873 → 0.1.3886

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,29 +1,23 @@
1
1
  import { Type } from "typebox";
2
- import { isFullGitObjectId } from "./git-object-id.js";
3
2
  import { exactUtf8 } from "./exact-utf8.js";
4
- import { sha256Hex } from "./sha256.js";
5
3
  import { openToolObjectFromUnion } from "./open-tool-schema.js";
6
4
  import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
7
- const oidPattern = "^(?:[0-9a-f]{40}|[0-9a-f]{64})$";
8
5
  const materialSchema = Type.Object({ bytesBase64: Type.String(), sha256: Type.String() }, { additionalProperties: false });
9
6
  const checkSchema = Type.Object({ name: Type.String({ minLength: 1 }), argv: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }) }, { additionalProperties: false });
10
7
  const mergerInputSchema = Type.Object({
11
- version: Type.Literal(1),
12
- attemptId: Type.String({ minLength: 1 }),
13
- targetObjectId: Type.String({ pattern: oidPattern }),
14
- sourceObjectId: Type.String({ pattern: oidPattern }),
8
+ attemptId: Type.String({ minLength: 1, description: "\u5BF9\u8D26 attempt \u8EAB\u4EFD\uFF08transport\uFF09" }),
9
+ targetObjectId: Type.String({ description: "\u6750\u6599\uFF1Atarget parent OID\uFF1B\u53EF\u7A7A" }),
10
+ sourceObjectId: Type.String({ description: "\u6750\u6599\uFF1Asource parent OID\uFF1B\u53EF\u7A7A" }),
15
11
  materials: Type.Object({ task: materialSchema, authority: materialSchema, targetIntent: materialSchema, sourceIntent: materialSchema }, { additionalProperties: false }),
16
- expectedConflictPaths: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
17
- resolutionScope: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
12
+ expectedConflictPaths: Type.Array(Type.String(), { description: "\u6750\u6599\uFF1A\u5F53\u524D\u672A\u5408\u5E76\u8DEF\u5F84\uFF1B\u53EF\u7A7A" }),
13
+ resolutionScope: Type.Array(Type.String(), { description: "\u6750\u6599\uFF1A\u89E3\u6790\u8303\u56F4\u63D0\u793A\uFF1B\u53EF\u7A7A" }),
18
14
  authorizedChecks: Type.Array(checkSchema)
19
15
  }, { additionalProperties: false });
20
16
  const mergerOutputVariants = Type.Union([
21
- Type.Object({ status: Type.Literal("completed", { description: "completed \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), attemptId: Type.String({ minLength: 1, description: "\u5DF2\u53D7\u7406\u5408\u5E76 attempt \u8EAB\u4EFD" }), report: Type.String({ minLength: 1, description: "\u5982\u5B9E\u7ED3\u679C\u62A5\u544A" }), mergeCommitId: Type.String({ pattern: oidPattern, description: "\u5DF2\u6838\u9A8C\u7684\u5B8C\u6210\u5408\u5E76 commit object ID" }) }, { additionalProperties: false }),
22
- Type.Object({ status: Type.Literal("escalate", { description: "escalate \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), attemptId: Type.String({ minLength: 1, description: "\u5DF2\u53D7\u7406\u5408\u5E76 attempt \u8EAB\u4EFD" }), diagnosis: Type.String({ minLength: 1, description: "\u5408\u5E76\u5B8C\u6210\u9700\u5347\u7EA7\u7684\u539F\u56E0" }), report: Type.String({ minLength: 1, description: "\u5982\u5B9E\u7ED3\u679C\u62A5\u544A" }) }, { additionalProperties: false })
17
+ Type.Object({ status: Type.Literal("completed", { description: "completed \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), attemptId: Type.String({ minLength: 1, description: "\u5DF2\u53D7\u7406\u5408\u5E76 attempt \u8EAB\u4EFD" }), report: Type.String({ minLength: 1, description: "\u5982\u5B9E\u7ED3\u679C\u62A5\u544A" }), mergeCommitId: Type.String({ description: "\u5B8C\u6210\u5408\u5E76 commit object ID" }) }, { additionalProperties: false }),
18
+ Type.Object({ status: Type.Literal("escalate", { description: "escalate \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), attemptId: Type.String({ minLength: 1, description: "\u5DF2\u53D7\u7406\u5408\u5E76 attempt \u8EAB\u4EFD" }), diagnosis: Type.String({ minLength: 1, description: "\u5408\u5E76\u65E0\u6CD5\u6216\u4E0D\u5E94\u7531\u672C\u5E2D\u5B8C\u6210\u7684\u539F\u56E0\uFF08\u542B\u65E0\u8FDB\u884C\u4E2D\u5408\u5E76\u3001\u65E0\u6D3B\u53EF\u5E72\u3001\u9700\u65B0\u7684\u4EA7\u54C1/\u6743\u529B\u51B3\u5B9A\uFF09" }), report: Type.String({ minLength: 1, description: "\u5982\u5B9E\u7ED3\u679C\u62A5\u544A" }) }, { additionalProperties: false })
23
19
  ]);
24
- const mergerOutputSchema = withInfrastructureFailureDeclaration(
25
- openToolObjectFromUnion(mergerOutputVariants)
26
- );
20
+ const mergerOutputSchema = withInfrastructureFailureDeclaration(openToolObjectFromUnion(mergerOutputVariants));
27
21
  const MERGER_OUTPUT_TOOL_NAME = "ak_merger_output";
28
22
  const MERGER_ACCEPTED_TEXT = "\u5408\u5E76\u56DE\u6267\u5DF2\u63A5\u53D7";
29
23
  const record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -37,19 +31,6 @@ class MergerInputContractError extends Error {
37
31
  function fail(message = "Merger input violates its exact contract") {
38
32
  throw new MergerInputContractError(message);
39
33
  }
40
- function canonicalPath(path) {
41
- return typeof path === "string" && path.length > 0 && !path.startsWith("/") && !path.includes("\0") && path.split("/").every((part) => part !== "" && part !== "." && part !== "..");
42
- }
43
- function validatePathSet(value, label) {
44
- if (!Array.isArray(value) || value.length === 0 || !value.every(canonicalPath)) fail(`Merger ${label} must be a non-empty canonical path set`);
45
- return value;
46
- }
47
- function validateMaterial(value, label) {
48
- if (!record(value) || typeof value.bytesBase64 !== "string" || typeof value.sha256 !== "string") fail(`Merger ${label} material is malformed`);
49
- const bytes = Buffer.from(value.bytesBase64, "base64");
50
- exactUtf8(bytes, `Merger ${label} material`);
51
- if (sha256Hex(bytes) !== value.sha256) fail(`Merger ${label} material digest mismatch`);
52
- }
53
34
  function deepFreeze(value) {
54
35
  if (value && typeof value === "object") {
55
36
  for (const child of Object.values(value)) deepFreeze(child);
@@ -57,24 +38,24 @@ function deepFreeze(value) {
57
38
  }
58
39
  return value;
59
40
  }
41
+ const asString = (v) => typeof v === "string" ? v : "";
42
+ const asStringArray = (v) => Array.isArray(v) ? v.filter((item) => typeof item === "string") : [];
43
+ function readMaterial(value, label) {
44
+ if (!record(value) || typeof value.bytesBase64 !== "string") fail(`Merger ${label} material is malformed`);
45
+ exactUtf8(Buffer.from(value.bytesBase64, "base64"), `Merger ${label} material`);
46
+ return { bytesBase64: value.bytesBase64, sha256: typeof value.sha256 === "string" ? value.sha256 : "" };
47
+ }
60
48
  function validateMergerInput(value) {
61
- if (!record(value) || blank(value.attemptId) || !isFullGitObjectId(value.targetObjectId) || !isFullGitObjectId(value.sourceObjectId) || value.targetObjectId.length !== value.sourceObjectId.length) fail("Merger input has invalid identity or object ID");
62
- if (!record(value.materials)) fail();
63
- for (const key of ["task", "authority", "targetIntent", "sourceIntent"]) validateMaterial(value.materials[key], key);
64
- const conflicts = validatePathSet(value.expectedConflictPaths, "expected conflict paths");
65
- const scope = validatePathSet(value.resolutionScope, "resolution scope");
66
- if (!conflicts.every((path) => scope.includes(path))) fail("Merger resolution scope must contain the complete conflict set");
67
- if (!Array.isArray(value.authorizedChecks)) fail("Merger authorized checks are malformed");
68
- for (const check of value.authorizedChecks) {
69
- if (!record(check) || !Array.isArray(check.argv) || check.argv.length === 0 || check.argv.some(blank)) fail("Merger authorized check is malformed");
70
- }
71
- return deepFreeze(structuredClone(value));
49
+ if (!record(value) || blank(value.attemptId)) fail("Merger input requires a non-empty attemptId for accounting");
50
+ if (!record(value.materials)) fail("Merger materials are missing");
51
+ const materials = { task: readMaterial(value.materials.task, "task"), authority: readMaterial(value.materials.authority, "authority"), targetIntent: readMaterial(value.materials.targetIntent, "targetIntent"), sourceIntent: readMaterial(value.materials.sourceIntent, "sourceIntent") };
52
+ const authorizedChecks = Array.isArray(value.authorizedChecks) ? value.authorizedChecks.filter((check) => record(check)).map((c) => ({ name: asString(c.name), argv: asStringArray(c.argv).filter((a) => a.trim().length > 0) })).filter((c) => c.name.trim().length > 0 && c.argv.length > 0) : [];
53
+ return deepFreeze(structuredClone({ attemptId: value.attemptId.trim(), targetObjectId: asString(value.targetObjectId), sourceObjectId: asString(value.sourceObjectId), materials, expectedConflictPaths: asStringArray(value.expectedConflictPaths), resolutionScope: asStringArray(value.resolutionScope), authorizedChecks }));
72
54
  }
73
- function validateMergerOutput(value, expectedAttemptId) {
74
- if (!record(value) || expectedAttemptId !== void 0 && value.attemptId !== expectedAttemptId) throw new Error("\u5408\u5E76\u56DE\u6267 attempt \u4E0D\u5339\u914D");
55
+ function validateMergerOutput(value) {
56
+ if (!record(value)) throw new Error("\u5408\u5E76\u56DE\u6267\u65E0\u5DF2\u8BC6\u522B\u7684\u6267\u884C\u5224\u522B");
75
57
  const status = typeof value.status === "string" ? value.status : void 0;
76
- if (status === "completed" && isFullGitObjectId(value.mergeCommitId)) return structuredClone(value);
77
- if (status === "escalate") return structuredClone(value);
58
+ if (status === "completed" || status === "escalate") return structuredClone(value);
78
59
  throw new Error("\u5408\u5E76\u56DE\u6267\u65E0\u5DF2\u8BC6\u522B\u7684\u6267\u884C\u5224\u522B");
79
60
  }
80
61
  export {
@@ -1,4 +1,7 @@
1
1
  import { execFile } from "node:child_process";
2
+ import { access, readFile } from "node:fs/promises";
3
+ import { constants as fsConstants } from "node:fs";
4
+ import { isAbsolute, resolve } from "node:path";
2
5
  import { promisify } from "node:util";
3
6
  import { exactUtf8 } from "./exact-utf8.js";
4
7
  import { isFullGitObjectId } from "./git-object-id.js";
@@ -13,11 +16,37 @@ function line(bytes, label) {
13
16
  throw new Error(`${label} is empty`);
14
17
  return value;
15
18
  }
16
- function nulPaths(bytes, label) {
17
- const raw = exactUtf8(bytes, label);
18
- if (raw.length > 0 && !raw.endsWith("\0"))
19
- throw new Error(`${label} is not NUL terminated`);
20
- return raw.split("\0").filter(Boolean).sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)));
19
+ async function pathExists(path) {
20
+ try {
21
+ await access(path, fsConstants.F_OK);
22
+ return true;
23
+ }
24
+ catch (error) {
25
+ if (error.code === "ENOENT")
26
+ return false;
27
+ throw error;
28
+ }
29
+ }
30
+ async function requireInsideWorkTree(cwd) {
31
+ const stdout = await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
32
+ if (exactUtf8(stdout, "Git worktree check").trim() !== "true")
33
+ throw new Error("Assigned path is not inside a Git work tree");
34
+ }
35
+ async function tryResolveCommit(cwd, rev) {
36
+ let stdout;
37
+ try {
38
+ const res = await execFileAsync("git", ["rev-parse", "--verify", "--quiet", `${rev}^{commit}`], { cwd, encoding: "buffer", maxBuffer: 16 * 1024 * 1024 });
39
+ stdout = new Uint8Array(res.stdout);
40
+ }
41
+ catch (error) {
42
+ if (error.code === 1)
43
+ return undefined;
44
+ throw error;
45
+ }
46
+ const oid = line(stdout, `Git ${rev}`);
47
+ if (!isFullGitObjectId(oid))
48
+ throw new Error(`Git ${rev} identity is unavailable or invalid`);
49
+ return oid;
21
50
  }
22
51
  async function unmerged(cwd) {
23
52
  const raw = exactUtf8(await git(cwd, ["ls-files", "-u", "-z"]), "Git unmerged index");
@@ -32,47 +61,45 @@ async function unmerged(cwd) {
32
61
  }
33
62
  return [...paths].sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)));
34
63
  }
64
+ async function resolveHeadMaterial(cwd) {
65
+ const commit = await tryResolveCommit(cwd, "HEAD");
66
+ if (commit !== undefined)
67
+ return commit;
68
+ try {
69
+ await execFileAsync("git", ["rev-parse", "--verify", "--quiet", "HEAD"], { cwd, encoding: "buffer", maxBuffer: 16 * 1024 * 1024 });
70
+ }
71
+ catch (error) {
72
+ if (error.code === 1)
73
+ return "";
74
+ throw error;
75
+ }
76
+ throw new Error("Git HEAD identity is unavailable or invalid");
77
+ }
35
78
  export function createProductionMergerGitState(repositoryRoot = process.cwd()) {
36
79
  return {
37
80
  async activeMerge() {
38
- const targetObjectId = line(await git(repositoryRoot, ["rev-parse", "--verify", "HEAD"]), "Git HEAD");
39
- let mergeHeadRaw;
40
- try {
41
- mergeHeadRaw = await git(repositoryRoot, ["rev-parse", "--verify", "MERGE_HEAD"]);
42
- }
43
- catch {
44
- throw new Error("Assigned repository does not have one ordinary in-progress merge");
81
+ await requireInsideWorkTree(repositoryRoot);
82
+ const targetObjectId = await resolveHeadMaterial(repositoryRoot);
83
+ const mergeHeadReported = line(await git(repositoryRoot, ["rev-parse", "--git-path", "MERGE_HEAD"]), "Git MERGE_HEAD path");
84
+ const mergeHeadPath = isAbsolute(mergeHeadReported) ? mergeHeadReported : resolve(repositoryRoot, mergeHeadReported);
85
+ let sourceObjectId = "";
86
+ if (await pathExists(mergeHeadPath)) {
87
+ const raw = exactUtf8(await readFile(mergeHeadPath), "Git MERGE_HEAD");
88
+ const mergeHeads = raw.trim().split(/\r?\n/).map((row) => row.trim()).filter(Boolean);
89
+ if (mergeHeads.length === 0)
90
+ throw new Error("Git MERGE_HEAD is empty");
91
+ if (mergeHeads.length !== 1)
92
+ throw new Error("Assigned repository does not have one ordinary in-progress merge");
93
+ const source = mergeHeads[0];
94
+ if (!isFullGitObjectId(source))
95
+ throw new Error("Git MERGE_HEAD identity is unavailable or invalid");
96
+ const resolved = await tryResolveCommit(repositoryRoot, source);
97
+ if (resolved === undefined || (targetObjectId !== "" && resolved.length !== targetObjectId.length)) {
98
+ throw new Error("Git MERGE_HEAD identity is unavailable or invalid");
99
+ }
100
+ sourceObjectId = resolved;
45
101
  }
46
- const mergeHeads = exactUtf8(mergeHeadRaw, "Git MERGE_HEAD").trim().split(/\r?\n/).filter(Boolean);
47
- if (mergeHeads.length !== 1 || !isFullGitObjectId(targetObjectId) || !isFullGitObjectId(mergeHeads[0]) || targetObjectId.length !== mergeHeads[0].length)
48
- throw new Error("Assigned repository does not have one ordinary in-progress merge");
49
- let automaticMergeTreeRaw;
50
- try {
51
- automaticMergeTreeRaw = await git(repositoryRoot, ["rev-parse", "--verify", "AUTO_MERGE^{tree}"]);
52
- }
53
- catch {
54
- throw new Error("Git automatic merge tree identity is unavailable or invalid");
55
- }
56
- const automaticMergeTreeId = line(automaticMergeTreeRaw, "Git automatic merge tree");
57
- if (!isFullGitObjectId(automaticMergeTreeId) || automaticMergeTreeId.length !== targetObjectId.length)
58
- throw new Error("Git automatic merge tree identity is unavailable or invalid");
59
- return { targetObjectId, sourceObjectId: mergeHeads[0], unmergedPaths: await unmerged(repositoryRoot), automaticMergeTreeId };
60
- },
61
- async completedMerge(mergeCommitId, automaticMergeTreeId) {
62
- if (!isFullGitObjectId(mergeCommitId) || !isFullGitObjectId(automaticMergeTreeId) || mergeCommitId.length !== automaticMergeTreeId.length)
63
- throw new Error("Merger completion object ID is invalid");
64
- const identity = exactUtf8(await git(repositoryRoot, ["show", "-s", "--format=%H%x00%P", mergeCommitId]), "Git merge commit").trimEnd().split("\0");
65
- if (identity.length !== 2 || identity[0] !== mergeCommitId)
66
- throw new Error("Git merge completion identity drifted");
67
- const parentObjectIds = identity[1].split(" ").filter(Boolean);
68
- const currentHead = line(await git(repositoryRoot, ["rev-parse", "--verify", "HEAD"]), "Git HEAD");
69
- const status = await git(repositoryRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
70
- const frozenTree = line(await git(repositoryRoot, ["rev-parse", "--verify", `${automaticMergeTreeId}^{tree}`]), "Git frozen automatic merge tree");
71
- if (frozenTree !== automaticMergeTreeId)
72
- throw new Error("Git frozen automatic merge tree identity drifted");
73
- const mergeTree = line(await git(repositoryRoot, ["rev-parse", "--verify", `${mergeCommitId}^{tree}`]), "Git completed merge tree");
74
- const resolutionChangedPaths = nulPaths(await git(repositoryRoot, ["diff-tree", "--no-commit-id", "--name-only", "-r", "-z", "--no-renames", automaticMergeTreeId, mergeTree]), "Git resolution path delta");
75
- return { mergeCommitId: currentHead, parentObjectIds, unmergedPaths: await unmerged(repositoryRoot), worktreeClean: status.byteLength === 0 && currentHead === mergeCommitId, resolutionChangedPaths };
102
+ return { targetObjectId, sourceObjectId, unmergedPaths: await unmerged(repositoryRoot) };
76
103
  },
77
104
  };
78
105
  }
@@ -1,16 +1,22 @@
1
- import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, mergerOutputSchema, validateMergerInput, validateMergerOutput } from "./merger-contracts.js";
1
+ import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, mergerOutputSchema, validateMergerInput, } from "./merger-contracts.js";
2
2
  import { exactUtf8 } from "./exact-utf8.js";
3
- import { isFullGitObjectId } from "./git-object-id.js";
4
3
  export { MERGER_OUTPUT_TOOL_NAME };
5
- export const MERGER_INPUT_FLAG = { name: "ak-merger-input", definition: { description: "Path to an immutable digest-bound Merger v1 input JSON file", type: "string" } };
6
- function same(a, b) { return a.length === b.length && a.every((value, i) => value === b[i]); }
7
- function materialText(input, key) { return exactUtf8(Buffer.from(input.materials[key].bytesBase64, "base64"), `Merger ${key}`); }
8
- export function createMergerRoleRuntime(pi, dependencies, host) {
4
+ export const MERGER_INPUT_FLAG = { name: "ak-merger-input", definition: { description: "Path to Merger assignment materials JSON", type: "string" } };
5
+ function materialText(input, key) {
6
+ return exactUtf8(Buffer.from(input.materials[key].bytesBase64, "base64"), `Merger ${key}`);
7
+ }
8
+ /**
9
+ * Merger (校书郎) role runtime.
10
+ * Code admits one submission and terminates the round (第 0 条).
11
+ * Git state / completion verification are not attendance or rejection gates (#827).
12
+ */
13
+ export function createMergerRoleRuntime(pi, dependencies) {
9
14
  let activation;
10
15
  let registered = false;
11
16
  let accepted = false;
12
17
  pi.registerFlag(MERGER_INPUT_FLAG.name, MERGER_INPUT_FLAG.definition);
13
- return { async activate() {
18
+ return {
19
+ async activate() {
14
20
  const path = pi.getFlag(MERGER_INPUT_FLAG.name);
15
21
  if (typeof path !== "string" || path.trim().length === 0)
16
22
  throw new Error("Merger requires --ak-merger-input");
@@ -18,49 +24,32 @@ export function createMergerRoleRuntime(pi, dependencies, host) {
18
24
  if (!soul)
19
25
  throw new Error("Merger soul is empty");
20
26
  const input = validateMergerInput(await dependencies.loadInput(path));
21
- const state = await dependencies.gitState.activeMerge();
22
- if (state.targetObjectId !== input.targetObjectId || state.sourceObjectId !== input.sourceObjectId || !same(state.unmergedPaths, input.expectedConflictPaths) || state.unmergedPaths.length === 0 || !isFullGitObjectId(state.automaticMergeTreeId) || state.automaticMergeTreeId.length !== input.targetObjectId.length)
23
- throw new Error("Merger activation rejected repository parent, merge, automatic-result, or complete conflict-set drift");
24
- activation = { soul, input, automaticMergeTreeId: state.automaticMergeTreeId };
27
+ activation = { soul, input };
25
28
  accepted = false;
26
29
  if (!registered) {
27
30
  registered = true;
28
- pi.registerTool({ name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate;基础设施及 Git 失败由 abort 通道承接。", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
29
- async execute(id, params, _signal, _update, ctx) {
31
+ pi.registerTool({
32
+ name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate。", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
33
+ async execute(_id, params, _signal, _update, _ctx) {
30
34
  if (!activation)
31
35
  throw new Error("校书郎未激活");
32
36
  if (accepted)
33
37
  throw new Error("合并回执已受理");
34
- let output;
35
- try {
36
- output = validateMergerOutput(params, activation.input.attemptId);
37
- }
38
- catch (error) {
39
- host.failInfrastructure(error, ctx, id);
40
- }
41
- if (output.status === "completed") {
42
- let state;
43
- try {
44
- state = await dependencies.gitState.completedMerge(output.mergeCommitId, activation.automaticMergeTreeId);
45
- }
46
- catch (error) {
47
- host.failInfrastructure(error, ctx, id);
48
- }
49
- const scope = new Set(activation.input.resolutionScope);
50
- if (state.mergeCommitId !== output.mergeCommitId || !same(state.parentObjectIds, [activation.input.targetObjectId, activation.input.sourceObjectId]) || state.unmergedPaths.length !== 0 || !state.worktreeClean || state.resolutionChangedPaths.some(path => !scope.has(path)))
51
- host.failInfrastructure(new Error("Merger completed-state verification failed"), ctx, id);
52
- }
53
38
  accepted = true;
54
- const acceptedDetails = output;
55
- return { content: [{ type: "text", text: MERGER_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true };
56
- } });
57
- pi.on("before_agent_start", (event) => { if (!activation)
58
- throw new Error("校书郎未激活"); const admitted = { attemptId: activation.input.attemptId, targetObjectId: activation.input.targetObjectId, sourceObjectId: activation.input.sourceObjectId, task: materialText(activation.input, "task"), authority: materialText(activation.input, "authority"), targetIntent: materialText(activation.input, "targetIntent"), sourceIntent: materialText(activation.input, "sourceIntent"), expectedConflictPaths: activation.input.expectedConflictPaths, resolutionScope: activation.input.resolutionScope, authorizedChecks: activation.input.authorizedChecks }; return { systemPrompt: `${event.systemPrompt}\n\n<merger_soul>\n${activation.soul}\n</merger_soul>\n\n<merger_assignment>\n${JSON.stringify(admitted)}\n</merger_assignment>` }; });
39
+ return { content: [{ type: "text", text: MERGER_ACCEPTED_TEXT }], details: params, terminate: true };
40
+ },
41
+ });
42
+ pi.on("before_agent_start", (event) => {
43
+ if (!activation)
44
+ throw new Error("校书郎未激活");
45
+ const admitted = { attemptId: activation.input.attemptId, targetObjectId: activation.input.targetObjectId, sourceObjectId: activation.input.sourceObjectId, task: materialText(activation.input, "task"), authority: materialText(activation.input, "authority"), targetIntent: materialText(activation.input, "targetIntent"), sourceIntent: materialText(activation.input, "sourceIntent"), expectedConflictPaths: activation.input.expectedConflictPaths, resolutionScope: activation.input.resolutionScope, authorizedChecks: activation.input.authorizedChecks };
46
+ return { systemPrompt: `${event.systemPrompt}\n\n<merger_soul>\n${activation.soul}\n</merger_soul>\n\n<merger_assignment>\n${JSON.stringify(admitted)}\n</merger_assignment>` };
47
+ });
59
48
  }
60
- // Host-neutral AK output only (ADR 0018 fail-closed); no host-builtin name hard-require.
61
- const all = pi.getAllTools().map(tool => tool.name);
62
- if (all.filter(item => item === MERGER_OUTPUT_TOOL_NAME).length !== 1) {
49
+ const all = pi.getAllTools().map((tool) => tool.name);
50
+ if (all.filter((item) => item === MERGER_OUTPUT_TOOL_NAME).length !== 1) {
63
51
  throw new Error(`Merger required tool collision or missing: ${MERGER_OUTPUT_TOOL_NAME}`);
64
52
  }
65
- } };
53
+ },
54
+ };
66
55
  }
@@ -299,16 +299,6 @@ function parseInstructionArgv(args, owner) {
299
299
  ...(sourceRun === undefined ? {} : { sourceRun }),
300
300
  };
301
301
  }
302
- /** Honest activation-class failure while deriving the active-merge envelope. */
303
- export class MergerEnvelopeDerivationError extends Error {
304
- code = "merger-envelope-derivation";
305
- /** Typed cause for #107 classifyPostAdmissionFailure (isTypedActivationError). */
306
- knownCause = "activation";
307
- constructor(message, options) {
308
- super(message, options);
309
- this.name = "MergerEnvelopeDerivationError";
310
- }
311
- }
312
302
  /** Reject missing/blank path values so empty overrides cannot silently degrade. */
313
303
  function requireOptionPath(flag, value) {
314
304
  if (value === undefined || value.trim() === "") {
@@ -1875,7 +1865,7 @@ export function parseMergerArgv(args) {
1875
1865
  token.startsWith("--expectedConflictPaths=") ||
1876
1866
  token === "--resolutionScope" ||
1877
1867
  token.startsWith("--resolutionScope=")) {
1878
- throw new CliUsageError("merger does not accept public packet fields; the adapter derives the active-merge envelope");
1868
+ throw new CliUsageError("merger does not accept public packet fields; the adapter reads Git merge materials");
1879
1869
  }
1880
1870
  if (token.startsWith("-") && token !== "-") {
1881
1871
  throw new CliUsageError(`unknown merger option: ${token}`);
@@ -1897,39 +1887,26 @@ function mergerMaterialFromUtf8(text) {
1897
1887
  });
1898
1888
  }
1899
1889
  /**
1900
- * Derive the mechanical Merger envelope from an already-active ordinary merge.
1901
- * Uses the production Git seam (HEAD, sole MERGE_HEAD, AUTO_MERGE, unmerged set).
1902
- * Failures are activation-class facts — not CLI semantic guesses.
1890
+ * Read merger materials from current Git state.
1891
+ * No in-progress merge / empty conflict set still yields materials (possibly empty);
1892
+ * code does not gate attendance on merge state (#827).
1903
1893
  */
1904
1894
  export async function deriveMergerEnvelopeFromActiveMerge(projectRoot, gitState = createProductionMergerGitState(projectRoot)) {
1905
- let state;
1906
- try {
1907
- state = await gitState.activeMerge();
1908
- }
1909
- catch (error) {
1910
- const message = error instanceof Error && error.message.trim() !== ""
1911
- ? error.message
1912
- : "Assigned repository does not have one ordinary in-progress merge";
1913
- throw new MergerEnvelopeDerivationError(message, { cause: error });
1914
- }
1915
- if (state.unmergedPaths.length === 0) {
1916
- throw new MergerEnvelopeDerivationError("Assigned repository does not have one ordinary in-progress merge with a complete conflict set");
1917
- }
1895
+ const state = await gitState.activeMerge();
1918
1896
  const expectedConflictPaths = Object.freeze([...state.unmergedPaths]);
1919
- // Scope is derived as the complete conflict set; the role may not broaden it.
1920
1897
  const resolutionScope = Object.freeze([...state.unmergedPaths]);
1921
1898
  return Object.freeze({
1922
1899
  targetObjectId: state.targetObjectId,
1923
1900
  sourceObjectId: state.sourceObjectId,
1924
- automaticMergeTreeId: state.automaticMergeTreeId,
1925
1901
  expectedConflictPaths,
1926
1902
  resolutionScope,
1927
1903
  });
1928
1904
  }
1929
1905
  /**
1930
1906
  * Admit a Merger Role run on the common Invocation request.
1931
- * Mechanical envelope (parents, AUTO_MERGE, conflicts, scope) is derived from
1932
- * the active merge — callers never supply public packet fields for those facts.
1907
+ * Git materials (parents, conflicts, scope) are read from the worktree and
1908
+ * handed to the role as assignment materials — including when empty (#827).
1909
+ * Callers never supply public packet fields for those facts.
1933
1910
  */
1934
1911
  export async function admitMergerInvocation(options) {
1935
1912
  if (options.project !== undefined) {
@@ -1940,8 +1917,6 @@ export async function admitMergerInvocation(options) {
1940
1917
  throw new CliUsageError("merger requires a nonblank task instruction");
1941
1918
  }
1942
1919
  const projectRoot = resolve(options.project ?? options.cwd);
1943
- // Derive mechanical envelope before placing a run identity so no-merge/drift
1944
- // fails honestly without orphan ledger rows or guessed packet fields.
1945
1920
  const derived = await deriveMergerEnvelopeFromActiveMerge(projectRoot, options.gitState ?? createProductionMergerGitState(projectRoot));
1946
1921
  const runId = (options.createRunId ?? uuidv7)();
1947
1922
  const { principal, sessionDirectory, sessionFile, runDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
@@ -1955,13 +1930,13 @@ export async function admitMergerInvocation(options) {
1955
1930
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
1956
1931
  const attachments = await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
1957
1932
  // Intent materials seed primary-source investigation; the method owns the work.
1958
- const targetIntent = mergerMaterialFromUtf8(`Investigate primary sources for target parent ${derived.targetObjectId}. Do not invent intent.`);
1959
- const sourceIntent = mergerMaterialFromUtf8(`Investigate primary sources for source parent ${derived.sourceObjectId}. Do not invent intent.`);
1933
+ const targetLabel = derived.targetObjectId === "" ? "(none observed)" : derived.targetObjectId;
1934
+ const sourceLabel = derived.sourceObjectId === "" ? "(none observed)" : derived.sourceObjectId;
1935
+ const targetIntent = mergerMaterialFromUtf8(`Investigate primary sources for target parent ${targetLabel}. Do not invent intent.`);
1936
+ const sourceIntent = mergerMaterialFromUtf8(`Investigate primary sources for source parent ${sourceLabel}. Do not invent intent.`);
1960
1937
  const taskMaterial = mergerMaterialFromUtf8(instruction);
1961
1938
  const authorityMaterial = mergerMaterialFromUtf8(instruction);
1962
- // Validate merger envelope before placing admitted identity.
1963
1939
  const mergerInput = validateMergerInput({
1964
- version: 1,
1965
1940
  attemptId: runId,
1966
1941
  targetObjectId: derived.targetObjectId,
1967
1942
  sourceObjectId: derived.sourceObjectId,
@@ -1973,7 +1948,6 @@ export async function admitMergerInvocation(options) {
1973
1948
  },
1974
1949
  expectedConflictPaths: [...derived.expectedConflictPaths],
1975
1950
  resolutionScope: [...derived.resolutionScope],
1976
- // Authorized checks remain available on the assignment; default none.
1977
1951
  authorizedChecks: [],
1978
1952
  });
1979
1953
  const mergerInputPath = join(runDirectory, "merger-input.json");
@@ -1991,7 +1965,6 @@ export async function admitMergerInvocation(options) {
1991
1965
  derived: {
1992
1966
  targetObjectId: derived.targetObjectId,
1993
1967
  sourceObjectId: derived.sourceObjectId,
1994
- automaticMergeTreeId: derived.automaticMergeTreeId,
1995
1968
  expectedConflictPaths: [...derived.expectedConflictPaths],
1996
1969
  resolutionScope: [...derived.resolutionScope],
1997
1970
  },