@astrosheep/keiyaku 2.9.8 → 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 +10 -7
  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 +242 -79
  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 +65 -29
  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 +13 -35
  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 +45 -69
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +3 -1
  90. package/build/config/env.js +24 -1
  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 +13 -6
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +29 -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 +35 -337
  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 +125 -127
  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
@@ -0,0 +1,35 @@
1
+ import { FlowError } from "../flow-error.js";
2
+ import { normalizeSectionTitle } from "./markdown/titles.js";
3
+ import { markdownSourceSections } from "./scope.js";
4
+ export function effectiveScope(entries) {
5
+ const bind = entries.find((entry) => entry.kind === "bind");
6
+ if (!bind)
7
+ throw new FlowError("INTERNAL_STATE", "lifecycle history must contain bind entry");
8
+ const scope = [...bind.data.scope];
9
+ for (const entry of entries) {
10
+ if (entry.kind === "amend" && entry.data.scopeDelta)
11
+ scope.push(...entry.data.scopeDelta.add);
12
+ }
13
+ return scope;
14
+ }
15
+ /** Ordered contract-wide criteria derived from admitted lifecycle history. */
16
+ export function effectiveCriteria(entries) {
17
+ const bind = entries.find((entry) => entry.kind === "bind");
18
+ if (!bind)
19
+ throw new FlowError("INTERNAL_STATE", "lifecycle history must contain bind entry");
20
+ const criteria = [...bind.data.criteria];
21
+ for (const entry of entries) {
22
+ if (entry.kind !== "amend")
23
+ continue;
24
+ for (const section of markdownSourceSections(entry.data.amendment)) {
25
+ if (section.level !== 2 || normalizeSectionTitle(section.title) !== "criteria")
26
+ continue;
27
+ for (const line of entry.data.amendment.slice(section.contentStart, section.end).split(/\r?\n/)) {
28
+ const match = line.match(/^\s*-\s+(.+)$/);
29
+ if (match)
30
+ criteria.push(match[1].trim());
31
+ }
32
+ }
33
+ }
34
+ return criteria;
35
+ }
@@ -0,0 +1,117 @@
1
+ import { withContractCarrierSession } from "./contract-carrier.js";
2
+ import { resolveAcceptedLifecycleTransaction, } from "./repository-ledger/accepted-tail.js";
3
+ export async function reconcileCommittedMutation(plan) {
4
+ const observed = await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "observe-only", signal: plan.signal }, async (session) => ({
5
+ model: session.lifecycle,
6
+ repository: session.repository,
7
+ }));
8
+ const authoritativeHead = observed.model.observedLedgerHead;
9
+ const accepted = findLatestAcceptedTransaction(observed.repository, plan.contractId, observed.model.history.at(-1)?.objectId ?? null);
10
+ if (accepted.status === "refused") {
11
+ return {
12
+ kind: "refused",
13
+ authoritativeHead,
14
+ refusal: {
15
+ code: "OUTSTANDING_REPLAY_UNPROVEN",
16
+ message: `contract ${plan.contractId} recovery transaction is not accepted by the current typed ledger fold`,
17
+ facts: { reason: accepted.reason, transactionObjectId: accepted.transactionObjectId },
18
+ },
19
+ };
20
+ }
21
+ if (accepted.status === "accepted") {
22
+ if (accepted.transaction.verb === "bind" || accepted.transaction.verb === "bootstrap") {
23
+ const replayed = await alignDerivedTransaction(plan, accepted.transaction);
24
+ if (replayed.kind === "refused")
25
+ return replayed;
26
+ return observeOrdinaryCoordinate(plan);
27
+ }
28
+ if (accepted.transaction.verb !== plan.mutation.verb) {
29
+ const replayed = await alignDerivedTransaction(plan, accepted.transaction);
30
+ if (replayed.kind === "refused")
31
+ return replayed;
32
+ return observeOrdinaryCoordinate(plan);
33
+ }
34
+ const transaction = accepted.transaction;
35
+ if (plan.mutation.matchesAccepted && !plan.mutation.matchesAccepted(transaction)) {
36
+ const replayed = await alignDerivedTransaction(plan, transaction);
37
+ if (replayed.kind === "refused")
38
+ return replayed;
39
+ return observeOrdinaryCoordinate(plan);
40
+ }
41
+ return {
42
+ kind: "aligned",
43
+ coordinate: { state: "verb-committed", invokedVerb: plan.mutation.verb, transaction, authoritativeHead },
44
+ };
45
+ }
46
+ if (observed.model.phase === "claimed" || observed.model.phase === "forfeited") {
47
+ return {
48
+ kind: "refused",
49
+ authoritativeHead,
50
+ refusal: { code: "CONTRACT_TERMINAL", message: `contract ${plan.contractId} is terminal (${observed.model.phase})` },
51
+ };
52
+ }
53
+ const bindObjectId = observed.model.history.find(({ entry }) => entry.kind === "bind")?.objectId;
54
+ if (!bindObjectId) {
55
+ return {
56
+ kind: "refused",
57
+ authoritativeHead,
58
+ refusal: { code: "INTERNAL_STATE", message: `contract ${plan.contractId} typed lifecycle has no bind object` },
59
+ };
60
+ }
61
+ return {
62
+ kind: "aligned",
63
+ coordinate: {
64
+ state: "bind-aligned",
65
+ invokedVerb: plan.mutation.verb,
66
+ bindObjectId,
67
+ authoritativeHead,
68
+ },
69
+ };
70
+ }
71
+ async function alignDerivedTransaction(plan, transaction) {
72
+ return withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "mutation", signal: plan.signal }, async (session) => {
73
+ try {
74
+ await session.replayCommitted({
75
+ transactionObjectId: transaction.transactionObjectId,
76
+ ...(transaction.verb === "claim" || transaction.verb === "forfeit" ? { carrier: "retire" } : {}),
77
+ });
78
+ await session.completeReplay(transaction.verb === "claim" || transaction.verb === "forfeit" ? "retire" : "project");
79
+ return { kind: "aligned" };
80
+ }
81
+ catch (error) {
82
+ return {
83
+ kind: "refused",
84
+ authoritativeHead: session.lifecycle.observedLedgerHead,
85
+ refusal: {
86
+ code: "LIFECYCLE_RECONCILIATION_INCOMPLETE",
87
+ message: error instanceof Error ? error.message : `contract ${plan.contractId} derived replay did not complete`,
88
+ },
89
+ };
90
+ }
91
+ });
92
+ }
93
+ async function observeOrdinaryCoordinate(plan) {
94
+ return withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "observe-only", signal: plan.signal }, async (session) => bindAligned(session.lifecycle.history, plan, session.lifecycle.observedLedgerHead));
95
+ }
96
+ function bindAligned(history, plan, authoritativeHead) {
97
+ const bindObjectId = history.find(({ entry }) => entry.kind === "bind")?.objectId;
98
+ if (!bindObjectId) {
99
+ return {
100
+ kind: "refused",
101
+ authoritativeHead,
102
+ refusal: { code: "INTERNAL_STATE", message: "typed lifecycle has no bind object" },
103
+ };
104
+ }
105
+ return { kind: "aligned", coordinate: { state: "bind-aligned", invokedVerb: plan.mutation.verb, bindObjectId, authoritativeHead } };
106
+ }
107
+ function findLatestAcceptedTransaction(read, contractId, finalEntryObjectId) {
108
+ if (finalEntryObjectId === null)
109
+ return { status: "none" };
110
+ const node = read.topology.nodes.find((candidate) => candidate.type === "transaction"
111
+ && candidate.contract === contractId
112
+ && candidate.finalEntryObjectId === finalEntryObjectId);
113
+ if (!node || node.type !== "transaction")
114
+ return { status: "none" };
115
+ const resolved = resolveAcceptedLifecycleTransaction({ read, transactionObjectId: node.objectId, expectedContract: contractId });
116
+ return resolved.status === "accepted" ? { status: "accepted", transaction: resolved } : resolved;
117
+ }
@@ -0,0 +1,377 @@
1
+ import { FlowError } from "../flow-error.js";
2
+ import { withContractCarrierSession, } from "./contract-carrier.js";
3
+ import { cancelledLifecycleFacts, committedLifecycleFacts, containedCommittedLifecycleFacts, precommitLifecycleFacts, refusedLifecycleFacts, } from "./outcome-base.js";
4
+ import { resolveAcceptedLifecycleTransaction } from "./repository-ledger/accepted-tail.js";
5
+ import { reconcileCommittedMutation, } from "./lifecycle-recovery.js";
6
+ function coordinateObject(pins, target) {
7
+ return {
8
+ ledgerHead: pins.ledgerHead,
9
+ targetRef: pins.target.ref,
10
+ targetHead: pins.target.head,
11
+ deliveryRef: pins.delivery.ref,
12
+ deliveryHead: pins.delivery.head,
13
+ carrierTarget: target.status === "available" ? target.path : null,
14
+ };
15
+ }
16
+ function sameCoordinates(admission, session) {
17
+ const left = coordinateObject(admission.pins, admission.target);
18
+ const right = coordinateObject(session.pins, session.target);
19
+ return JSON.stringify(left) === JSON.stringify(right);
20
+ }
21
+ function validateReplayCoordinate(session, coordinate) {
22
+ if (coordinate.state === "bind-aligned") {
23
+ const currentHead = session.lifecycle.observedLedgerHead;
24
+ if (coordinate.authoritativeHead !== currentHead) {
25
+ return {
26
+ code: "LIFECYCLE_REPLAY_COORDINATES_CHANGED",
27
+ message: "repository ledger head changed during lifecycle admission; retry",
28
+ pinned: { authoritativeHead: coordinate.authoritativeHead },
29
+ current: { authoritativeHead: currentHead },
30
+ };
31
+ }
32
+ const bindObjectId = session.lifecycle.history.find(({ entry }) => entry.kind === "bind")?.objectId ?? null;
33
+ return bindObjectId === coordinate.bindObjectId
34
+ ? undefined
35
+ : {
36
+ code: "LIFECYCLE_REPLAY_COORDINATES_CHANGED",
37
+ message: "typed bind coordinate changed after replay classification; retry",
38
+ pinned: { bindObjectId: coordinate.bindObjectId },
39
+ current: { bindObjectId },
40
+ };
41
+ }
42
+ const resolved = resolveAcceptedLifecycleTransaction({
43
+ read: session.repository,
44
+ transactionObjectId: coordinate.transaction.transactionObjectId,
45
+ expectedContract: session.contractId,
46
+ });
47
+ if (resolved.status === "accepted" && resolved.verb === coordinate.invokedVerb)
48
+ return undefined;
49
+ return {
50
+ code: "LIFECYCLE_REPLAY_COORDINATES_CHANGED",
51
+ message: "accepted lifecycle transaction changed after replay classification; retry",
52
+ pinned: {
53
+ transactionObjectId: coordinate.transaction.transactionObjectId,
54
+ verb: coordinate.invokedVerb,
55
+ },
56
+ current: {
57
+ status: resolved.status,
58
+ authoritativeHead: session.lifecycle.observedLedgerHead,
59
+ },
60
+ };
61
+ }
62
+ function surfaceCoordinateRefusal(mutation, refusal) {
63
+ return mutation.coordinateChangeRefusal
64
+ ? { ...refusal, ...mutation.coordinateChangeRefusal }
65
+ : refusal;
66
+ }
67
+ function isCancellation(error) {
68
+ return error instanceof FlowError && error.code === "CARRIER_GATE_CANCELLED";
69
+ }
70
+ function flowRefusal(error) {
71
+ return {
72
+ code: error.code,
73
+ message: error.message,
74
+ ...(error.facts ? { facts: error.facts } : {}),
75
+ };
76
+ }
77
+ function containedCause(error) {
78
+ const source = error;
79
+ return {
80
+ code: typeof source?.code === "string" ? source.code : "LIFECYCLE_EFFECT_FAILED",
81
+ message: typeof source?.message === "string" ? source.message : String(error),
82
+ };
83
+ }
84
+ function journalContainmentCause(session) {
85
+ const row = session.journal.all().find((candidate) => candidate.status !== "succeeded");
86
+ if (!row)
87
+ return undefined;
88
+ if (row.status === "failed")
89
+ return row.cause;
90
+ return {
91
+ code: row.cause.code,
92
+ message: row.cause.message,
93
+ facts: { effect: row.effect, dependency: row.dependency },
94
+ };
95
+ }
96
+ /** Canonical two-admission execution shape for an existing-contract lifecycle verb. */
97
+ export async function runLifecycle(plan) {
98
+ let observedHead = null;
99
+ let admission;
100
+ try {
101
+ const reconciliation = await reconcileCommittedMutation({
102
+ cwd: plan.cwd,
103
+ contractId: plan.contractId,
104
+ mutation: plan.mutation,
105
+ signal: plan.signal,
106
+ });
107
+ if (reconciliation.kind === "refused") {
108
+ return {
109
+ kind: "noncommitted",
110
+ outcome: refusedLifecycleFacts({
111
+ contractId: plan.contractId,
112
+ authoritativeHead: reconciliation.authoritativeHead,
113
+ ...reconciliation.refusal,
114
+ }),
115
+ };
116
+ }
117
+ const attempt = await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "observe-only", signal: plan.signal }, async (session) => {
118
+ observedHead = session.lifecycle.observedLedgerHead;
119
+ const replayRefusal = validateReplayCoordinate(session, reconciliation.coordinate);
120
+ if (replayRefusal) {
121
+ return { kind: "refused", outcome: refusedLifecycleFacts({
122
+ contractId: plan.contractId,
123
+ authoritativeHead: session.lifecycle.observedLedgerHead,
124
+ ...surfaceCoordinateRefusal(plan.mutation, replayRefusal),
125
+ }) };
126
+ }
127
+ let business;
128
+ try {
129
+ business = await plan.admit(session, reconciliation.coordinate);
130
+ }
131
+ catch (error) {
132
+ if (error instanceof FlowError && !isCancellation(error)) {
133
+ return { kind: "refused", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.lifecycle.observedLedgerHead, ...flowRefusal(error) }) };
134
+ }
135
+ throw error;
136
+ }
137
+ return {
138
+ kind: "admitted",
139
+ ledgerHead: session.lifecycle.observedLedgerHead,
140
+ lifecycle: session.lifecycle,
141
+ repository: session.repository,
142
+ pins: session.pins,
143
+ target: session.target,
144
+ carrier: session.initialCarrier,
145
+ replay: reconciliation.coordinate,
146
+ business,
147
+ };
148
+ });
149
+ if (attempt.kind === "refused")
150
+ return { kind: "noncommitted", outcome: attempt.outcome };
151
+ admission = attempt;
152
+ }
153
+ catch (error) {
154
+ if (isCancellation(error))
155
+ return { kind: "noncommitted", outcome: cancelledLifecycleFacts(plan.contractId, observedHead) };
156
+ throw error;
157
+ }
158
+ let prepared;
159
+ try {
160
+ if (plan.signal?.aborted)
161
+ return { kind: "noncommitted", outcome: cancelledLifecycleFacts(plan.contractId, observedHead) };
162
+ prepared = await plan.prepare(admission);
163
+ if (plan.signal?.aborted)
164
+ return { kind: "noncommitted", outcome: cancelledLifecycleFacts(plan.contractId, observedHead) };
165
+ }
166
+ catch (error) {
167
+ if (isCancellation(error) || plan.signal?.aborted)
168
+ return { kind: "noncommitted", outcome: cancelledLifecycleFacts(plan.contractId, observedHead) };
169
+ if (error instanceof FlowError) {
170
+ return { kind: "noncommitted", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: admission.ledgerHead, ...flowRefusal(error) }) };
171
+ }
172
+ throw error;
173
+ }
174
+ try {
175
+ return await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "mutation", signal: plan.signal, ...(plan.renewDeliveryProjector ? { renewDeliveryProjector: plan.renewDeliveryProjector } : {}), ...(plan.terminalWorkspaceCleanup ? { terminalWorkspaceCleanup: plan.terminalWorkspaceCleanup } : {}) }, async (session) => {
176
+ observedHead = session.lifecycle.observedLedgerHead;
177
+ const replayRefusal = validateReplayCoordinate(session, admission.replay);
178
+ if (replayRefusal) {
179
+ return { kind: "noncommitted", outcome: refusedLifecycleFacts({
180
+ contractId: plan.contractId,
181
+ authoritativeHead: session.lifecycle.observedLedgerHead,
182
+ ...surfaceCoordinateRefusal(plan.mutation, replayRefusal),
183
+ }) };
184
+ }
185
+ if (!sameCoordinates(admission, session)) {
186
+ return { kind: "noncommitted", outcome: refusedLifecycleFacts({
187
+ contractId: plan.contractId,
188
+ authoritativeHead: session.lifecycle.observedLedgerHead,
189
+ code: "LIFECYCLE_COORDINATES_CHANGED",
190
+ message: "contract coordinates changed during lifecycle preparation",
191
+ pinned: coordinateObject(admission.pins, admission.target),
192
+ current: coordinateObject(session.pins, session.target),
193
+ }) };
194
+ }
195
+ let refusal;
196
+ try {
197
+ refusal = await plan.revalidate({ session, admission, prepared });
198
+ }
199
+ catch (error) {
200
+ if (!(error instanceof FlowError) || isCancellation(error))
201
+ throw error;
202
+ refusal = flowRefusal(error);
203
+ }
204
+ if (refusal) {
205
+ return { kind: "noncommitted", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.lifecycle.observedLedgerHead, ...refusal }) };
206
+ }
207
+ const effectSession = Object.freeze({
208
+ stableRoot: session.stableRoot,
209
+ lifecycle: session.lifecycle,
210
+ pins: session.pins,
211
+ completeReplay: (carrier) => session.completeReplay(carrier),
212
+ });
213
+ const context = {
214
+ session: effectSession,
215
+ journal: session.journal,
216
+ beginEffects: () => session.beginEffects(),
217
+ publishAuthoritative: (authoritativeTransaction) => {
218
+ const submittedTransaction = authoritativeTransaction;
219
+ if (submittedTransaction.verb !== plan.mutation.verb) {
220
+ throw new FlowError("INTERNAL_STATE", `${plan.mutation.verb} lifecycle effect cannot publish ${submittedTransaction.verb} authoritative transaction`);
221
+ }
222
+ return session.publishAuthoritative(submittedTransaction);
223
+ },
224
+ replayCommitted: (input) => session.replayCommitted(input),
225
+ };
226
+ let result;
227
+ try {
228
+ result = await plan.effect({ context, admission, prepared });
229
+ if (result.kind === "committed") {
230
+ const replayIncomplete = session.journal.all().some((row) => row.status !== "succeeded");
231
+ if (!replayIncomplete) {
232
+ await session.completeReplay(plan.mutation.verb === "claim" || plan.mutation.verb === "forfeit" ? "retire" : "project");
233
+ result = { ...result, commit: session.committedResult() };
234
+ }
235
+ }
236
+ }
237
+ catch (error) {
238
+ if (!session.effectsStarted()) {
239
+ if (error instanceof FlowError && !isCancellation(error)) {
240
+ return { kind: "noncommitted", outcome: refusedLifecycleFacts({
241
+ contractId: plan.contractId,
242
+ authoritativeHead: session.lifecycle.observedLedgerHead,
243
+ ...flowRefusal(error),
244
+ }) };
245
+ }
246
+ throw error;
247
+ }
248
+ await session.journal.run({
249
+ effect: "unhandled-effect-boundary",
250
+ run: () => { throw error; },
251
+ });
252
+ const committed = session.committedResult();
253
+ if (committed) {
254
+ const ledgerCas = session.journal.rows("prerequisite").find((row) => row.effect === "ledger-cas" && row.status === "succeeded");
255
+ const entryKind = ledgerCas?.status === "succeeded" && typeof ledgerCas.receipt?.entryKind === "string"
256
+ ? ledgerCas.receipt.entryKind
257
+ : "unknown";
258
+ return {
259
+ kind: "contained",
260
+ outcome: containedCommittedLifecycleFacts({
261
+ head: committed.head,
262
+ entryKind,
263
+ contractId: plan.contractId,
264
+ carrier: committed.carrier,
265
+ cancellationObserved: session.cancellationObserved(),
266
+ prerequisites: session.journal.rows("prerequisite"),
267
+ downstream: session.journal.rows("downstream"),
268
+ cause: containedCause(error),
269
+ }),
270
+ carrier: committed.carrier,
271
+ };
272
+ }
273
+ return {
274
+ kind: "noncommitted",
275
+ outcome: precommitLifecycleFacts({
276
+ contractId: plan.contractId,
277
+ authoritativeHead: session.lifecycle.observedLedgerHead,
278
+ journal: session.journal,
279
+ cancellationObserved: session.cancellationObserved(),
280
+ }),
281
+ };
282
+ }
283
+ if (result.kind !== "committed") {
284
+ const committed = session.committedResult();
285
+ if (committed) {
286
+ const ledgerCas = session.journal.all().find((row) => row.effect === "ledger-cas" && row.status === "succeeded");
287
+ const entryKind = ledgerCas?.status === "succeeded" && typeof ledgerCas.receipt?.entryKind === "string"
288
+ ? ledgerCas.receipt.entryKind
289
+ : "unknown";
290
+ const cause = journalContainmentCause(session) ?? {
291
+ code: result.kind === "refused-before-effects" ? result.refusal.code : "LIFECYCLE_RECONCILIATION_INCOMPLETE",
292
+ message: result.kind === "refused-before-effects" ? result.refusal.message : "committed lifecycle reconciliation did not complete",
293
+ };
294
+ return {
295
+ kind: "contained",
296
+ outcome: containedCommittedLifecycleFacts({
297
+ head: committed.head,
298
+ entryKind,
299
+ contractId: plan.contractId,
300
+ carrier: committed.carrier,
301
+ cancellationObserved: session.cancellationObserved(),
302
+ prerequisites: session.journal.rows("prerequisite"),
303
+ downstream: session.journal.rows("downstream"),
304
+ cause,
305
+ }),
306
+ carrier: committed.carrier,
307
+ };
308
+ }
309
+ }
310
+ if (result.kind === "precommit-effects-only") {
311
+ return {
312
+ kind: "noncommitted",
313
+ outcome: precommitLifecycleFacts({
314
+ contractId: plan.contractId,
315
+ authoritativeHead: result.authoritativeHead,
316
+ journal: session.journal,
317
+ cancellationObserved: session.cancellationObserved(),
318
+ }),
319
+ };
320
+ }
321
+ if (result.kind === "refused-before-effects") {
322
+ if (session.effectsStarted()) {
323
+ return {
324
+ kind: "noncommitted",
325
+ outcome: precommitLifecycleFacts({
326
+ contractId: plan.contractId,
327
+ authoritativeHead: result.authoritativeHead,
328
+ journal: session.journal,
329
+ cancellationObserved: session.cancellationObserved(),
330
+ }),
331
+ };
332
+ }
333
+ return { kind: "noncommitted", outcome: refusedLifecycleFacts({
334
+ contractId: plan.contractId,
335
+ authoritativeHead: result.authoritativeHead,
336
+ ...result.refusal,
337
+ }) };
338
+ }
339
+ const degraded = journalContainmentCause(session);
340
+ if (degraded) {
341
+ return {
342
+ kind: "contained",
343
+ outcome: containedCommittedLifecycleFacts({
344
+ head: result.commit.head,
345
+ entryKind: result.entryKind,
346
+ contractId: plan.contractId,
347
+ carrier: result.commit.carrier,
348
+ cancellationObserved: result.commit.cancellationObserved,
349
+ prerequisites: session.journal.rows("prerequisite"),
350
+ downstream: session.journal.rows("downstream"),
351
+ cause: degraded,
352
+ }),
353
+ carrier: result.commit.carrier,
354
+ };
355
+ }
356
+ return {
357
+ kind: "complete",
358
+ outcome: committedLifecycleFacts({
359
+ head: result.commit.head,
360
+ entryKind: result.entryKind,
361
+ contractId: plan.contractId,
362
+ carrier: result.commit.carrier,
363
+ cancellationObserved: result.commit.cancellationObserved,
364
+ prerequisites: session.journal.rows("prerequisite"),
365
+ downstream: session.journal.rows("downstream"),
366
+ }),
367
+ payload: result.value,
368
+ carrier: result.commit.carrier,
369
+ };
370
+ });
371
+ }
372
+ catch (error) {
373
+ if (isCancellation(error))
374
+ return { kind: "noncommitted", outcome: cancelledLifecycleFacts(plan.contractId, observedHead) };
375
+ throw error;
376
+ }
377
+ }
package/build/core/log.js CHANGED
@@ -1,24 +1,50 @@
1
1
  import { FlowError } from "../flow-error.js";
