@astrosheep/keiyaku 1.0.0 → 1.0.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.
Files changed (123) hide show
  1. package/README.md +73 -56
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms_v2.js +103 -0
  4. package/build/agents/harness/activity-values.js +54 -0
  5. package/build/agents/harness/event-channel.js +55 -20
  6. package/build/agents/harness/events.js +47 -1
  7. package/build/agents/harness/execution-handle.js +292 -61
  8. package/build/agents/harness/index.js +103 -121
  9. package/build/agents/harness/pump.js +36 -47
  10. package/build/agents/harness/runtime.js +3 -0
  11. package/build/agents/launch-snapshot_v2.js +348 -0
  12. package/build/agents/opencode-sdk.js +42 -169
  13. package/build/agents/providers/claude-agent-sdk.js +240 -79
  14. package/build/agents/providers/codex-app-server.js +280 -72
  15. package/build/agents/providers/codex-sdk.js +159 -41
  16. package/build/agents/providers/event-type-tail.js +16 -0
  17. package/build/agents/providers/opencode-sdk.js +165 -59
  18. package/build/agents/providers/pi.js +254 -42
  19. package/build/agents/selector_v2.js +122 -0
  20. package/build/cli/commands/akuma.js +120 -42
  21. package/build/cli/completion.js +6 -7
  22. package/build/cli/flags.js +36 -10
  23. package/build/cli/help.js +42 -26
  24. package/build/cli/index.js +225 -93
  25. package/build/cli/parse.js +194 -75
  26. package/build/cli/projection-address.js +11 -0
  27. package/build/cli/render/address.js +88 -0
  28. package/build/cli/render/arc.js +10 -10
  29. package/build/cli/render/call.js +59 -0
  30. package/build/cli/render/compact-text.js +3 -0
  31. package/build/cli/render/errors.js +4 -2
  32. package/build/cli/render/format.js +6 -0
  33. package/build/cli/render/line-width.js +70 -0
  34. package/build/cli/render/misc.js +7 -4
  35. package/build/cli/render/petition.js +4 -3
  36. package/build/cli/render/projection-activity.js +201 -0
  37. package/build/cli/render/shared.js +46 -12
  38. package/build/cli/render/status.js +59 -10
  39. package/build/cli/render/tool-ledger-rollup.js +74 -0
  40. package/build/cli/render/tool-presentation.js +97 -0
  41. package/build/cli/render/wait.js +229 -0
  42. package/build/cli/skills-install.js +42 -24
  43. package/build/cli/subagent-guard.js +1 -1
  44. package/build/config/akuma-loader_v2.js +222 -0
  45. package/build/config/env-keys.js +0 -4
  46. package/build/config/env.js +0 -6
  47. package/build/config/settings.js +325 -12
  48. package/build/core/addressing.js +224 -0
  49. package/build/core/amend.js +30 -47
  50. package/build/core/arc.js +68 -60
  51. package/build/core/bind.js +105 -25
  52. package/build/core/call-persist.js +69 -0
  53. package/build/core/call.js +658 -0
  54. package/build/core/claim.js +72 -44
  55. package/build/core/command-io.js +19 -19
  56. package/build/core/context.js +11 -36
  57. package/build/core/draft.js +2 -3
  58. package/build/core/entry.js +101 -6
  59. package/build/core/execution-coordinate.js +62 -0
  60. package/build/core/execution-pact.js +141 -0
  61. package/build/core/forfeit.js +2 -3
  62. package/build/core/hints.js +10 -10
  63. package/build/core/ids.js +6 -0
  64. package/build/core/ledger.js +16 -1
  65. package/build/core/petition-claim-gates.js +14 -12
  66. package/build/core/petition-claim.js +1 -0
  67. package/build/core/petition-run.js +6 -2
  68. package/build/core/petition.js +63 -21
  69. package/build/core/places.js +106 -13
  70. package/build/core/projection/generation.js +412 -0
  71. package/build/core/projection/heart.js +316 -0
  72. package/build/core/projection/identity.js +81 -0
  73. package/build/core/projection/leash.js +87 -0
  74. package/build/core/projection/mint.js +154 -0
  75. package/build/core/projection-activity.js +219 -0
  76. package/build/core/projection-coordinate.js +35 -0
  77. package/build/core/projection-core.js +69 -463
  78. package/build/core/projection-generation-continuation.js +31 -0
  79. package/build/core/projection-generation-execution.js +148 -0
  80. package/build/core/projection-generation-identity.js +17 -0
  81. package/build/core/projection-generation-launcher.js +90 -0
  82. package/build/core/projection-generation-process.js +43 -0
  83. package/build/core/projection-generation-runner.js +144 -0
  84. package/build/core/projection-generation-runtime.js +15 -0
  85. package/build/core/projection-generation-store.js +707 -0
  86. package/build/core/projection-identity.js +11 -0
  87. package/build/core/projection-life-observer.js +87 -0
  88. package/build/core/projection-life-protocol.js +95 -0
  89. package/build/core/projection-mint.js +96 -24
  90. package/build/core/projection-runner-lock.js +230 -0
  91. package/build/core/projection-status.js +207 -107
  92. package/build/core/projection-tells.js +401 -0
  93. package/build/core/projection-wait.js +155 -0
  94. package/build/core/projection-wake.js +109 -231
  95. package/build/core/queue_v2.js +342 -0
  96. package/build/core/registry.js +6 -3
  97. package/build/core/render.js +63 -1
  98. package/build/core/renew.js +80 -53
  99. package/build/core/scope.js +186 -137
  100. package/build/core/seal.js +16 -19
  101. package/build/core/status.js +88 -25
  102. package/build/core/stored-agent-event.js +101 -0
  103. package/build/core/transcripts.js +214 -228
  104. package/build/core/verdict.js +27 -15
  105. package/build/core/worktree-bootstrap.js +187 -0
  106. package/build/core/worktree-path.js +72 -18
  107. package/build/flow-error.js +4 -6
  108. package/build/generated/version.js +1 -1
  109. package/build/git/branches.js +6 -1
  110. package/build/index.js +8 -5
  111. package/build/keiyaku.js +1 -3
  112. package/package.json +6 -5
  113. package/skills/keiyaku/SKILL.md +6 -47
  114. package/skills/keiyaku-akuma/SKILL.md +75 -0
  115. package/build/agents/effective-policy.js +0 -32
  116. package/build/agents/index.js +0 -114
  117. package/build/agents/selector.js +0 -28
  118. package/build/cli/render/summon.js +0 -17
  119. package/build/cli/render/tell.js +0 -11
  120. package/build/config/akuma-loader.js +0 -268
  121. package/build/core/queue.js +0 -73
  122. package/build/core/summon-persist.js +0 -73
  123. package/build/core/summon.js +0 -377
