@astrosheep/keiyaku 4.0.6 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/akuma/archetype.js +8 -1
- package/build/src/akuma/provider-recipe.d.ts +2 -0
- package/build/src/akuma/provider-recipe.js +10 -0
- package/build/src/akuma/providers/acp/config.d.ts +2 -0
- package/build/src/akuma/providers/acp/config.js +9 -1
- package/build/src/akuma/providers/acp/core.d.ts +5 -2
- package/build/src/akuma/providers/acp/core.js +29 -16
- package/build/src/akuma/providers/acp/index.js +8 -2
- package/build/src/akuma/providers/claude/index.d.ts +1 -1
- package/build/src/akuma/providers/claude/index.js +9 -5
- package/build/src/akuma/providers/codex-app-server/index.js +4 -1
- package/build/src/akuma/providers/grok-build/index.js +15 -1
- package/build/src/akuma/providers/index.js +1 -1
- package/build/src/akuma/providers/opencode-sdk/index.js +3 -0
- package/build/src/akuma/providers/pi/index.js +3 -1
- package/build/src/body/amend.js +0 -9
- package/build/src/body/region.js +2 -2
- package/build/src/cli/accepted.js +2 -0
- package/build/src/cli/commands/contract.d.ts +5 -3
- package/build/src/cli/commands/contract.js +23 -13
- package/build/src/cli/commands/task-invoke.d.ts +4 -1
- package/build/src/cli/commands/task-invoke.js +6 -3
- package/build/src/cli/commands/task.js +10 -2
- package/build/src/cli/parse.js +2 -2
- package/build/src/cli/render/akuma.js +42 -11
- package/build/src/cli/render/catalog.js +5 -1
- package/build/src/cli/render/contract.js +25 -3
- package/build/src/cli/render/kanshi.js +142 -94
- package/build/src/cli/render/receipt.d.ts +1 -0
- package/build/src/cli/render/receipt.js +18 -14
- package/build/src/cli/render/refusal.js +6 -0
- package/build/src/cli/render/task.js +63 -11
- package/build/src/cli/result.d.ts +8 -1
- package/build/src/contract-worktree.d.ts +0 -2
- package/build/src/contract-worktree.js +13 -24
- package/build/src/git/nuke.js +1 -6
- package/build/src/git/reconcile.d.ts +0 -2
- package/build/src/git/reconcile.js +2 -33
- package/build/src/git/tender.d.ts +13 -2
- package/build/src/git/tender.js +43 -4
- package/build/src/kanshi/read.js +32 -12
- package/build/src/kanshi/report.d.ts +2 -1
- package/build/src/library/continuation.d.ts +26 -0
- package/build/src/library/continuation.js +78 -0
- package/build/src/library/contract.d.ts +9 -5
- package/build/src/library/contract.js +27 -2
- package/build/src/library/delivery.d.ts +18 -13
- package/build/src/library/keiyaku.d.ts +1 -1
- package/build/src/library/mutation.js +11 -4
- package/build/src/protocol/deliver.d.ts +12 -2
- package/build/src/protocol/deliver.js +52 -19
- package/build/src/protocol/operations.d.ts +7 -2
- package/build/src/settlement/fence.d.ts +0 -2
- package/build/src/settlement/fence.js +0 -24
- package/build/src/task/board.d.ts +21 -2
- package/build/src/task/board.js +25 -12
- package/build/src/task/identity.js +14 -4
- package/build/src/task/index.d.ts +1 -0
- package/build/src/task/index.js +7 -1
- package/build/src/task/operations.d.ts +2 -1
- package/build/src/task/operations.js +15 -4
- package/build/src/task/query.js +2 -2
- package/build/src/task/store.d.ts +2 -0
- package/build/src/task/store.js +12 -83
- package/package.json +1 -1
|
@@ -25,10 +25,10 @@ function priorityText(priority) {
|
|
|
25
25
|
return priority === null ? "P?" : `P${priority}`;
|
|
26
26
|
}
|
|
27
27
|
function scanUnit(id, priority, word) {
|
|
28
|
-
return `${markFor(word)} ${id} · ${priorityText(priority)} ${word}`;
|
|
28
|
+
return word === undefined ? `${markFor("ready")} ${id} · ${priorityText(priority)}` : `${markFor(word)} ${id} · ${priorityText(priority)} ${word}`;
|
|
29
29
|
}
|
|
30
30
|
function entityLines(entity, columns, indent = "") {
|
|
31
|
-
const scan = `${indent}${scanUnit(entity.id, entity.priority, entity.word)}`;
|
|
31
|
+
const scan = `${indent}${scanUnit(entity.id, entity.priority, entity.word)}${entity.facts === undefined ? "" : ` · ${entity.facts}`}`;
|
|
32
32
|
if (entity.title === null || entity.title.length === 0)
|
|
33
33
|
return [scan];
|
|
34
34
|
const title = safeText(entity.title);
|
|
@@ -37,9 +37,6 @@ function entityLines(entity, columns, indent = "") {
|
|
|
37
37
|
return [inline];
|
|
38
38
|
return [`${scan} —`, ...renderTextBlock(title, `${indent} `, columns)];
|
|
39
39
|
}
|
|
40
|
-
function listEntity(item) {
|
|
41
|
-
return { id: item.id, priority: item.priority, word: item.disposition, title: item.title };
|
|
42
|
-
}
|
|
43
40
|
function stateEntity(task) {
|
|
44
41
|
return { id: task.id, priority: "priority" in task && task.priority !== undefined ? task.priority : null, word: task.state, title: task.title };
|
|
45
42
|
}
|
|
@@ -79,23 +76,71 @@ function pageHeading(view, page) {
|
|
|
79
76
|
return `${view} ${page.returned} of ${page.total} · limit ${page.returned}`;
|
|
80
77
|
return `${view} ${page.returned}`;
|
|
81
78
|
}
|
|
82
|
-
function
|
|
83
|
-
const
|
|
79
|
+
function updatedAge(updatedAt) {
|
|
80
|
+
const elapsed = Math.max(0, performance.timeOrigin + performance.now() - Date.parse(updatedAt));
|
|
81
|
+
const minutes = Math.floor(elapsed / 60_000);
|
|
82
|
+
if (minutes < 60)
|
|
83
|
+
return `${minutes}m`;
|
|
84
|
+
const hours = Math.floor(minutes / 60);
|
|
85
|
+
if (hours < 24)
|
|
86
|
+
return `${hours}h`;
|
|
87
|
+
return `${Math.floor(hours / 24)}d`;
|
|
88
|
+
}
|
|
89
|
+
function compactFacts(item) {
|
|
90
|
+
return [
|
|
91
|
+
`updated ${updatedAge(item.updatedAt)}`,
|
|
92
|
+
...(item.bodyPresent ? [] : ["no body"]),
|
|
93
|
+
...(item.children === undefined ? [] : [`children ${item.children.live}/${item.children.total} live`]),
|
|
94
|
+
].join(" · ");
|
|
95
|
+
}
|
|
96
|
+
function listEntity(item, omitDisposition) {
|
|
97
|
+
return {
|
|
98
|
+
id: item.id,
|
|
99
|
+
priority: item.priority,
|
|
100
|
+
...(omitDisposition ? {} : { word: item.disposition }),
|
|
101
|
+
facts: compactFacts(item),
|
|
102
|
+
title: item.title,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function renderListRow(item, columns, omitDisposition) {
|
|
106
|
+
const lines = [...entityLines(listEntity(item, omitDisposition), columns)];
|
|
84
107
|
if ("blockers" in item) {
|
|
85
108
|
for (const blocker of item.blockers)
|
|
86
109
|
lines.push(` needs ${blocker.id} · ${blocker.state}`);
|
|
87
110
|
}
|
|
88
111
|
return lines;
|
|
89
112
|
}
|
|
113
|
+
function shellQuote(value) { return `'${value.replaceAll("'", "'\\''")}'`; }
|
|
114
|
+
function recoveryCommand(command, total) {
|
|
115
|
+
const parts = ["keiyaku", "task", command.action];
|
|
116
|
+
if (command.action === "ls") {
|
|
117
|
+
if (command.flags.closed === true)
|
|
118
|
+
parts.push("--closed");
|
|
119
|
+
if (command.flags.all === true)
|
|
120
|
+
parts.push("--all");
|
|
121
|
+
}
|
|
122
|
+
if (command.flags.world === true)
|
|
123
|
+
parts.push("--world");
|
|
124
|
+
if ((command.action === "ready" || command.action === "blocked") && typeof command.flags.parent === "string") {
|
|
125
|
+
parts.push("--parent", command.flags.parent);
|
|
126
|
+
}
|
|
127
|
+
if (command.action === "query" && typeof command.flags.where === "string")
|
|
128
|
+
parts.push("--where", shellQuote(command.flags.where));
|
|
129
|
+
if (command.action === "query" && typeof command.flags.sort === "string")
|
|
130
|
+
parts.push("--sort", command.flags.sort);
|
|
131
|
+
parts.push("--limit", String(total));
|
|
132
|
+
return parts.join(" ");
|
|
133
|
+
}
|
|
90
134
|
function renderRows(command, result, columns) {
|
|
91
135
|
if (result.kind !== "accepted")
|
|
92
136
|
return renderFailure(command.action, result, columns);
|
|
93
137
|
const view = command.action === "ls" ? "tasks" : command.action;
|
|
94
|
-
|
|
138
|
+
const footer = result.value.truncated
|
|
139
|
+
? ["", ` + ${result.value.total - result.value.returned} more not shown`, ` ${recoveryCommand(command, result.value.total)}`]
|
|
140
|
+
: [];
|
|
141
|
+
return [pageHeading(view, result.value), ...result.value.rows.flatMap((item) => renderListRow(item, columns, command.action === "ready")), ...footer].join("\n");
|
|
95
142
|
}
|
|
96
|
-
function
|
|
97
|
-
if ("kind" in result)
|
|
98
|
-
return result.kind === "accepted" ? entityLines(stateEntity(result.value), columns).join("\n") : renderFailure("show", result, columns);
|
|
143
|
+
function renderShowDetail(result, columns) {
|
|
99
144
|
const task = result.task;
|
|
100
145
|
const lines = [
|
|
101
146
|
...entityLines(stateEntity(task), columns),
|
|
@@ -124,6 +169,13 @@ function renderShow(result, columns) {
|
|
|
124
169
|
receiptPayload(lines, "body", task.body);
|
|
125
170
|
return lines.join("\n");
|
|
126
171
|
}
|
|
172
|
+
function renderShow(result, columns) {
|
|
173
|
+
if (Array.isArray(result))
|
|
174
|
+
return result.map((detail) => renderShowDetail(detail, columns)).join("\n\n");
|
|
175
|
+
if ("kind" in result)
|
|
176
|
+
return renderFailure("show", result, columns);
|
|
177
|
+
return renderShowDetail(result, columns);
|
|
178
|
+
}
|
|
127
179
|
function treeLines(node, columns, depth = 0) {
|
|
128
180
|
const indent = " ".repeat(depth);
|
|
129
181
|
if (node.cycle === true)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuditReport, ContractHistory, ContractId, Delivery, Fact, IntegrationConflictMaterialized, MutationResult, PlacementStop, ReconcileReport, Review, VerificationReuse, VerificationStop } from "../index.js";
|
|
1
|
+
import type { AuditReport, ContractHistory, ContractId, ContinuationReport, Delivery, Fact, IntegrationConflictMaterialized, MutationResult, PlacementStop, ReconcileReport, Review, VerificationReuse, VerificationStop } from "../index.js";
|
|
2
2
|
import type { KanshiReport } from "../kanshi/index.js";
|
|
3
3
|
import type { RegionRead, Section } from "../kanshi/index.js";
|
|
4
4
|
import type { Catalog } from "../index.js";
|
|
@@ -45,6 +45,7 @@ export type AcceptedBindResult = AcceptedEnvelope & Readonly<{
|
|
|
45
45
|
verificationReuse?: never;
|
|
46
46
|
verificationSummary?: never;
|
|
47
47
|
placement?: never;
|
|
48
|
+
continuation?: never;
|
|
48
49
|
cleanup?: never;
|
|
49
50
|
leak?: never;
|
|
50
51
|
report?: never;
|
|
@@ -63,6 +64,7 @@ export type AcceptedAmendResult = AcceptedEnvelope & Readonly<{
|
|
|
63
64
|
verificationReuse?: never;
|
|
64
65
|
verificationSummary?: never;
|
|
65
66
|
placement?: never;
|
|
67
|
+
continuation?: never;
|
|
66
68
|
cleanup?: never;
|
|
67
69
|
leak?: never;
|
|
68
70
|
report?: never;
|
|
@@ -80,6 +82,7 @@ export type AcceptedDeliverResult = AcceptedEnvelope & Readonly<{
|
|
|
80
82
|
verificationReuse?: VerificationReuse;
|
|
81
83
|
verificationSummary?: string;
|
|
82
84
|
placement?: PlacementStop;
|
|
85
|
+
continuation?: ContinuationReport;
|
|
83
86
|
cleanup?: MutationResult<unknown>["cleanup"];
|
|
84
87
|
leak?: MutationResult<unknown>["leak"];
|
|
85
88
|
target?: never;
|
|
@@ -101,6 +104,7 @@ export type AcceptedReviewResult = AcceptedEnvelope & Readonly<{
|
|
|
101
104
|
verificationReuse?: VerificationReuse;
|
|
102
105
|
verificationSummary?: string;
|
|
103
106
|
placement?: PlacementStop;
|
|
107
|
+
continuation?: ContinuationReport;
|
|
104
108
|
workspace?: Review["workspace"];
|
|
105
109
|
cleanup?: MutationResult<unknown>["cleanup"];
|
|
106
110
|
leak?: MutationResult<unknown>["leak"];
|
|
@@ -124,6 +128,7 @@ export type AcceptedArcResult = AcceptedEnvelope & Readonly<{
|
|
|
124
128
|
verificationReuse?: never;
|
|
125
129
|
verificationSummary?: never;
|
|
126
130
|
placement?: never;
|
|
131
|
+
continuation?: never;
|
|
127
132
|
cleanup?: never;
|
|
128
133
|
leak?: never;
|
|
129
134
|
overlaps?: never;
|
|
@@ -144,6 +149,7 @@ export type AcceptedAbandonResult = AcceptedEnvelope & Readonly<{
|
|
|
144
149
|
verificationReuse?: never;
|
|
145
150
|
verificationSummary?: never;
|
|
146
151
|
placement?: never;
|
|
152
|
+
continuation?: never;
|
|
147
153
|
cleanup?: never;
|
|
148
154
|
leak?: never;
|
|
149
155
|
overlaps?: never;
|
|
@@ -166,6 +172,7 @@ export type AcceptedAuditResult = AcceptedEnvelope & Readonly<{
|
|
|
166
172
|
verificationReuse?: never;
|
|
167
173
|
verificationSummary?: never;
|
|
168
174
|
placement?: never;
|
|
175
|
+
continuation?: never;
|
|
169
176
|
overlaps?: never;
|
|
170
177
|
overlapFailure?: never;
|
|
171
178
|
workspace?: never;
|
|
@@ -54,6 +54,4 @@ export declare function withContractWorktreeAppointment<T>(repository: GitReposi
|
|
|
54
54
|
export declare function releaseContractWorktree(repository: GitRepository, contract: ContractId): Promise<void>;
|
|
55
55
|
/** Remove matching here appointments and their exact generated support from registered worktrees. */
|
|
56
56
|
export declare function nukeHereAppointments(repository: GitRepository): Promise<void>;
|
|
57
|
-
/** Validate the existing Contract-worktree coordination lock before releasing it. */
|
|
58
|
-
export declare function nukeContractWorktreeLock(repository: GitRepository): Promise<void>;
|
|
59
57
|
export declare function projectContractWorktree(repository: GitRepository, state: ContractState | null, register?: PlaceRegister, onConflict?: (diagnostic: string) => never): Promise<ContractWorktreeResult>;
|
|
@@ -30,7 +30,14 @@ const FULFILLMENT = [
|
|
|
30
30
|
"For work requiring three or more steps, prefer `keiyaku task -C <worktree>` "
|
|
31
31
|
+ "to organize and manage Tasks.",
|
|
32
32
|
"Promptly update progress for Tasks already present in the current worktree.",
|
|
33
|
-
"Leave lifecycle decisions to the caller; report the candidate, verification performed, and any unmet term.",
|
|
33
|
+
"Leave lifecycle decisions to the caller; report the candidate, verification performed, and any unmet term.",
|
|
34
|
+
"Deliver from this worktree. A clean worktree delivers HEAD; uncommitted work",
|
|
35
|
+
"needs `deliver --include-dirty`, which captures the final non-ignored tree and",
|
|
36
|
+
"stages or commits nothing. If deliver reports a conflict, run",
|
|
37
|
+
"`deliver --materialize-conflict`, resolve the conflicted files, stage them,",
|
|
38
|
+
"and deliver again; while the merge stays uncommitted that continuation uses",
|
|
39
|
+
"`--include-dirty`. Unresolved paths always refuse.",
|
|
40
|
+
"",
|
|
34
41
|
"### Reviewer", "",
|
|
35
42
|
"Review this Contract worktree against the complete Contract.",
|
|
36
43
|
"Review the complete current worktree snapshot, not a named candidate commit.",
|
|
@@ -177,30 +184,12 @@ async function removeOwnedHereSupport(worktree) {
|
|
|
177
184
|
export async function nukeHereAppointments(repository) {
|
|
178
185
|
for (const registered of await registeredWorktrees(repository)) {
|
|
179
186
|
const scoped = { ...repository, effectiveCwd: registered.path };
|
|
180
|
-
await
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/** Validate the existing Contract-worktree coordination lock before releasing it. */
|
|
190
|
-
export async function nukeContractWorktreeLock(repository) {
|
|
191
|
-
const path = join(repository.commonDirectory, "keiyaku", "contract-worktree.sqlite");
|
|
192
|
-
try {
|
|
193
|
-
const value = await lstat(path);
|
|
194
|
-
if (!value.isFile() || value.isSymbolicLink())
|
|
195
|
-
throw new Error(`Contract worktree lock custody is not a regular file: ${path}`);
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
if (error.code === "ENOENT")
|
|
199
|
-
return;
|
|
200
|
-
throw error;
|
|
187
|
+
const appointment = await readContractAppointment(scoped);
|
|
188
|
+
if (appointment.kind === "appointed") {
|
|
189
|
+
await unlink(appointment.path);
|
|
190
|
+
await removeOwnedHereSupport(registered.path);
|
|
191
|
+
}
|
|
201
192
|
}
|
|
202
|
-
const held = await acquireSqliteTransactionLock({ path, mode: "immediate" });
|
|
203
|
-
held.close();
|
|
204
193
|
}
|
|
205
194
|
async function repair(repository, worktree, relativePath, bytes) {
|
|
206
195
|
const path = join(worktree, relativePath);
|
package/build/src/git/nuke.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
|
-
import {
|
|
3
|
-
import { nukeSettlementFences } from "../settlement/fence.js";
|
|
2
|
+
import { nukeHereAppointments } from "../contract-worktree.js";
|
|
4
3
|
import { nukeEmptyPlaceAuthority, readPlaceRegister, releaseManagedWorktrees, } from "../workspace-place.js";
|
|
5
4
|
import { nukeWorktreeHookResidue } from "./hooks.js";
|
|
6
|
-
import { nukeReconcileLocks } from "./reconcile.js";
|
|
7
5
|
import { runGit } from "./process.js";
|
|
8
6
|
import { NoGitWorldError, registeredWorktrees, repositoryAt, worktreeGitDirectory } from "./repository.js";
|
|
9
7
|
import { worktreePath } from "./workspace.js";
|
|
@@ -86,9 +84,6 @@ export async function nukeGit(world) {
|
|
|
86
84
|
}
|
|
87
85
|
await nukeHereAppointments(repository);
|
|
88
86
|
await nukeEmptyPlaceAuthority(repository);
|
|
89
|
-
await nukeContractWorktreeLock(repository);
|
|
90
|
-
await nukeReconcileLocks(repository);
|
|
91
|
-
await nukeSettlementFences(repository);
|
|
92
87
|
await removeOwnedRefs(repository, custody.refs);
|
|
93
88
|
if (custody.state) {
|
|
94
89
|
const oid = (await runGit(repository, ["rev-parse", "--verify", "refs/heads/keiyaku-state"])).toString("utf8").trim();
|
|
@@ -64,6 +64,4 @@ type ReconcileBatchOptions = Readonly<{
|
|
|
64
64
|
}>;
|
|
65
65
|
/** Reconcile each discovered Contract through its own serialized, fresh observation. */
|
|
66
66
|
export declare function reconcileBatch(repository: GitRepository, channel: GitDecodeChannel, contracts: Iterable<ContractId>, options: ReconcileBatchOptions): Promise<readonly ReconcileBatchItem[]>;
|
|
67
|
-
/** Remove only validated Contract reconciliation lock residue. */
|
|
68
|
-
export declare function nukeReconcileLocks(repository: GitRepository): Promise<void>;
|
|
69
67
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { access, mkdir
|
|
2
|
-
import { dirname, join
|
|
1
|
+
import { access, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
3
|
import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
4
4
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
5
5
|
import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, isKeiyakuOwnedRef, readRef, registeredWorktreePaths, writeCommit, worktreeGitDirectory, } from "./repository.js";
|
|
@@ -466,34 +466,3 @@ export async function reconcileBatch(repository, channel, contracts, options) {
|
|
|
466
466
|
}
|
|
467
467
|
return items;
|
|
468
468
|
}
|
|
469
|
-
/** Remove only validated Contract reconciliation lock residue. */
|
|
470
|
-
export async function nukeReconcileLocks(repository) {
|
|
471
|
-
const root = join(commonGitDirectory(repository), "keiyaku", "locks", "reconcile");
|
|
472
|
-
let first;
|
|
473
|
-
try {
|
|
474
|
-
first = await readdir(root, { withFileTypes: true });
|
|
475
|
-
}
|
|
476
|
-
catch (error) {
|
|
477
|
-
if (error.code === "ENOENT")
|
|
478
|
-
return;
|
|
479
|
-
throw error;
|
|
480
|
-
}
|
|
481
|
-
let paths = [];
|
|
482
|
-
for (const directory of first) {
|
|
483
|
-
if (!/^[0-9a-f]{2}$/u.test(directory.name) || !directory.isDirectory() || directory.isSymbolicLink()) {
|
|
484
|
-
throw new Error(`reconciliation lock custody is invalid: ${resolve(root, directory.name)}`);
|
|
485
|
-
}
|
|
486
|
-
const nested = resolve(root, directory.name);
|
|
487
|
-
const files = await readdir(nested, { withFileTypes: true });
|
|
488
|
-
for (const file of files) {
|
|
489
|
-
if (!/^[0-9a-f]{62}\.sqlite$/u.test(file.name) || !file.isFile() || file.isSymbolicLink()) {
|
|
490
|
-
throw new Error(`reconciliation lock custody is invalid: ${resolve(nested, file.name)}`);
|
|
491
|
-
}
|
|
492
|
-
paths.push(resolve(nested, file.name));
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
for (const path of paths) {
|
|
496
|
-
const held = await acquireSqliteTransactionLock({ path, mode: "immediate" });
|
|
497
|
-
held.close();
|
|
498
|
-
}
|
|
499
|
-
}
|
|
@@ -8,11 +8,18 @@ export type TenderCaptureCoordinates = Readonly<{
|
|
|
8
8
|
coordinates: ContractCoordinates;
|
|
9
9
|
place?: string;
|
|
10
10
|
workspacePath?: string;
|
|
11
|
+
captureMergeState?: boolean;
|
|
12
|
+
rejectUnmerged?: boolean;
|
|
11
13
|
}>;
|
|
12
|
-
export type
|
|
14
|
+
export type WorktreeMissingRefusal = Readonly<{
|
|
13
15
|
kind: "worktree-missing";
|
|
14
16
|
contractId: ContractId;
|
|
15
17
|
}>;
|
|
18
|
+
export type TenderCaptureRefusal = WorktreeMissingRefusal | Readonly<{
|
|
19
|
+
kind: "unmerged-paths";
|
|
20
|
+
contractId: ContractId;
|
|
21
|
+
paths: readonly string[];
|
|
22
|
+
}>;
|
|
16
23
|
export type WorkspaceDirtyDelta = Readonly<{
|
|
17
24
|
staged: readonly string[];
|
|
18
25
|
unstaged: readonly string[];
|
|
@@ -35,12 +42,16 @@ export type DirtyWorkspaceRefusal = Readonly<{
|
|
|
35
42
|
export type TenderCapture = Readonly<{
|
|
36
43
|
tree: GitObjectId;
|
|
37
44
|
head: SnapshotId;
|
|
45
|
+
mergeHead?: SnapshotId;
|
|
38
46
|
at: string;
|
|
39
47
|
dirty: boolean;
|
|
40
48
|
changes: Awaited<ReturnType<typeof captureWorkspaceTree>>["changes"];
|
|
41
49
|
}>;
|
|
42
50
|
/** Capture the complete workspace tree through Git's private index mechanics. */
|
|
43
|
-
export declare function captureTender(repository: GitRepository, input: TenderCaptureCoordinates
|
|
51
|
+
export declare function captureTender(repository: GitRepository, input: TenderCaptureCoordinates & Readonly<{
|
|
52
|
+
rejectUnmerged: true;
|
|
53
|
+
}>): Promise<Preparation<TenderCapture, TenderCaptureRefusal>>;
|
|
54
|
+
export declare function captureTender(repository: GitRepository, input: TenderCaptureCoordinates): Promise<Preparation<TenderCapture, WorktreeMissingRefusal>>;
|
|
44
55
|
export declare function dirtyTenderRefusal(repository: GitRepository, contractId: ContractId, tender: TenderCapture): Promise<DirtyWorkspaceRefusal>;
|
|
45
56
|
export declare function dirtyTenderDelta(repository: GitRepository, tender: TenderCapture): Promise<WorkspaceDirtyDelta | undefined>;
|
|
46
57
|
type GitCommitIdentity = Readonly<{
|
package/build/src/git/tender.js
CHANGED
|
@@ -13,13 +13,49 @@ function workspaceFor(repository, input) {
|
|
|
13
13
|
return input.workspacePath;
|
|
14
14
|
return input.place === undefined ? undefined : worktreePath(repository, input.place);
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
async function workspaceMergeHead(repository, workspace) {
|
|
17
|
+
try {
|
|
18
|
+
return mintSnapshotId((await runGit(repository, ["-C", workspace, "rev-parse", "-q", "--verify", "MERGE_HEAD"]))
|
|
19
|
+
.toString("utf8")
|
|
20
|
+
.trim());
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error instanceof GitPlumbingError && error.status === 1)
|
|
24
|
+
return undefined;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function unmergedWorkspacePaths(repository, workspace) {
|
|
29
|
+
const records = (await runGit(repository, ["-C", workspace, "ls-files", "--unmerged", "-z"]))
|
|
30
|
+
.toString("utf8")
|
|
31
|
+
.split("\0");
|
|
32
|
+
const paths = new Set();
|
|
33
|
+
for (const record of records.slice(0, -1)) {
|
|
34
|
+
const separator = record.indexOf("\t");
|
|
35
|
+
if (separator < 0)
|
|
36
|
+
throw new Error(`malformed unmerged index record: ${record}`);
|
|
37
|
+
paths.add(record.slice(separator + 1));
|
|
38
|
+
}
|
|
39
|
+
return [...paths].sort();
|
|
40
|
+
}
|
|
17
41
|
export async function captureTender(repository, input) {
|
|
18
42
|
const workspace = workspaceFor(repository, input);
|
|
19
43
|
if (workspace === undefined || !(await workspaceExists(repository, input.coordinates.workspace, workspace))) {
|
|
20
44
|
return { kind: "refused", refusal: { kind: "worktree-missing", contractId: input.contractId } };
|
|
21
45
|
}
|
|
22
|
-
|
|
46
|
+
if (input.rejectUnmerged === true) {
|
|
47
|
+
const paths = await unmergedWorkspacePaths(repository, workspace);
|
|
48
|
+
if (paths.length > 0)
|
|
49
|
+
return { kind: "refused", refusal: { kind: "unmerged-paths", contractId: input.contractId, paths } };
|
|
50
|
+
}
|
|
51
|
+
const captured = await captureWorkspaceTree(repository, workspace);
|
|
52
|
+
const mergeHead = input.captureMergeState === true
|
|
53
|
+
? await workspaceMergeHead(repository, workspace)
|
|
54
|
+
: undefined;
|
|
55
|
+
return {
|
|
56
|
+
kind: "prepared",
|
|
57
|
+
data: { ...captured, ...(mergeHead === undefined ? {} : { mergeHead }) },
|
|
58
|
+
};
|
|
23
59
|
}
|
|
24
60
|
async function dirtyShortStat(repository, tender) {
|
|
25
61
|
return decodeGitNumstat(await runGit(repository, [
|
|
@@ -81,9 +117,12 @@ export async function prepareDeliveryCommitMetadata(repository, input) {
|
|
|
81
117
|
}
|
|
82
118
|
/** Materialize the tender tree when it contains admitted dirty workspace bytes. */
|
|
83
119
|
export async function materializeTenderSnapshot(repository, tender, commit) {
|
|
84
|
-
if (!tender.dirty)
|
|
120
|
+
if (!tender.dirty && tender.mergeHead === undefined)
|
|
85
121
|
return tender.head;
|
|
86
|
-
const
|
|
122
|
+
const parents = ["-p", gitObjectIdForSnapshot(tender.head)];
|
|
123
|
+
if (tender.mergeHead !== undefined)
|
|
124
|
+
parents.push("-p", gitObjectIdForSnapshot(tender.mergeHead));
|
|
125
|
+
const oid = (await runGitWithEnvironment(repository, ["commit-tree", tender.tree, ...parents], commit.message, {
|
|
87
126
|
GIT_AUTHOR_NAME: commit.identity.name,
|
|
88
127
|
GIT_AUTHOR_EMAIL: commit.identity.email,
|
|
89
128
|
GIT_COMMITTER_NAME: commit.identity.name,
|
package/build/src/kanshi/read.js
CHANGED
|
@@ -244,22 +244,42 @@ async function joinAkuma(path, observeContract, dispatches, aliases) {
|
|
|
244
244
|
for (const binding of aliases.value)
|
|
245
245
|
aliasById.set(binding.akuId, [...(aliasById.get(binding.akuId) ?? []), binding]);
|
|
246
246
|
const dispatchById = new Map(dispatches.map((dispatch) => [dispatch.akuId, dispatch]));
|
|
247
|
+
const rows = source.rows.map((row) => {
|
|
248
|
+
const dispatch = dispatchById.get(row.id);
|
|
249
|
+
return {
|
|
250
|
+
...row,
|
|
251
|
+
aliases: (aliasById.get(row.id) ?? []).map((binding) => binding.alias),
|
|
252
|
+
...(dispatch === undefined ? {} : {
|
|
253
|
+
contract: {
|
|
254
|
+
id: dispatch.contractId,
|
|
255
|
+
observed: observeContract(dispatch.contractId),
|
|
256
|
+
},
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
const running = rows
|
|
261
|
+
.filter((row) => row.life === "running")
|
|
262
|
+
.sort((left, right) => {
|
|
263
|
+
const l = "lastActivityAt" in left && left.lastActivityAt !== null ? Date.parse(left.lastActivityAt) : -Infinity;
|
|
264
|
+
const r = "lastActivityAt" in right && right.lastActivityAt !== null ? Date.parse(right.lastActivityAt) : -Infinity;
|
|
265
|
+
return r - l;
|
|
266
|
+
})
|
|
267
|
+
.slice(0, 3);
|
|
268
|
+
const snapshots = new Map(await Promise.all(running.map(async (row) => {
|
|
269
|
+
try {
|
|
270
|
+
return [row.id, (await Akuma.of(path).of({ id: row.id }).status()).timeline];
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return [row.id, undefined];
|
|
274
|
+
}
|
|
275
|
+
})));
|
|
247
276
|
return {
|
|
248
277
|
kind: "present",
|
|
249
278
|
value: {
|
|
250
279
|
...source,
|
|
251
|
-
rows:
|
|
252
|
-
const
|
|
253
|
-
return {
|
|
254
|
-
...row,
|
|
255
|
-
aliases: (aliasById.get(row.id) ?? []).map((binding) => binding.alias),
|
|
256
|
-
...(dispatch === undefined ? {} : {
|
|
257
|
-
contract: {
|
|
258
|
-
id: dispatch.contractId,
|
|
259
|
-
observed: observeContract(dispatch.contractId),
|
|
260
|
-
},
|
|
261
|
-
}),
|
|
262
|
-
};
|
|
280
|
+
rows: rows.map((row) => {
|
|
281
|
+
const snapshot = snapshots.get(row.id);
|
|
282
|
+
return snapshot === undefined ? row : { ...row, snapshot };
|
|
263
283
|
}),
|
|
264
284
|
},
|
|
265
285
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ContractBoard, ContractDisposition } from "../library/contract.js";
|
|
2
2
|
import type { TaskId, TaskRef, TaskRow } from "../task/index.js";
|
|
3
|
-
import type { AkumaList, AkumaListRow, UnbornAkumaListRow } from "../akuma/index.js";
|
|
3
|
+
import type { AkumaList, AkumaListRow, UnbornAkumaListRow, ActivitySnapshot } from "../akuma/index.js";
|
|
4
4
|
import type { AkumaAlias } from "../identity/selector.js";
|
|
5
5
|
import type { WorldRoot } from "../world.js";
|
|
6
6
|
import type { ContractId } from "../library/contract.js";
|
|
@@ -53,6 +53,7 @@ export type AkumaKanshiRow = (AkumaListRow | UnbornAkumaListRow) & Readonly<{
|
|
|
53
53
|
id: string;
|
|
54
54
|
observed: ContractEndpointObservation;
|
|
55
55
|
}>;
|
|
56
|
+
snapshot?: ActivitySnapshot;
|
|
56
57
|
}>;
|
|
57
58
|
export type AkumaKanshiWorld = Omit<AkumaList, "rows"> & Readonly<{
|
|
58
59
|
rows: readonly AkumaKanshiRow[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ContractId, ContractState } from "../core/facts/types.js";
|
|
2
|
+
import { type GitDecodeChannel } from "../git/read-observation.js";
|
|
3
|
+
import type { DocumentDerivation, HereWorkspaceResolver, PlacementStop, RepositoryScope } from "../protocol/operations.js";
|
|
4
|
+
import type { AcceptedIntent } from "./mutation.js";
|
|
5
|
+
export type ContinuationReport = Readonly<{
|
|
6
|
+
attempted: number;
|
|
7
|
+
claimed: readonly ContractId[];
|
|
8
|
+
stopped: readonly Readonly<{
|
|
9
|
+
contractId: ContractId;
|
|
10
|
+
stop: PlacementStop | Readonly<{
|
|
11
|
+
kind: "already-terminal" | "delivery-missing";
|
|
12
|
+
}>;
|
|
13
|
+
}>[];
|
|
14
|
+
}>;
|
|
15
|
+
export declare function continueDeliveredDependents<Value extends object>(input: Readonly<{
|
|
16
|
+
scope: RepositoryScope;
|
|
17
|
+
channel: GitDecodeChannel;
|
|
18
|
+
contractId: ContractId;
|
|
19
|
+
accepted: AcceptedIntent<Value>;
|
|
20
|
+
deriveDocument: (state: ContractState) => DocumentDerivation;
|
|
21
|
+
resolveHereWorkspace?: HereWorkspaceResolver;
|
|
22
|
+
actor?: import("../core/facts/types.js").ActorId;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
}>): Promise<AcceptedIntent<Value & Readonly<{
|
|
25
|
+
continuation?: ContinuationReport;
|
|
26
|
+
}>>>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { observeActiveContractWorld } from "../git/observe.js";
|
|
2
|
+
import { withGitReadObservation } from "../git/read-observation.js";
|
|
3
|
+
import { continueDeliveryOperation } from "../protocol/deliver.js";
|
|
4
|
+
function directDependents(world, claimed, attempted, newlyClaimed) {
|
|
5
|
+
const dependents = [];
|
|
6
|
+
for (const [contractId, record] of world.contracts) {
|
|
7
|
+
const state = record.state;
|
|
8
|
+
if (state === null
|
|
9
|
+
|| attempted.has(contractId)
|
|
10
|
+
|| !state.terms.after.includes(claimed)
|
|
11
|
+
|| state.delivery === null
|
|
12
|
+
|| !state.terms.after.every((dependency) => newlyClaimed.has(dependency) || world.eligibility.get(dependency)?.terminal?.kind === "claimed"))
|
|
13
|
+
continue;
|
|
14
|
+
dependents.push({ state, journal: record.entries });
|
|
15
|
+
}
|
|
16
|
+
return dependents.sort((left, right) => left.state.id.localeCompare(right.state.id));
|
|
17
|
+
}
|
|
18
|
+
function appendAccepted(primary, child) {
|
|
19
|
+
const effects = [...(primary.physical?.effects ?? []), ...(child.physical?.effects ?? [])];
|
|
20
|
+
const lag = [...(primary.physical?.lag ?? []), ...(child.physical?.lag ?? [])];
|
|
21
|
+
return {
|
|
22
|
+
...primary,
|
|
23
|
+
facts: [...primary.facts, ...child.facts],
|
|
24
|
+
...(effects.length === 0 && lag.length === 0 ? {} : { physical: { effects, lag } }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function continueDeliveredDependents(input) {
|
|
28
|
+
const world = await withGitReadObservation(input.scope, input.channel, async (observation) => await observeActiveContractWorld(observation));
|
|
29
|
+
const pending = [input.contractId];
|
|
30
|
+
const attempted = new Set();
|
|
31
|
+
const newlyClaimed = new Set(pending);
|
|
32
|
+
const claimed = [];
|
|
33
|
+
const stopped = [];
|
|
34
|
+
let accepted = input.accepted;
|
|
35
|
+
while (pending.length > 0) {
|
|
36
|
+
const parent = pending.shift();
|
|
37
|
+
for (const candidate of directDependents(world, parent, attempted, newlyClaimed)) {
|
|
38
|
+
const contractId = candidate.state.id;
|
|
39
|
+
attempted.add(contractId);
|
|
40
|
+
const child = await continueDeliveryOperation({
|
|
41
|
+
scope: input.scope,
|
|
42
|
+
channel: input.channel,
|
|
43
|
+
state: candidate.state,
|
|
44
|
+
journal: candidate.journal,
|
|
45
|
+
deriveDocument: input.deriveDocument,
|
|
46
|
+
...(input.resolveHereWorkspace === undefined ? {} : { resolveHereWorkspace: input.resolveHereWorkspace }),
|
|
47
|
+
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
48
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
49
|
+
});
|
|
50
|
+
accepted = appendAccepted(accepted, child.admission);
|
|
51
|
+
if (child.evidence.completion !== undefined) {
|
|
52
|
+
claimed.push(contractId);
|
|
53
|
+
newlyClaimed.add(contractId);
|
|
54
|
+
pending.push(contractId);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const stop = child.evidence.placement;
|
|
58
|
+
if (stop === undefined)
|
|
59
|
+
throw new Error("incomplete continuation is missing its placement stop");
|
|
60
|
+
if ("refusal" in stop && stop.refusal.kind === "terminal") {
|
|
61
|
+
stopped.push({ contractId, stop: { kind: "already-terminal" } });
|
|
62
|
+
}
|
|
63
|
+
else if ("refusal" in stop && stop.refusal.kind === "delivery-missing") {
|
|
64
|
+
stopped.push({ contractId, stop: { kind: "delivery-missing" } });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
stopped.push({ contractId, stop });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...accepted,
|
|
73
|
+
value: {
|
|
74
|
+
...accepted.value,
|
|
75
|
+
...(attempted.size === 0 ? {} : { continuation: { attempted: attempted.size, claimed, stopped } }),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -6,7 +6,7 @@ export { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
|
6
6
|
import { type PlacementStop, type RepositoryScope, type VerificationStop, type DeliveryPreparationRefusal } from "../protocol/operations.js";
|
|
7
7
|
import type { ContractBoard, ContractDisposition, ContractGateCurrent, ContractGateReport, ContractObservation, ContractPhase, ContractRow } from "../protocol/read/status.js";
|
|
8
8
|
import type { AuditReport } from "../protocol/audit.js";
|
|
9
|
-
import { type
|
|
9
|
+
import { type IntegrationConflictMaterialized, type VerificationReuse } from "../protocol/deliver.js";
|
|
10
10
|
import type { ReconcileReport as ProtocolReconcileReport } from "../protocol/reconcile.js";
|
|
11
11
|
import { type ReviewValue } from "../protocol/review.js";
|
|
12
12
|
import type { FactKind } from "../core/facts/types.js";
|
|
@@ -15,7 +15,8 @@ import { type SettlementReport } from "../settlement/settle.js";
|
|
|
15
15
|
import { type TaskId } from "../task/identity.js";
|
|
16
16
|
import { Repo, type ReconcileInput } from "./repo.js";
|
|
17
17
|
import { type AuditInput } from "./audit.js";
|
|
18
|
-
import { Delivery } from "./delivery.js";
|
|
18
|
+
import { Delivery, type DeliveryValue } from "./delivery.js";
|
|
19
|
+
import { type ContinuationReport } from "./continuation.js";
|
|
19
20
|
import { type MutationResult } from "./mutation.js";
|
|
20
21
|
import { KeiyakuRetry, type KeiyakuRefusal, type KeiyakuRetryReason } from "./refusal.js";
|
|
21
22
|
export { KeiyakuRefused, type ContractAppointmentRefusal, } from "./refusal.js";
|
|
@@ -41,8 +42,11 @@ export type ContractHistory = Readonly<{
|
|
|
41
42
|
}>;
|
|
42
43
|
export type ActorId = string;
|
|
43
44
|
export type AttestationVerdict = "satisfied" | "unsatisfied";
|
|
44
|
-
export type Review = ReviewValue
|
|
45
|
+
export type Review = ReviewValue & Readonly<{
|
|
46
|
+
continuation?: ContinuationReport;
|
|
47
|
+
}>;
|
|
45
48
|
export type { PlacementStop, VerificationReuse, VerificationStop, IntegrationConflictMaterialized };
|
|
49
|
+
export type { ContinuationReport };
|
|
46
50
|
export type TopologyEffect = ProtocolReconcileReport["effects"][number] | ContractFileEffect;
|
|
47
51
|
export type Lag = ProtocolReconcileReport["lag"][number] | ContractFileLag;
|
|
48
52
|
export type { MutationResult };
|
|
@@ -115,7 +119,7 @@ export type DeliverInput = ActorOptions & Readonly<{
|
|
|
115
119
|
}>;
|
|
116
120
|
type ForwardedDeliveryReceipt = Readonly<{
|
|
117
121
|
kind: "accepted";
|
|
118
|
-
result: MutationResult<
|
|
122
|
+
result: MutationResult<DeliveryValue>;
|
|
119
123
|
}> | Readonly<{
|
|
120
124
|
kind: "refused";
|
|
121
125
|
refusal: KeiyakuRefusal;
|
|
@@ -125,7 +129,7 @@ type ForwardedDeliveryReceipt = Readonly<{
|
|
|
125
129
|
}> | IntegrationConflictMaterialized;
|
|
126
130
|
type ForwardedReviewReceipt = Readonly<{
|
|
127
131
|
kind: "accepted";
|
|
128
|
-
result: MutationResult<
|
|
132
|
+
result: MutationResult<Review>;
|
|
129
133
|
}> | Readonly<{
|
|
130
134
|
kind: "refused";
|
|
131
135
|
refusal: KeiyakuRefusal;
|