@akagilnc/pi-workflow-roles 0.1.4062 → 0.1.4196
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/dist/acp-host/production-host.js +2075 -1088
- package/dist/archivist-record-entry.js +204 -20
- package/dist/archivist-record-topology.js +36 -16
- package/dist/auditor-dossier-tool.js +2 -1
- package/dist/diarist-contracts.js +96 -0
- package/dist/doctor-evidence.js +3 -2
- package/dist/headless-host/production-host.js +2215 -1228
- package/dist/migrate-book-topology.js +278917 -0
- package/dist/navigator-public-session.js +22 -25
- package/dist/notary-source-run.js +7 -4
- package/dist/pi/durable-principal.js +9 -6
- package/dist/public-cli/auto-resume.js +2 -5
- package/dist/public-cli/diarist-run.js +27 -25
- package/dist/public-cli/instruction-seat-run.js +8 -11
- package/dist/public-cli/invocation.js +127 -63
- package/dist/public-cli/main.js +1507 -645
- package/dist/public-cli/option-definitions.js +2 -2
- package/dist/public-cli/post-admission.js +47 -12
- package/dist/public-cli/run-lifecycle.js +206 -58
- package/dist/public-cli/seat-ticket-binding.js +3 -6
- package/dist/public-cli/settlement.js +5 -3
- package/dist/role-run-placement.js +84 -0
- package/dist/role-run-relocation.js +358 -0
- package/dist/run-terminal-artifacts.js +2 -1
- package/dist/run-ticket-number.js +51 -32
- package/dist/sitian-appender.js +11 -26
- package/dist/submission-ledger.js +78 -19
- package/dist/ticket-provenance.js +3 -1
- package/package.json +3 -2
- package/scripts/build-package.mjs +34 -2
- package/souls/countersign.md +2 -2
- package/souls/fixer.md +3 -2
- package/src/analyst-ledger.ts +14 -15
- package/src/archivist-record-entry.ts +285 -38
- package/src/archivist-record-topology.ts +43 -18
- package/src/auditor-dossier-tool.ts +2 -1
- package/src/book-topology-migration-placement.ts +493 -0
- package/src/book-topology-migration.ts +220 -0
- package/src/book-topology-migrators-t11.ts +856 -0
- package/src/book-topology-mixed-volume-migrators.ts +470 -0
- package/src/book-topology-partition-migrators.ts +25 -0
- package/src/book-topology-record-class-migrators.ts +779 -0
- package/src/book-topology-runs-migrator.ts +244 -0
- package/src/diarist-contracts.ts +116 -0
- package/src/diarist-role.ts +6 -0
- package/src/diarist.ts +2 -0
- package/src/doctor-evidence.ts +4 -1
- package/src/doctor-role.ts +1 -1
- package/src/navigator-public-session.ts +33 -30
- package/src/notary-source-run.ts +9 -4
- package/src/pi/durable-principal.ts +9 -13
- package/src/public-cli/auto-resume.ts +2 -6
- package/src/public-cli/countersign-run.ts +182 -32
- package/src/public-cli/diarist-run.ts +34 -26
- package/src/public-cli/instruction-seat-run.ts +7 -11
- package/src/public-cli/invocation.ts +192 -58
- package/src/public-cli/option-definitions.ts +2 -2
- package/src/public-cli/post-admission.ts +73 -13
- package/src/public-cli/run-lifecycle.ts +255 -65
- package/src/public-cli/seat-ticket-binding.ts +3 -6
- package/src/public-cli/settlement.ts +6 -4
- package/src/role-run-placement.ts +128 -0
- package/src/role-run-relocation.ts +491 -0
- package/src/role-runtime.ts +11 -5
- package/src/run-terminal-artifacts.ts +3 -1
- package/src/run-ticket-number.ts +80 -20
- package/src/sitian-appender.ts +25 -28
- package/src/submission-ledger.ts +102 -19
- package/src/ticket-provenance.ts +4 -1
- package/src/ticket-trajectory.ts +22 -35
- package/src/worker-role.ts +4 -4
- package/src/worker-submission-gates.ts +6 -6
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { mkdir, readdir, rename } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
physicalPathIdentity,
|
|
7
|
+
physicallyContainedIn,
|
|
8
|
+
} from "./activation-ledger-topology.ts";
|
|
9
|
+
import {
|
|
10
|
+
autopsyWriterLock,
|
|
11
|
+
describeErrorIdentity,
|
|
12
|
+
} from "./public-cli/run-lifecycle.ts";
|
|
13
|
+
export type MigrationDisposition = "placed" | "unbound" | "discarded";
|
|
14
|
+
|
|
15
|
+
export type MigrationItemOutcome =
|
|
16
|
+
| {
|
|
17
|
+
readonly disposition: MigrationDisposition;
|
|
18
|
+
/** Source-relative identity of the directory, file, or JSONL row. */
|
|
19
|
+
readonly source: string;
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
readonly disposition: "unbound";
|
|
23
|
+
readonly source: string;
|
|
24
|
+
readonly malformed: true;
|
|
25
|
+
/** Exact source bytes are mandatory for every malformed JSONL row. */
|
|
26
|
+
readonly malformedRaw: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type MigrationPartitionReport = {
|
|
30
|
+
readonly partition: string;
|
|
31
|
+
readonly unit: "entries" | "lines";
|
|
32
|
+
readonly before: number;
|
|
33
|
+
readonly placed: number;
|
|
34
|
+
readonly unbound: number;
|
|
35
|
+
readonly discarded: number;
|
|
36
|
+
readonly malformedRows: readonly {
|
|
37
|
+
readonly source: string;
|
|
38
|
+
readonly raw: string;
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type BookTopologyMigrationContext = {
|
|
43
|
+
/** Immutable source after the atomic books/ rename. */
|
|
44
|
+
readonly backupBooksDirectory: string;
|
|
45
|
+
/** Fresh destination at the former books/ path. */
|
|
46
|
+
readonly booksDirectory: string;
|
|
47
|
+
/**
|
|
48
|
+
* Ticket numbers whose records.jsonl grew during record-class placement.
|
|
49
|
+
* Human view is finalized once after all record-class migrators finish —
|
|
50
|
+
* never after ticket-provenance home alone (misplaced lines can still land).
|
|
51
|
+
*/
|
|
52
|
+
readonly touchedTicketsByBook: Map<string, Set<number>>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type BookTopologyPartitionMigrator = {
|
|
56
|
+
readonly partition: string;
|
|
57
|
+
migrate(context: BookTopologyMigrationContext): Promise<MigrationPartitionReport>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type BookTopologyMigrationReport = {
|
|
61
|
+
readonly backupBooksDirectory: string;
|
|
62
|
+
readonly booksDirectory: string;
|
|
63
|
+
readonly partitions: readonly MigrationPartitionReport[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Shared reconciliation projection. Counting outcomes makes the closure true by construction. */
|
|
67
|
+
export function reconcileMigrationPartition(
|
|
68
|
+
partition: string,
|
|
69
|
+
unit: MigrationPartitionReport["unit"],
|
|
70
|
+
outcomes: readonly MigrationItemOutcome[],
|
|
71
|
+
): MigrationPartitionReport {
|
|
72
|
+
let placed = 0;
|
|
73
|
+
let unbound = 0;
|
|
74
|
+
let discarded = 0;
|
|
75
|
+
const malformedRows: { source: string; raw: string }[] = [];
|
|
76
|
+
|
|
77
|
+
for (const outcome of outcomes) {
|
|
78
|
+
if (outcome.disposition === "placed") placed += 1;
|
|
79
|
+
else if (outcome.disposition === "unbound") unbound += 1;
|
|
80
|
+
else discarded += 1;
|
|
81
|
+
if ("malformed" in outcome) {
|
|
82
|
+
malformedRows.push({ source: outcome.source, raw: outcome.malformedRaw });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
partition,
|
|
88
|
+
unit,
|
|
89
|
+
before: outcomes.length,
|
|
90
|
+
placed,
|
|
91
|
+
unbound,
|
|
92
|
+
discarded,
|
|
93
|
+
malformedRows,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type WriterLockCandidate = {
|
|
98
|
+
readonly path: string;
|
|
99
|
+
readonly regularFile: boolean;
|
|
100
|
+
readonly kind: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
async function findWriterLocks(root: string): Promise<WriterLockCandidate[]> {
|
|
104
|
+
const locks: WriterLockCandidate[] = [];
|
|
105
|
+
async function walk(directory: string): Promise<void> {
|
|
106
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
107
|
+
const path = join(directory, entry.name);
|
|
108
|
+
if (entry.name === "writer.lock") {
|
|
109
|
+
const kind = entry.isFile() ? "file"
|
|
110
|
+
: entry.isDirectory() ? "directory"
|
|
111
|
+
: entry.isSymbolicLink() ? "symbolic link"
|
|
112
|
+
: entry.isFIFO() ? "FIFO"
|
|
113
|
+
: entry.isSocket() ? "socket"
|
|
114
|
+
: entry.isCharacterDevice() ? "character device"
|
|
115
|
+
: entry.isBlockDevice() ? "block device"
|
|
116
|
+
: "unknown filesystem object";
|
|
117
|
+
locks.push({ path, regularFile: entry.isFile(), kind });
|
|
118
|
+
} else if (entry.isDirectory()) {
|
|
119
|
+
await walk(path);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
await walk(root);
|
|
124
|
+
return locks;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Refuse migration from a role whose canonical dossier is inside books/, and
|
|
129
|
+
* while a writer lease proves a run is currently active. Retained lifecycle
|
|
130
|
+
* state is history, not holder liveness.
|
|
131
|
+
*/
|
|
132
|
+
export async function assertBookTopologyMigrationPrerequisites(
|
|
133
|
+
booksDirectory: string,
|
|
134
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
135
|
+
): Promise<void> {
|
|
136
|
+
const runDirectory = env.AK_ROLE_RUN_DIR;
|
|
137
|
+
if (
|
|
138
|
+
runDirectory !== undefined
|
|
139
|
+
&& (physicalPathIdentity(booksDirectory) === physicalPathIdentity(runDirectory)
|
|
140
|
+
|| physicallyContainedIn(booksDirectory, runDirectory))
|
|
141
|
+
) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`book topology migration cannot run from a role dossier inside books/: ${runDirectory}`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const active: string[] = [];
|
|
148
|
+
for (const candidate of await findWriterLocks(booksDirectory)) {
|
|
149
|
+
const lockPath = candidate.path;
|
|
150
|
+
if (!candidate.regularFile) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`cannot establish zero in-flight runs from ${lockPath}: writer lock is a ${candidate.kind}, holder liveness unverifiable`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const holder = await autopsyWriterLock(lockPath);
|
|
156
|
+
if (holder.verdict === "alive") {
|
|
157
|
+
active.push(`${lockPath} (live pid ${holder.pid})`);
|
|
158
|
+
} else if (holder.verdict === "unknown") {
|
|
159
|
+
const cause = holder.reason === "unreadable"
|
|
160
|
+
? `unreadable: ${describeErrorIdentity(holder.readFailure)}`
|
|
161
|
+
: `unparseable holder: ${JSON.stringify(holder.content)}`;
|
|
162
|
+
throw new Error(`cannot establish zero in-flight runs from ${lockPath}: ${cause}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (active.length > 0) {
|
|
166
|
+
throw new Error(`book topology migration requires zero in-flight runs:\n${active.join("\n")}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function datedBooksBackupDirectory(
|
|
171
|
+
booksDirectory: string,
|
|
172
|
+
now: Date = new Date(),
|
|
173
|
+
): string {
|
|
174
|
+
const date = now.toISOString().slice(0, 10);
|
|
175
|
+
return join(dirname(booksDirectory), `${basename(booksDirectory)}-${date}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Atomic same-parent rename: the readable pre-migration tree is never packed. */
|
|
179
|
+
export async function renameBooksToDatedBackup(
|
|
180
|
+
booksDirectory: string,
|
|
181
|
+
now: Date = new Date(),
|
|
182
|
+
): Promise<string> {
|
|
183
|
+
const backup = datedBooksBackupDirectory(booksDirectory, now);
|
|
184
|
+
await rename(booksDirectory, backup);
|
|
185
|
+
return backup;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function migrateBookTopology(input: {
|
|
189
|
+
readonly ledgerHome?: string;
|
|
190
|
+
readonly now?: Date;
|
|
191
|
+
readonly migrators: readonly BookTopologyPartitionMigrator[];
|
|
192
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
193
|
+
}): Promise<BookTopologyMigrationReport> {
|
|
194
|
+
if (input.migrators.length === 0) {
|
|
195
|
+
throw new Error("book topology migration has no partition migrators");
|
|
196
|
+
}
|
|
197
|
+
const ledgerHome = resolve(input.ledgerHome ?? join(homedir(), ".ak-roles"));
|
|
198
|
+
const booksDirectory = join(ledgerHome, "books");
|
|
199
|
+
await assertBookTopologyMigrationPrerequisites(booksDirectory, input.env);
|
|
200
|
+
const backupBooksDirectory = await renameBooksToDatedBackup(booksDirectory, input.now);
|
|
201
|
+
await mkdir(booksDirectory);
|
|
202
|
+
|
|
203
|
+
const context: BookTopologyMigrationContext = {
|
|
204
|
+
backupBooksDirectory,
|
|
205
|
+
booksDirectory,
|
|
206
|
+
touchedTicketsByBook: new Map(),
|
|
207
|
+
};
|
|
208
|
+
const partitions: MigrationPartitionReport[] = [];
|
|
209
|
+
for (const migrator of input.migrators) {
|
|
210
|
+
const report = await migrator.migrate(context);
|
|
211
|
+
if (report.partition !== migrator.partition) {
|
|
212
|
+
throw new Error(`partition report mismatch: expected ${migrator.partition}, received ${report.partition}`);
|
|
213
|
+
}
|
|
214
|
+
if (report.before !== report.placed + report.unbound + report.discarded) {
|
|
215
|
+
throw new Error(`partition reconciliation did not close: ${report.partition}`);
|
|
216
|
+
}
|
|
217
|
+
partitions.push(report);
|
|
218
|
+
}
|
|
219
|
+
return { backupBooksDirectory, booksDirectory, partitions };
|
|
220
|
+
}
|