@@ -0,0 +1,141 @@
1
+ import * as path from "node:path";
2
+ import { ProjectionStateError } from "./atomic-publish.js";
3
+ import { parseExecutionBinding as parseCanonicalExecutionBinding, parseExecutionCoordinates, } from "./execution-coordinate.js";
4
+ const TOP_LEVEL_FIELDS = [
5
+ "version",
6
+ "pid",
7
+ "akuma",
8
+ "provider",
9
+ "authority",
10
+ "binding",
11
+ "mintedAt",
12
+ "workspaceRoot",
13
+ ];
14
+ const TOP_LEVEL_OPTIONAL_FIELDS = ["effectivePolicy"];
15
+ const PACT_FIELD_NAMES = TOP_LEVEL_FIELDS.filter((field) => field !== "authority");
16
+ function pactError(pactPath, field, reason) {
17
+ return new ProjectionStateError(`invalid projection pact at ${pactPath}: field '${field}' ${reason}`);
18
+ }
19
+ function isRecord(value) {
20
+ return value !== null && typeof value === "object" && !Array.isArray(value);
21
+ }
22
+ function assertExactFields(value, required, optional, pactPath, fieldPrefix) {
23
+ for (const field of required) {
24
+ if (!(field in value)) {
25
+ throw pactError(pactPath, fieldPrefix ? `${fieldPrefix}.${field}` : field, "is missing");
26
+ }
27
+ }
28
+ const allowed = new Set([...required, ...optional]);
29
+ const unknown = Object.keys(value).filter((field) => !allowed.has(field)).sort()[0];
30
+ if (unknown !== undefined) {
31
+ throw pactError(pactPath, fieldPrefix ? `${fieldPrefix}.${unknown}` : unknown, "is unknown");
32
+ }
33
+ }
34
+ function assertNonBlankString(value, pactPath, field) {
35
+ if (typeof value !== "string" || value.trim() === "") {
36
+ throw pactError(pactPath, field, "must be a non-blank string");
37
+ }
38
+ }
39
+ /** Parse the pre-publication authority choice without inventing a durable identity. */
40
+ export function parseExecutionAuthoritySelection(value, pactPath = "projection identity", expectedKind) {
41
+ if (!isRecord(value))
42
+ throw pactError(pactPath, "authority", "must be an object");
43
+ assertExactFields(value, ["kind"], [], pactPath, "authority");
44
+ if (value.kind !== "repository" && value.kind !== "user") {
45
+ throw pactError(pactPath, "authority.kind", "must be 'repository' or 'user'");
46
+ }
47
+ if (expectedKind !== undefined && value.kind !== expectedKind) {
48
+ throw pactError(pactPath, "authority.kind", `must be '${expectedKind}' for this projection mint`);
49
+ }
50
+ return { kind: value.kind };
51
+ }
52
+ /** Parse and validate an execution binding without inventing authority identity. */
53
+ export function parseExecutionBinding(value, pactPath = "projection identity") {
54
+ return parseCanonicalExecutionBinding(value, (field, reason) => pactError(pactPath, field, reason));
55
+ }
56
+ export function assertExecutionPactCoordinates(authorityValue, bindingValue, pactPath = "projection identity") {
57
+ return parseExecutionCoordinates(authorityValue, bindingValue, (field, reason) => pactError(pactPath, field, reason));
58
+ }
59
+ function parseEffectivePolicy(value, pactPath) {
60
+ if (!isRecord(value))
61
+ throw pactError(pactPath, "effectivePolicy", "must be an object");
62
+ assertExactFields(value, [], ["access", "network", "webSearch"], pactPath, "effectivePolicy");
63
+ if (value.access !== undefined && value.access !== "read" && value.access !== "write" && value.access !== "auto") {
64
+ throw pactError(pactPath, "effectivePolicy.access", "must be 'read', 'write', or 'auto'");
65
+ }
66
+ if (value.network !== undefined && value.network !== "disabled" && value.network !== "enabled") {
67
+ throw pactError(pactPath, "effectivePolicy.network", "must be 'disabled' or 'enabled'");
68
+ }
69
+ if (value.webSearch !== undefined && !["disabled", "cached", "live"].includes(String(value.webSearch))) {
70
+ throw pactError(pactPath, "effectivePolicy.webSearch", "must be 'disabled', 'cached', or 'live'");
71
+ }
72
+ return value;
73
+ }
74
+ function parseExecutionPactFieldsRecord(value, pactPath) {
75
+ if (value.version !== 1)
76
+ throw pactError(pactPath, "version", "must equal 1");
77
+ if (!Number.isInteger(value.pid) || value.pid < 0) {
78
+ throw pactError(pactPath, "pid", "must be a non-negative integer");
79
+ }
80
+ assertNonBlankString(value.akuma, pactPath, "akuma");
81
+ assertNonBlankString(value.provider, pactPath, "provider");
82
+ assertNonBlankString(value.mintedAt, pactPath, "mintedAt");
83
+ const mintedAtMs = Date.parse(value.mintedAt);
84
+ if (!Number.isFinite(mintedAtMs) || new Date(mintedAtMs).toISOString() !== value.mintedAt) {
85
+ throw pactError(pactPath, "mintedAt", "must be an ISO-8601 timestamp");
86
+ }
87
+ assertNonBlankString(value.workspaceRoot, pactPath, "workspaceRoot");
88
+ if (!path.isAbsolute(value.workspaceRoot)) {
89
+ throw pactError(pactPath, "workspaceRoot", "must be an absolute path");
90
+ }
91
+ const binding = parseExecutionBinding(value.binding, pactPath);
92
+ return {
93
+ version: 1,
94
+ pid: value.pid,
95
+ akuma: value.akuma,
96
+ provider: value.provider,
97
+ binding,
98
+ mintedAt: value.mintedAt,
99
+ workspaceRoot: value.workspaceRoot,
100
+ ...(value.effectivePolicy !== undefined
101
+ ? { effectivePolicy: parseEffectivePolicy(value.effectivePolicy, pactPath) }
102
+ : {}),
103
+ };
104
+ }
105
+ /** Validate every pact field that exists before a repository identity is minted. */
106
+ export function parseExecutionPactFields(value, pactPath = "projection identity") {
107
+ if (!isRecord(value))
108
+ throw pactError(pactPath, "$", "must be an object");
109
+ assertExactFields(value, PACT_FIELD_NAMES, TOP_LEVEL_OPTIONAL_FIELDS, pactPath, "");
110
+ return parseExecutionPactFieldsRecord(value, pactPath);
111
+ }
112
+ export function parseExecutionPact(value, pactPath = "projection identity") {
113
+ if (!isRecord(value))
114
+ throw pactError(pactPath, "$", "must be an object");
115
+ assertExactFields(value, TOP_LEVEL_FIELDS, TOP_LEVEL_OPTIONAL_FIELDS, pactPath, "");
116
+ const fields = parseExecutionPactFieldsRecord(value, pactPath);
117
+ const { authority, binding } = assertExecutionPactCoordinates(value.authority, fields.binding, pactPath);
118
+ return {
119
+ version: fields.version,
120
+ pid: fields.pid,
121
+ akuma: fields.akuma,
122
+ provider: fields.provider,
123
+ authority,
124
+ binding,
125
+ mintedAt: fields.mintedAt,
126
+ workspaceRoot: fields.workspaceRoot,
127
+ ...(fields.effectivePolicy !== undefined ? { effectivePolicy: fields.effectivePolicy } : {}),
128
+ };
129
+ }
130
+ export function parseExecutionPactJson(raw, pactPath = "projection identity") {
131
+ let value;
132
+ try {
133
+ value = JSON.parse(raw);
134
+ }
135
+ catch (error) {
136
+ throw new ProjectionStateError(`invalid projection pact at ${pactPath}: field '$' contains malformed JSON`, {
137
+ cause: error,
138
+ });
139
+ }
140
+ return parseExecutionPact(value, pactPath);
141
+ }
@@ -1,6 +1,5 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
3
- import { dequeueContract } from "./queue.js";
4
3
  import { releaseScope } from "./registry.js";