2
- import { readLedger } from "./ledger.js";
3
- import { renderContractView } from "./contract-view.js";
4
- import { deriveContractState } from "./status/lifecycle.js";
2
+ import { getConfig } from "../config/env.js";
3
+ import { renderCurrentContractView } from "./contract-view.js";
4
+ import { currentLifecycleState, lookupCurrentContract, readRepositoryLifecycle, } from "./repository-ledger/read-model.js";
5
+ function amendmentWarning(model) {
6
+ const state = currentLifecycleState(model);
7
+ if (state.state === "claimed" || state.state === "forfeited")
8
+ return undefined;
9
+ const amendmentCount = model.history.filter((row) => row.entry.kind === "amend").length;
10
+ const threshold = getConfig().amendmentWarning.threshold;
11
+ return amendmentCount > threshold ? { amendmentCount, threshold } : undefined;
12
+ }
13
+ function unavailableLogError(contractId, outcome) {
14
+ return new FlowError("INTERNAL_STATE", `contract ${contractId} is unavailable because repository lifecycle evidence is ${outcome.status}`, {
15
+ facts: {
16
+ kind: "repository_lifecycle_disease",
17
+ contractId,
18
+ status: outcome.status,
19
+ diagnostics: outcome.diagnostics.map((diagnostic) => ({
20
+ objectId: diagnostic.objectId,
21
+ code: diagnostic.code,
22
+ detail: diagnostic.detail,
23
+ ...(diagnostic.contract === undefined ? {} : { contract: diagnostic.contract }),
24
+ })),
25
+ },
26
+ });
27
+ }
5
28
  export async function readContractLog(input) {
6
- const ledger = await readLedger(input.cwd, input.contractId);
7
- if (!ledger) {
29
+ const read = await readRepositoryLifecycle(input.cwd);
30
+ const outcome = lookupCurrentContract(read, input.contractId);
31
+ if (outcome.status === "absent") {
8
32
  throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found`);
9
33
  }
34
+ if (outcome.status !== "healthy")
35
+ throw unavailableLogError(input.contractId, outcome);
36
+ const model = outcome.model;
37
+ const warning = amendmentWarning(model);
10
38
  return {
11
39
  contractId: input.contractId,
12
- state: deriveContractState(ledger.entries),
13
- entries: ledger.entries.map((entry, index) => ({
40
+ state: currentLifecycleState(model),
41
+ entries: model.history.map(({ entry }, index) => ({
14
42
  index: index + 1,
15
43
  kind: entry.kind,
16
44
  at: entry.at,
17
45
  actor: entry.actor,
18
46
  })),
19
- markdown: renderContractView({
20
- id: input.contractId,
21
- entries: ledger.entries,
22
- }),
47
+ markdown: renderCurrentContractView(model),
48
+ ...(warning ? { amendmentWarning: warning } : {}),
23
49
  };
24
50
  }