@astrosheep/keiyaku 2.9.9 → 2.9.10

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 (230) hide show
  1. package/README.md +50 -182
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +2 -1
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +6 -1
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +3 -2
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +9 -16
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +3 -5
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +2 -0
  90. package/build/config/env.js +23 -0
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +6 -5
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +27 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +21 -358
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +114 -119
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -1,126 +1,103 @@
1
- import { listContractIds, readLedger } from "../ledger.js";
2
- import { deriveContractState } from "../status/lifecycle.js";
3
- import { commissionTarget } from "../status/drift.js";
4
- export function isCurrentPetitionData(data) {
5
- return "target" in data && "seat" in data;
6
- }
7
- function latestPetition(entries) {
8
- for (let index = entries.length - 1; index >= 0; index -= 1) {
9
- const entry = entries[index];
10
- if (entry.kind === "petition")
11
- return entry;
12
- }
13
- return undefined;
14
- }
15
- export function deriveQueueReadModel(ledgers) {
1
+ import { lookupCurrentContract, readRepositoryLifecycle, } from "../repository-ledger/read-model.js";
2
+ export function deriveQueueReadModel(read) {
16
3
  const seatsByContract = new Map();
17
4
  const diagnosticsByContract = new Map();
18
- const orderedByTarget = new Map();
19
- for (const ledger of ledgers) {
20
- if (deriveContractState(ledger.entries).state !== "petitioned")
21
- continue;
22
- const petition = latestPetition(ledger.entries);
23
- if (!petition)
24
- continue;
25
- const derived = commissionTarget(ledger.entries);
26
- if (!derived)
27
- continue;
28
- const diagnostics = [];
29
- let seat;
30
- if (isCurrentPetitionData(petition.data)) {
31
- seat = {
32
- contract: ledger.contractId,
33
- target: petition.data.target,
34
- seat: petition.data.seat,
35
- legacy: false,
5
+ const candidatesByTarget = new Map();
6
+ const occupancyByTarget = new Map();
7
+ for (const contract of read.isolatedContracts) {
8
+ diagnosticsByContract.set(contract, [{
9
+ kind: "lifecycle-disease",
10
+ status: "isolated",
11
+ diagnostics: read.diagnostics.filter((diagnostic) => diagnostic.contract === contract),
12
+ }]);
13
+ }
14
+ for (const model of read.contracts) {
15
+ // Every accepted petition occupies its seat, even if a later transition
16
+ // diseased the contract and removed it from settlement ordering.
17
+ for (const { objectId, entry } of model.history) {
18
+ if (entry.kind !== "petition")
19
+ continue;
20
+ const occupancy = {
21
+ contract: model.contract,
22
+ target: entry.data.target,
23
+ seat: entry.data.seat,
24
+ objectId,
36
25
  };
37
- if (petition.data.target !== derived.ref) {
38
- diagnostics.push({
39
- kind: "target-mismatch",
40
- petitioned: petition.data.target,
41
- derived: derived.ref,
42
- });
43
- }
44
- const bucket = orderedByTarget.get(petition.data.target) ?? [];
45
- bucket.push(seat);
46
- orderedByTarget.set(petition.data.target, bucket);
26
+ const occupancies = occupancyByTarget.get(occupancy.target) ?? [];
27
+ occupancies.push(occupancy);
28
+ occupancyByTarget.set(occupancy.target, occupancies);
47
29
  }
48
- else {
49
- seat = {
50
- contract: ledger.contractId,
51
- target: derived.ref,
52
- seat: null,
53
- legacy: true,
54
- };
55
- diagnostics.push({ kind: "legacy-petition" });
30
+ // A diseased contract remains visible to status and occupies its persisted
31
+ // seat, but cannot participate in settlement ordering.
32
+ const outcome = lookupCurrentContract(read, model.contract);
33
+ if (outcome.status === "diseased") {
34
+ diagnosticsByContract.set(model.contract, [{
35
+ kind: "lifecycle-disease",
36
+ status: "diseased",
37
+ diagnostics: outcome.diagnostics,
38
+ }]);
39
+ continue;
56
40
  }
57
- seatsByContract.set(ledger.contractId, seat);
58
- diagnosticsByContract.set(ledger.contractId, diagnostics);
41
+ if (outcome.status !== "healthy")
42
+ continue;
43
+ if (model.phase !== "awaiting-verdict" && model.phase !== "approved")
44
+ continue;
45
+ if (model.petition.state === "none")
46
+ continue;
47
+ const seat = {
48
+ contract: model.contract,
49
+ target: model.petition.target,
50
+ seat: model.petition.seat,
51
+ };
52
+ const candidates = candidatesByTarget.get(seat.target) ?? [];
53
+ candidates.push({ seat, petitionObjectId: model.petition.objectId });
54
+ candidatesByTarget.set(seat.target, candidates);
55
+ seatsByContract.set(model.contract, seat);
56
+ diagnosticsByContract.set(model.contract, []);
59
57
  }
60
- for (const [target, seats] of orderedByTarget) {
61
- const bySeat = new Map();
62
- for (const seat of seats) {
63
- if (seat.seat === null)
64
- continue;
65
- const group = bySeat.get(seat.seat) ?? [];
66
- group.push(seat);
67
- bySeat.set(seat.seat, group);
68
- }
69
- for (const [seatNumber, group] of bySeat) {
70
- if (group.length < 2)
58
+ const orderedByTarget = new Map();
59
+ for (const [target, candidates] of candidatesByTarget) {
60
+ for (const candidate of candidates) {
61
+ const collisions = (occupancyByTarget.get(target) ?? [])
62
+ .filter((occupancy) => occupancy.seat === candidate.seat.seat && occupancy.objectId !== candidate.petitionObjectId)
63
+ .sort((left, right) => left.contract.localeCompare(right.contract) || left.objectId.localeCompare(right.objectId));
64
+ if (collisions.length === 0)
71
65
  continue;
72
- const sorted = [...group].sort((a, b) => a.contract.localeCompare(b.contract));
73
- for (const member of sorted) {
74
- const others = sorted.filter((other) => other.contract !== member.contract);
75
- const existing = diagnosticsByContract.get(member.contract) ?? [];
76
- for (const other of others) {
77
- existing.push({ kind: "duplicate-seat", seat: seatNumber, with: other.contract });
78
- }
79
- diagnosticsByContract.set(member.contract, existing);
66
+ const diagnostics = diagnosticsByContract.get(candidate.seat.contract);
67
+ for (const collision of collisions) {
68
+ diagnostics.push({ kind: "duplicate-seat", seat: candidate.seat.seat, with: collision.contract });
80
69
  }
81
70
  }
82
- seats.sort((a, b) => {
83
- const seatA = a.seat ?? Number.POSITIVE_INFINITY;
84
- const seatB = b.seat ?? Number.POSITIVE_INFINITY;
85
- if (seatA !== seatB)
86
- return seatA - seatB;
71
+ // A duplicated persisted seat is a resource collision, not a deterministic
72
+ // tie-break. Keep the affected petitions visible with diagnostics, but do
73
+ // not let either one participate in settlement ordering.
74
+ const ordered = candidates.map((candidate) => candidate.seat).filter((candidate) => !diagnosticsByContract.get(candidate.contract)?.some((diagnostic) => diagnostic.kind === "duplicate-seat"));
75
+ ordered.sort((a, b) => {
76
+ if (a.seat !== b.seat)
77
+ return a.seat - b.seat;
87
78
  return a.contract.localeCompare(b.contract);
88
79
  });
89
- orderedByTarget.set(target, seats);
80
+ orderedByTarget.set(target, ordered);
90
81
  }
91
82
  const targetQueues = [...orderedByTarget.entries()]
92
83
  .sort(([a], [b]) => a.localeCompare(b))
93
84
  .map(([target, ordered]) => ({ target, ordered }));
94
85
  return {
86
+ observedLedgerHead: read.observedLedgerHead,
95
87
  targetQueues,
96
88
  seatsByContract,
97
89
  diagnosticsByContract,
90
+ unattributedDiagnostics: read.diagnostics.filter((diagnostic) => diagnostic.contract === undefined),
98
91
  };
99
92
  }
100
93
  export async function readQueueReadModel(cwd) {
101
- const contractIds = await listContractIds(cwd);
102
- const ledgers = [];
103
- for (const id of contractIds) {
104
- const ledger = await readLedger(cwd, id);
105
- if (ledger)
106
- ledgers.push(ledger);
107
- }
108
- return deriveQueueReadModel(ledgers);
94
+ return deriveQueueReadModel(await readRepositoryLifecycle(cwd));
109
95
  }
110
96
  export function queueMembership(model, contractId) {
111
97
  const seat = model.seatsByContract.get(contractId);
112
98
  if (!seat)
113
99
  return undefined;
114
100
  const diagnostics = model.diagnosticsByContract.get(contractId) ?? [];
115
- if (seat.legacy) {
116
- return {
117
- seat,
118
- position: null,
119
- queueLength: 0,
120
- queueHead: null,
121
- diagnostics,
122
- };
123
- }
124
101
  const targetQueue = model.targetQueues.find((queue) => queue.target === seat.target);
125
102
  if (!targetQueue) {
126
103
  return {
@@ -1,103 +1,51 @@
1
- import { createHash } from "node:crypto";
2
1
  import { FlowError } from "../../flow-error.js";
3
- import { listContractIds, readLedger } from "../ledger.js";
4
- import { casUpdateRefLog, createRefLogCommit, listRefLogCommits, readRefLogCommitMessage, resolveRefLogHead, } from "../ref-log.js";
5
- import { isCurrentPetitionData, queueMembership, } from "./queue-read-model.js";
6
- const ISO_UTC_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
2
+ import { readRepositoryLifecycle } from "../repository-ledger/read-model.js";
3
+ import { queueMembership, } from "./queue-read-model.js";
7
4
  function isPositiveSafeInteger(value) {
8
5
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
9
6
  }
10
7
  export function assertClaimQueueHead(model, contractId) {
8
+ const lifecycleDisease = model.diagnosticsByContract.get(contractId)?.find((diagnostic) => diagnostic.kind === "lifecycle-disease");
9
+ if (lifecycleDisease?.kind === "lifecycle-disease") {
10
+ throw new FlowError("INTERNAL_STATE", `contract ${contractId} is unavailable because its repository ledger history is ${lifecycleDisease.status}`, {
11
+ facts: {
12
+ kind: "repository_lifecycle_disease",
13
+ contractId,
14
+ status: lifecycleDisease.status,
15
+ diagnostics: lifecycleDisease.diagnostics.map((diagnostic) => ({ ...diagnostic })),
16
+ },
17
+ });
18
+ }
11
19
  const membership = queueMembership(model, contractId);
12
20
  if (!membership) {
21
+ if (model.unattributedDiagnostics.length > 0) {
22
+ throw new FlowError("INTERNAL_STATE", `contract ${contractId} queue membership is indeterminate because the repository ledger is diseased`, {
23
+ facts: {
24
+ kind: "repository_lifecycle_disease",
25
+ contractId,
26
+ status: "indeterminate",
27
+ diagnostics: model.unattributedDiagnostics.map((diagnostic) => ({ ...diagnostic })),
28
+ },
29
+ });
30
+ }
13
31
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} is not queue head; position=not queued`);
14
32
  }
15
- if (membership.seat.legacy) {
16
- throw new FlowError("QUEUE_LEGACY_PETITION", `contract ${contractId} petitioned before seat law took effect; reject its petition and petition again`);
17
- }
18
- const mismatch = membership.diagnostics.find((diagnostic) => diagnostic.kind === "target-mismatch");
19
- if (mismatch && mismatch.kind === "target-mismatch") {
20
- throw new FlowError("QUEUE_TARGET_MISMATCH", `contract ${contractId} petition target ${mismatch.petitioned} disagrees with derived target ${mismatch.derived}; reject its petition and petition again`);
33
+ const collision = membership.diagnostics.find((diagnostic) => diagnostic.kind === "duplicate-seat");
34
+ if (collision && collision.kind === "duplicate-seat") {
35
+ throw new FlowError("INTERNAL_STATE", `contract ${contractId} has duplicate settlement seat ${collision.seat} with ${collision.with}; reject its petition and petition again`);
21
36
  }
22
37
  if (membership.position !== 1) {
23
38
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} is not queue head; position=${membership.position ?? "not queued"}`);
24
39
  }
25
40
  return membership;
26
41
  }
27
- export function seatAllocatorRef(target) {
28
- const digest = createHash("sha256").update(target, "utf8").digest("hex");
29
- return `refs/keiyaku/seat/${digest}`;
30
- }
31
- function parseSeatAllocatorMessage(message, expectedTarget) {
32
- const normalized = message.replace(/\r\n/g, "\n");
33
- const separatorIndex = normalized.indexOf("\n\n");
34
- if (separatorIndex < 0)
35
- throw new Error("missing blank line");
36
- const subject = normalized.slice(0, separatorIndex).trim();
37
- const body = normalized.slice(separatorIndex + 2).trim();
38
- const parsed = JSON.parse(body);
39
- const keys = Object.keys(parsed).sort();
40
- if (keys.join(",") !== "actor,at,seat,target,v")
41
- throw new Error("unexpected fields");
42
- if (parsed.v !== 1)
43
- throw new Error("bad version");
44
- if (typeof parsed.target !== "string" || parsed.target !== expectedTarget)
45
- throw new Error("target mismatch");
46
- if (!isPositiveSafeInteger(parsed.seat))
47
- throw new Error("bad seat");
48
- if (typeof parsed.at !== "string" || !ISO_UTC_RE.test(parsed.at))
49
- throw new Error("bad at");
50
- if (typeof parsed.actor !== "string" || parsed.actor.trim().length === 0)
51
- throw new Error("bad actor");
52
- if (subject !== `keiyaku seat: ${parsed.seat}`)
53
- throw new Error("bad subject");
54
- return {
55
- v: 1,
56
- target: parsed.target,
57
- seat: parsed.seat,
58
- at: parsed.at,
59
- actor: parsed.actor,
60
- };
61
- }
62
- async function parseAllocatorChain(cwd, head, expectedTarget) {
63
- // Intentional degrade: any rev-list or parse failure treats the chain as corrupt.
64
- let commits;
65
- try {
66
- commits = await listRefLogCommits(cwd, head);
67
- }
68
- catch {
69
- return null;
70
- }
71
- if (commits.length === 0)
72
- return null;
73
- const entries = [];
74
- let previousSeat = 0;
75
- for (const commit of commits) {
76
- try {
77
- const entry = parseSeatAllocatorMessage(await readRefLogCommitMessage(cwd, commit), expectedTarget);
78
- if (entry.seat <= previousSeat)
79
- return null;
80
- previousSeat = entry.seat;
81
- entries.push(entry);
82
- }
83
- catch {
84
- return null;
85
- }
86
- }
87
- return { entries, lastSeat: previousSeat };
88
- }
89
42
  async function ledgerSeatFloor(cwd, target) {
90
- const contractIds = await listContractIds(cwd);
43
+ const lifecycle = await readRepositoryLifecycle(cwd);
91
44
  let maxSeat = 0;
92
- for (const id of contractIds) {
93
- const ledger = await readLedger(cwd, id);
94
- if (!ledger)
95
- continue;
96
- for (const entry of ledger.entries) {
45
+ for (const model of lifecycle.contracts) {
46
+ for (const { entry } of model.history) {
97
47
  if (entry.kind !== "petition")
98
48
  continue;
99
- if (!isCurrentPetitionData(entry.data))
100
- continue;
101
49
  if (entry.data.target !== target)
102
50
  continue;
103
51
  if (entry.data.seat > maxSeat)
@@ -107,50 +55,11 @@ async function ledgerSeatFloor(cwd, target) {
107
55
  return maxSeat;
108
56
  }
109
57
  export async function allocateSettlementSeat(cwd, input) {
110
- const ref = seatAllocatorRef(input.target);
111
- const maxRetries = 3;
112
- for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
113
- // Raw observed-head read: any object id, not commit-only.
114
- // Non-commit heads are returned so parseAllocatorChain can classify them as corrupt.
115
- const observedHead = await resolveRefLogHead(cwd, ref, { peel: "object" });
116
- const floor = await ledgerSeatFloor(cwd, input.target);
117
- const parsed = observedHead ? await parseAllocatorChain(cwd, observedHead, input.target) : null;
118
- const healthy = parsed !== null && parsed.lastSeat >= floor;
119
- let seat;
120
- let parent;
121
- let expectedOld;
122
- if (healthy && parsed) {
123
- seat = parsed.lastSeat + 1;
124
- parent = observedHead;
125
- expectedOld = observedHead;
126
- }
127
- else {
128
- // Missing, parse failure, target mismatch, non-monotonic, or stale-behind-ledger:
129
- // mint a detached no-parent root at floor+1. Leave the bad chain for Git GC.
130
- seat = floor + 1;
131
- parent = null;
132
- expectedOld = observedHead;
133
- }
134
- if (!isPositiveSafeInteger(seat)) {
135
- throw new FlowError("INTERNAL_STATE", `settlement seats exhausted for ${input.target}`);
136
- }
137
- const entry = {
138
- v: 1,
139
- target: input.target,
140
- seat,
141
- at: input.at,
142
- actor: input.actor,
143
- };
144
- const newHead = await createRefLogCommit(cwd, {
145
- subject: `keiyaku seat: ${entry.seat}`,
146
- body: JSON.stringify(entry, null, 2),
147
- parentHead: parent,
148
- errorLabel: `seat ${entry.seat}`,
149
- });
150
- // Intentional degrade: CAS failure returns false; bounded retry re-reads head.
151
- const casOk = await casUpdateRefLog(cwd, ref, newHead, expectedOld);
152
- if (casOk)
153
- return { head: newHead, seat };
58
+ // The candidate is derived from one typed ledger observation. The later
59
+ // repository-ledger transaction owns the only authoritative CAS and commit.
60
+ const seat = (await ledgerSeatFloor(cwd, input.target)) + 1;
61
+ if (!isPositiveSafeInteger(seat)) {
62
+ throw new FlowError("INTERNAL_STATE", `settlement seats exhausted for ${input.target}`);
154
63
  }
155
- throw new FlowError("INTERNAL_STATE", `failed to allocate settlement seat for ${input.target}`);
64
+ return { seat };
156
65
  }
@@ -3,4 +3,4 @@
3
3
  * Implementation lives in sibling files; this module preserves the historical import path.
4
4
  */
5
5
  export { deriveQueueReadModel, queueMembership, readQueueReadModel, } from "./queue-read-model.js";
6
- export { allocateSettlementSeat, assertClaimQueueHead, seatAllocatorRef, } from "./queue-seat-allocation.js";
6
+ export { allocateSettlementSeat, assertClaimQueueHead, } from "./queue-seat-allocation.js";