5
4
  import { deriveContractState, isTerminalState } from "./status.js";
6
5
  import { cleanupContractWorkspace, stableRepoRoot } from "./worktree-path.js";
@@ -15,7 +14,7 @@ export async function forfeitContract(input) {
15
14
  if (ledger) {
16
15
  const state = deriveContractState(ledger.entries);
17
16
  if (state.state === "forfeited") {
18
- await dequeueContract(input.cwd, { contractId: input.contractId, actor: input.actor, at });
17
+ // Already-forfeited retry only performs idempotent cleanup.
19
18
  const cleanup = await cleanupForfeit(input.cwd, input.contractId, input.actor, at);
20
19
  return { contractId: input.contractId, cleanup };
21
20
  }
@@ -44,7 +43,7 @@ export async function forfeitContract(input) {
44
43
  });
45
44
  if (!result.ok)
46
45
  throw new FlowError("INTERNAL_STATE", `failed to forfeit ${input.contractId}: ${result.reason}`);
47
- await dequeueContract(input.cwd, { contractId: input.contractId, actor: input.actor, at });
46
+ // Forfeit entry naturally removes membership; no dequeue record.
48
47
  const cleanup = await cleanupForfeit(input.cwd, input.contractId, input.actor, at);
49
48
  return { contractId: input.contractId, cleanup };
50
49
  }
@@ -4,12 +4,12 @@ import { renderTemplate } from "../flow-error.js";
4
4
  const GUIDANCE_BRANCH_LIST_LIMIT = 3;
5
5
  const HELP_BODY_TEMPLATE = [
6
6
  "## Workflow",
7
- "`bind -> arc -> [summon/ask | amend | delivery work]* -> arc | renew | petition`",
7
+ "`bind -> arc -> [call | amend | delivery work]* -> arc | renew | petition`",
8
8
  "",
9
9
  `- **\${bind}**: Create the active contract ledger and workspace files from Markdown stdin. The worktree must be clean; call it once per Keiyaku.`,
10
10
  `- **\${arc}**: Record the delivery arc title, objective, and brief before work starts. It seals any open arc and does not run agents or mutate delivery code.`,
11
- `- **\${summon}** / **ask**: Summon a ${ACTOR_IDENTITY} for bounded work. An open arc supplies scoped context; the caller still owns review, integration, and commits.`,
12
- `- **akuma list / akuma show**: Inspect available ${ACTOR_IDENTITY_PLURAL} and their resolved provider/executable before choosing \`keiyaku summon NAME\`.`,
11
+ `- **\${call}**: Call a ${ACTOR_IDENTITY} for bounded work. An open arc supplies scoped context; the caller still owns review, integration, and commits.`,
12
+ `- **akuma list / akuma show**: Inspect available ${ACTOR_IDENTITY_PLURAL} and their resolved provider/executable before choosing \`keiyaku call NAME\`.`,
13
13
  "- **delivery commits**: Commit the actual code or documentation changes in git before sealing them.",
14
14
  `- **\${renew}**: Capture receipts, update delivery onto the default branch, seal the open arc, and record the new base.`,
15
15
  `- **\${amend}**: Update the Keiyaku when scope, rules, criteria, or intent changed.`,
@@ -19,12 +19,12 @@ const HELP_BODY_TEMPLATE = [
19
19
  "- **status**: Render the Kanshi board: 現世 head, arc sparkline, age, drift ▲N, and queue stall.",
20
20
  "",
21
21
  "## Command Boundaries",
22
- `- \`\${arc}\` and \`\${renew}\` are ledger operations; they do not summon agents and do not edit delivery code.`,
23
- `- \`\${summon}\` and \`ask\` can run outside a Keiyaku, but an open arc gives the ${ACTOR_IDENTITY} better context.`,
24
- `- \`\${summon}\` starts a new session. Use \`keiyaku resume RESPONSE_PATH\` to continue a prior response artifact.`,
22
+ `- \`\${arc}\` and \`\${renew}\` are ledger operations; they do not call agents and do not edit delivery code.`,
23
+ `- \`\${call}\` can run outside a Keiyaku, but an open arc gives the ${ACTOR_IDENTITY} better context.`,
24
+ `- \`\${call}\` starts a new session. Use \`keiyaku revive ARTIFACT_ID\` to continue a prior response artifact.`,
25
25
  "- `akuma list` and `akuma show <name>` are read-only inspection commands; they do not start a helper.",
26
- "- `keiyaku resume` restores the agent from the response artifact; `--incognito` summons cannot be resumed later.",
27
- "- Premature `petition` requests are rejected; continue with `${arc}`, `${summon}`, or `${renew}` until the work is verified.",
26
+ "- `keiyaku revive` restores the agent from the response artifact; `--incognito` calls cannot be revived later.",
27
+ "- Premature `petition` requests are rejected; continue with `${arc}`, `${call}`, or `${renew}` until the work is verified.",
28
28
  "",
29
29
  "## Core Files (.keiyaku/)",
30
30
  "Detailed configuration and protocol nuances live here.",
@@ -56,13 +56,13 @@ const HELP_BODY_TEMPLATE = [
56
56
  "",
57
57
  "## Workflow Tactics",
58
58
  "- **Pause / Resume**: Use `status` to find active contracts and `@<id-prefix>` to address them from any terminal.",
59
- "- **Delegation Markers**: Place `KEIYAKU_TODO(<instruction>)` comments when you want ${summon} or ${bind} to pick up scoped follow-on work.",
59
+ "- **Delegation Markers**: Place `KEIYAKU_TODO(<instruction>)` comments when you want ${call} or ${bind} to pick up scoped follow-on work.",
60
60
  ];
61
61
  const ARCHITECT_TOOL_NAMES = {
62
62
  bind: TOOL_NAMES.bind,
63
63
  arc: TOOL_NAMES.arc,
64
64
  renew: TOOL_NAMES.renew,
65
- summon: TOOL_NAMES.summon,
65
+ call: TOOL_NAMES.call,
66
66
  amend: TOOL_NAMES.amend,
67
67
  close: TOOL_NAMES.close,
68
68
  };
package/build/core/ids.js CHANGED
@@ -1,6 +1,12 @@
1
1
  import { createGit, wrapGitError } from "../git/core.js";
2
2
  const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
3
3
  const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
4
+ /** Canonical commission identity: slugified lowercase hyphen segments + `.` + Crockford ULID. */
5
+ export const COMMISSION_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*\.[0-9A-HJKMNP-TV-Z]{26}$/;
6
+ /** Minted opaque repository identity: `repo_` + ULID. */
7
+ export function isCommissionId(value) {
8
+ return COMMISSION_ID_RE.test(value);
9
+ }
4
10
  export class ContractIdError extends Error {
5
11
  code = "INVALID_CONTRACT_ID";
6
12
  constructor(message, options) {
@@ -2,8 +2,9 @@ import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError
2
2
  import { formatLedgerEntryCommitMessage, parseLedgerEntryCommitMessage, } from "./entry.js";
3
3
  const EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
4
4
  const MISSING_REF_SENTINEL = "0".repeat(40);
5
+ export const ACTIVE_LEDGER_REF_PREFIX = "refs/keiyaku/active/";
5
6
  export function activeLedgerRef(contractId) {
6
- return `refs/keiyaku/active/${contractId}`;
7
+ return `${ACTIVE_LEDGER_REF_PREFIX}${contractId}`;
7
8
  }
8
9
  async function resolveRefHead(cwd, ref) {
9
10
  const git = createGit(cwd);
@@ -115,3 +116,17 @@ export async function readLedger(cwd, contractId) {
115
116
  return null;
116
117
  return { contractId, ...(await readLedgerRef(cwd, ref, head)) };
117
118
  }
119
+ export async function listContractIds(cwd) {
120
+ const git = createGit(cwd);
121
+ try {
122
+ const output = await git.raw(["for-each-ref", "--format=%(refname)", ACTIVE_LEDGER_REF_PREFIX]);
123
+ return output
124
+ .split(/\r?\n/)
125
+ .map((line) => line.trim())
126
+ .filter((line) => line.startsWith(ACTIVE_LEDGER_REF_PREFIX))
127
+ .map((ref) => ref.slice(ACTIVE_LEDGER_REF_PREFIX.length));
128
+ }
129
+ catch (error) {
130
+ throw wrapGitError(`for-each-ref ${ACTIVE_LEDGER_REF_PREFIX}`, error, cwd);
131
+ }
132
+ }
@@ -1,8 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { getConfig } from "../config/env.js";
3
3
  import { assertSettingsKnobUsable, loadKeiyakuSettings, resolveReviewerAgentName, } from "../config/settings.js";
4
- import { loadAkumaCatalog } from "../config/akuma-loader.js";
5
- import { formatAvailableAgentNames } from "../agents/selector.js";
4
+ import { loadAkumaCatalogV2 } from "../config/akuma-loader_v2.js";
5
+ import { formatAvailableAgentNamesV2 } from "../agents/selector_v2.js";
6
6
  import { getDiffStats } from "../git/diff/preview.js";
7
7
  import { readDiff } from "../git/diff/read.js";
8
8
  import { getCurrentBranch } from "../git/branches.js";
@@ -11,7 +11,7 @@ import { DIFF_PREVIEW_TEXT_MAX_CHARS } from "../keiyaku.js";
11
11
  import { FlowError } from "../flow-error.js";
12
12
  import { persistResponseHistory } from "./transcripts.js";
13
13
  import { logInfo } from "../telemetry/logger.js";
14
- import { execSubagent } from "./summon.js";
14
+ import { execSubagent } from "./call.js";
15
15
  import { readContractLog } from "./log.js";
16
16
  import { buildVerificationFailureHints } from "./hints.js";
17
17
  const CLAIM_VERIFICATION_OUTPUT_MAX_CHARS = 1200;
@@ -159,7 +159,7 @@ export function createAgentReviewGate() {
159
159
  return REVIEW_SKIPPED_RESULT;
160
160
  }
161
161
  const loaded = await loadKeiyakuSettings(cwd);
162
- // Always touch legacy profile authority + reviewer. Touch default only when reviewer is absent
162
+ // Always touch agents catalog disease + reviewer. Touch default only when reviewer is absent
163
163
  // and resolution would fall back through resolveReviewerAgentName → default.
164
164
  assertSettingsKnobUsable(loaded, "agents");
165
165
  assertSettingsKnobUsable(loaded, "reviewer");
@@ -168,10 +168,10 @@ export function createAgentReviewGate() {
168
168
  }
169
169
  const settings = loaded.knobs;
170
170
  const reviewerAgentName = resolveReviewerAgentName(settings);
171
- const catalog = await loadAkumaCatalog(cwd);
172
- const availableNames = Object.keys(catalog.definitions);
173
- if (catalog.definitions[reviewerAgentName] === undefined) {
174
- throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${reviewerAgentName}'. Expected one of: ${formatAvailableAgentNames(availableNames)}`);
171
+ const catalog = await loadAkumaCatalogV2(cwd);
172
+ const availableNames = Object.keys(catalog.selected);
173
+ if (catalog.selected[reviewerAgentName] === undefined) {
174
+ throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${reviewerAgentName}'. Expected one of: ${formatAvailableAgentNamesV2(availableNames)}`);
175
175
  }
176
176
  const [contractLog, diffStat, fullDiff, currentBranch, headRef] = await Promise.all([
177
177
  readContractLog({ cwd, contractId }),
@@ -197,26 +197,28 @@ export function createAgentReviewGate() {
197
197
  const selectedReviewerAgentName = reviewerAgentName;
198
198
  const subagentResult = await execSubagent(selectedReviewerAgentName, prompt, cwd, {
199
199
  signal: input.signal,
200
+ authority: { kind: "repository" },
201
+ binding: { kind: "commission", commissionId: contractId },
200
202
  });
201
203
  const reviewHeadBeforeWrite = await getLatestCommitHash(cwd);
202
204
  const raw = subagentResult.finalMessage.trim();
203
205
  const passed = raw.startsWith(REVIEW_PASS_TOKEN);
204
206
  const observations = passed ? raw.slice(REVIEW_PASS_TOKEN.length).trim() || undefined : undefined;
205
- const responsePath = await persistResponseHistory({
207
+ const persisted = await persistResponseHistory({
206
208
  cwd,
207
209
  tool: "review",
210
+ provenance: { authority: { kind: "repository" }, binding: { kind: "commission", commissionId: contractId }, evidenceCwd: cwd },
208
211
  title: String(reviewNumber),
209
212
  prompt,
210
213
  response: subagentResult.finalMessage,
211
214
  session: subagentResult.session,
212
215
  subagentName: selectedReviewerAgentName,
213
- branch: currentBranch,
214
- commit: headRef,
216
+ launchSnapshot: subagentResult.launchSnapshot,
215
217
  });
216
218
  const review = {
217
219
  review: reviewNumber,
218
220
  result: passed ? "passed" : "rejected",
219
- historyPath: responsePath,
221
+ historyPath: persisted.responsePath,
220
222
  findings: passed ? observations : raw || REVIEW_RESPONSE_FALLBACK,
221
223
  };
222
224
  if (review.result === "passed") {
@@ -82,6 +82,7 @@ export async function claimKeiyaku(input, context, dependencies) {
82
82
  nowMs: Date.now(),
83
83
  intent: "claim",
84
84
  oath,
85
+ waivePaths: input.waivePaths,
85
86
  });
86
87
  const log = await readContractLog({ cwd, contractId });
87
88
  const verdict = await verdictContract({
@@ -2,7 +2,7 @@ import { assertSettingsKnobUsable, loadKeiyakuSettings } from "../config/setting
2
2
  import { FlowError } from "../flow-error.js";
3
3
  import { readCurrentContractContext } from "./context.js";
4
4
  import { readLedger } from "./ledger.js";
5
- import { resolveDefaultBranch } from "./status.js";
5
+ import { commissionTarget } from "./status.js";
6
6
  import { getCurrentBranch, isGitRepo } from "../git/branches.js";
7
7
  import { claimKeiyaku } from "./petition-claim.js";
8
8
  import { forfeitKeiyaku } from "./petition-forfeit.js";
@@ -41,10 +41,14 @@ async function loadCloseContext(cwd, requestedContractId) {
41
41
  if (!ledger) {
42
42
  throw new FlowError("INTERNAL_STATE", `contract ${requestedContractId} not found or not active`);
43
43
  }
44
+ const target = commissionTarget(ledger.entries);
45
+ if (!target) {
46
+ throw new FlowError("INTERNAL_STATE", `contract ${requestedContractId} active ledger is missing commission target`);
47
+ }
44
48
  return {
45
49
  cwd,
46
50
  keiyakuBranch: `keiyaku/${requestedContractId}`,
47
- baseBranch: (await resolveDefaultBranch(cwd))?.branch ?? await getCurrentBranch(cwd),
51
+ baseBranch: target.branch,
48
52
  contractId: requestedContractId,
49
53
  };
50
54
  }
@@ -1,18 +1,32 @@
1
1
  import { FlowError } from "../flow-error.js";
2
+ import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../git/core.js";
2
3
  import { parseCommandMarkdown } from "./command-io.js";
3
4
  import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
4
- import { enqueueContract, queuePosition } from "./queue.js";
5
+ import { allocateSettlementSeat, queueMembership, readQueueReadModel } from "./queue_v2.js";
5
6
  import { findOpenArc, receiptsAfterFence, sealOpenArc } from "./seal.js";
6
- import { deriveContractState, latestBase, resolveDefaultBranch } from "./status.js";
7
+ import { commissionTarget, deriveContractState, latestBase } from "./status.js";
7
8
  import { findContractWorktreePath } from "./worktree-path.js";
8
- async function settledQueuePosition(cwd, contractId, writtenPosition) {
9
- return writtenPosition || (await queuePosition(cwd, contractId)) || 0;
9
+ async function resolveCommit(cwd, ref) {
10
+ const git = createGit(cwd);
11
+ try {
12
+ return (await git.raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
13
+ }
14
+ catch (error) {
15
+ if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
16
+ return null;
17
+ throw wrapGitError(`rev-parse ${ref}`, error, cwd);
18
+ }
10
19
  }
11
- async function assertFresh(cwd, contractId, base) {
12
- const target = await resolveDefaultBranch(cwd);
13
- if (!target)
14
- return;
15
- if (base !== target.head) {
20
+ async function assertFresh(cwd, contractId, entries, base) {
21
+ const target = commissionTarget(entries);
22
+ if (!target) {
23
+ throw new FlowError("INTERNAL_STATE", `contract ${contractId} missing commission target`);
24
+ }
25
+ const head = await resolveCommit(cwd, target.ref);
26
+ if (!head) {
27
+ throw new FlowError("INTERNAL_STATE", `contract ${contractId} commission target ${target.ref} does not resolve`);
28
+ }
29
+ if (base !== head) {
16
30
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} must renew onto ${target.branch} before petition`, { hints: [`keiyaku @${contractId} renew — renews delivery before petition`] });
17
31
  }
18
32
  }
@@ -24,20 +38,15 @@ function assertPetitionable(contractId, entries) {
24
38
  }
25
39
  export async function petitionContract(input) {
26
40
  const at = new Date(input.nowMs).toISOString();
27
- let ledger = await readLedger(input.cwd, input.contractId);
41
+ const ledger = await readLedger(input.cwd, input.contractId);
28
42
  if (!ledger) {
29
43
  throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
30
44
  }
31
- const existingState = deriveContractState(ledger.entries);
32
- if (existingState.state === "petitioned") {
33
- const queued = await enqueueContract(input.cwd, { contractId: input.contractId, actor: input.actor, at });
34
- return { contractId: input.contractId, position: await settledQueuePosition(input.cwd, input.contractId, queued.position) };
35
- }
36
45
  const deliveryCwd = (await findContractWorktreePath(input.cwd, input.contractId)) ?? input.cwd;
37
46
  const base = latestBase(ledger.entries);
38
47
  if (!base)
39
48
  throw new FlowError("INTERNAL_STATE", "ledger must contain bind entry");
40
- await assertFresh(input.cwd, input.contractId, base);
49
+ await assertFresh(input.cwd, input.contractId, ledger.entries, base);
41
50
  if (!findOpenArc(ledger.entries)) {
42
51
  const loose = await receiptsAfterFence(deliveryCwd, ledger.entries);
43
52
  if (loose.length > 0) {
@@ -51,6 +60,16 @@ export async function petitionContract(input) {
51
60
  contractId: input.contractId,
52
61
  sealedBy: "petition",
53
62
  nowMs: input.nowMs,
63
+ waivePaths: input.intent === "claim" ? input.waivePaths : undefined,
64
+ });
65
+ const target = commissionTarget(ledger.entries);
66
+ if (!target) {
67
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} missing commission target`);
68
+ }
69
+ const allocated = await allocateSettlementSeat(input.cwd, {
70
+ target: target.ref,
71
+ actor: input.actor,
72
+ at,
54
73
  });
55
74
  const ref = activeLedgerRef(input.contractId);
56
75
  const result = await appendWithRetry(input.cwd, ref, (_head, entries) => {
@@ -59,8 +78,17 @@ export async function petitionContract(input) {
59
78
  return null;
60
79
  assertPetitionable(input.contractId, entries);
61
80
  const data = input.intent === "claim"
62
- ? { intent: "claim", oath: input.oath }
63
- : { intent: "forfeit" };
81
+ ? {
82
+ intent: "claim",
83
+ oath: input.oath,
84
+ target: target.ref,
85
+ seat: allocated.seat,
86
+ }
87
+ : {
88
+ intent: "forfeit",
89
+ target: target.ref,
90
+ seat: allocated.seat,
91
+ };
64
92
  const entry = {
65
93
  v: 1,
66
94
  kind: "petition",
@@ -71,13 +99,26 @@ export async function petitionContract(input) {
71
99
  };
72
100
  return entry;
73
101
  });
74
- if (!result.ok && result.reason !== "aborted") {
102
+ if (!result.ok) {
103
+ // Race/abort: allocated seat remains a legal hole; do not report success.
104
+ if (result.reason === "aborted") {
105
+ const current = await readLedger(input.cwd, input.contractId);
106
+ if (current) {
107
+ assertPetitionable(input.contractId, current.entries);
108
+ }
109
+ throw new FlowError("INTERNAL_STATE", `failed to petition contract ${input.contractId}: aborted`);
110
+ }
75
111
  throw new FlowError("INTERNAL_STATE", `failed to petition contract ${input.contractId}: ${result.reason}`);
76
112
  }
77
- const queued = await enqueueContract(input.cwd, { contractId: input.contractId, actor: input.actor, at });
113
+ const model = await readQueueReadModel(input.cwd);
114
+ const membership = queueMembership(model, input.contractId);
115
+ const position = membership?.position ?? 0;
116
+ if (position <= 0) {
117
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} missing target-local queue position after petition`);
118
+ }
78
119
  return {
79
120
  contractId: input.contractId,
80
- position: await settledQueuePosition(input.cwd, input.contractId, queued.position),
121
+ position,
81
122
  ...(sealed ? { sealed } : {}),
82
123
  };
83
124
  }
@@ -92,5 +133,6 @@ export function buildPetitionClaimInput(input) {
92
133
  plea: `Settle contract ${input.contractId} through the petition pipeline.`,
93
134
  oath,
94
135
  signal: input.signal,
136
+ waivePaths: input.waivePaths,
95
137
  };
96
138
  }
@@ -5,7 +5,6 @@ export const CONTRACT_PLACES = Object.freeze([
5
5
  "neverland",
6
6
  "mordor",
7
7
  "shire",
8
- "rivendell",
9
8
  "olympus",
10
9
  "valhalla",
11
10
  "asgard",
@@ -18,7 +17,6 @@ export const CONTRACT_PLACES = Object.freeze([
18
17
  "namek",
19
18
  "laputa",
20
19
  "tatooine",
21
- "pandora",
22
20
  "stomach",
23
21
  "eyeball",
24
22
  "braincell",
@@ -27,20 +25,10 @@ export const CONTRACT_PLACES = Object.freeze([
27
25
  "armpit",
28
26
  "chineseroom",
29
27
  "catbox",
30
- "platocave",
31
- "hilberthotel",
32
- "panopticon",
33
- "theseus",
34
28
  "babel",
35
29
  "teapot",
36
30
  "blackhole",
37
31
  "wormhole",
38
- "oddpoint",
39
- "eventhorizon",
40
- "kleinbottle",
41
- "mobius",
42
- "tesseract",
43
- "nullisland",
44
32
  "devnull",
45
33
  "localhost",
46
34
  "recyclebin",
@@ -77,6 +65,111 @@ export const CONTRACT_PLACES = Object.freeze([
77
65
  "dumpster",
78
66
  "blanketfort",
79
67
  "kotatsu",
80
- "toire",
81
68
  "fishbowl",
69
+ "pokeball",
70
+ "pineapple",
71
+ "titanic",
72
+ "disneyland",
73
+ "ikea",
74
+ "costco",
75
+ "warppipe",
76
+ "tetris",
77
+ "minecraft",
78
+ "ufo",
79
+ "catbus",
80
+ "timemachine",
81
+ "moonbox",
82
+ "huaguoshan",
83
+ "pansidong",
84
+ "bellybutton",
85
+ "sock",
86
+ "spam",
87
+ "downloads",
88
+ "toilet",
89
+ "blender",
90
+ "toaster",
91
+ "litterbox",
92
+ "kiddiepool",
93
+ "beanbag",
94
+ "vacuum",
95
+ "elevator",
96
+ "glovebox",
97
+ "attic",
98
+ "cloud",
99
+ "wall",
100
+ "can",
101
+ "cup",
102
+ "wc",
103
+ "bean",
104
+ "bus",
105
+ "helicopter",
106
+ "commandroom",
107
+ "box",
108
+ "jar",
109
+ "pot",
110
+ "wok",
111
+ "bowl",
112
+ "bucket",
113
+ "drawer",
114
+ "sofa",
115
+ "fridge",
116
+ "microwave",
117
+ "well",
118
+ "ditch",
119
+ "puddle",
120
+ "tent",
121
+ "cave",
122
+ "shed",
123
+ "garage",
124
+ "basement",
125
+ "balcony",
126
+ "corner",
127
+ "pigpen",
128
+ "henhouse",
129
+ "excavator",
130
+ "tank",
131
+ "submarine",
132
+ "treehouse",
133
+ "igloo",
134
+ "phonebooth",
135
+ "mailbox",
136
+ "doormat",
137
+ "bathtub",
138
+ "wardrobe",
139
+ "quicksand",
140
+ "tarpit",
141
+ "haystack",
142
+ "manhole",
143
+ "chimney",
144
+ "dryer",
145
+ "fittingroom",
146
+ "lazysusan",
147
+ "revolvingdoor",
148
+ "minibar",
149
+ "casino",
150
+ "karaoke",
151
+ "pinball",
152
+ "funhouse",
153
+ "burrito",
154
+ "dumpling",
155
+ "hotpot",
156
+ "bento",
157
+ "teabag",
158
+ "fortunecookie",
159
+ "airlock",
160
+ "escapepod",
161
+ "hammock",
162
+ "legroom",
163
+ "sidequest",
164
+ "lootbox",
165
+ "gutter",
166
+ "pawnshop",
167
+ "hermitcrab",
168
+ "couchgap",
169
+ "dumbwaiter",
170
+ "petridish",
171
+ "baggageclaim",
172
+ "morgue",
173
+ "ballpit",
174
+ "clawmachine",
82
175
  ]);