@akagilnc/pi-workflow-roles 0.1.5679 → 0.1.5698
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 +4016 -3944
- package/dist/external-host-turn-loop.js +35 -17
- package/dist/headless-host/production-host.js +3246 -3174
- package/dist/migrate-book-topology.js +129 -100
- package/dist/pi/role-turn-host.js +1 -1
- package/dist/public-cli/main.js +1672 -1621
- package/dist/report-ticket-tool.js +12 -2
- package/dist/role-run-path-rewrite.js +149 -0
- package/dist/role-run-relocation.js +11 -121
- package/dist/run-dossier.js +10 -1
- package/dist/session-identity.js +1 -0
- package/dist/submission-ledger.js +11 -0
- package/package.json +1 -1
- package/resources/navigator-route-playbook.md +2 -12
- package/souls/navigator.md +2 -4
- package/src/acp-host/production-host.ts +1 -0
- package/src/acp-host/role-turn-host.ts +2 -2
- package/src/external-host-turn-loop.ts +36 -22
- package/src/headless-host/production-host.ts +1 -0
- package/src/headless-host/role-turn-host.ts +2 -2
- package/src/pi/role-turn-host.ts +1 -1
- package/src/prepared-role-turn.ts +2 -0
- package/src/report-ticket-tool.ts +12 -2
- package/src/role-envelope.ts +20 -11
- package/src/role-run-path-rewrite.ts +238 -0
- package/src/role-run-relocation.ts +34 -200
- package/src/role-runtime.ts +29 -12
- package/src/run-dossier.ts +11 -1
- package/src/session-identity.ts +1 -0
- package/src/submission-ledger.ts +11 -0
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type AdmittedRoleInvocation,
|
|
21
21
|
} from "./public-cli/invocation.ts";
|
|
22
22
|
import { isUnboundRunDirectory, sessionDirectoryOf, sessionFileOf } from "./role-run-placement.ts";
|
|
23
|
+
import { rewriteRunDirectoryPathValue } from "./role-run-path-rewrite.ts";
|
|
23
24
|
import { readPageSync } from "./run-dossier.ts";
|
|
24
25
|
import { readDeclaredTicketNumber } from "./run-ticket-number.ts";
|
|
25
26
|
import { isRecord } from "./unknown-value.ts";
|
|
@@ -128,6 +129,11 @@ export async function reportTicketFromHostContext(
|
|
|
128
129
|
// Live handles belong with rename ownership — before derived renderCurrentSync
|
|
129
130
|
// (#1171 F4-R2). Reuse relocate's heldLease.relocate seam; keep raw I/O throw.
|
|
130
131
|
const applyLiveHandles = (nextDirectory: string): void => {
|
|
132
|
+
// Capture live session handle before ownership transfer. After relocate the
|
|
133
|
+
// write handle must share host-authority coords with the principal (#1183):
|
|
134
|
+
// project the prior path onto the new leaf — never rebuild default
|
|
135
|
+
// session.jsonl over a non-default seal (and do not trust a setter rebuild).
|
|
136
|
+
const priorSessionFile = context.sessionManager.getSessionFile?.();
|
|
131
137
|
(context as { runDirectory?: string }).runDirectory = nextDirectory;
|
|
132
138
|
// Same-leg spawn env only (#1171 F1): never overwrite another leg's ambient
|
|
133
139
|
// AK_ROLE_RUN_DIR in a shared process. HostContext is the per-turn authority
|
|
@@ -135,8 +141,12 @@ export async function reportTicketFromHostContext(
|
|
|
135
141
|
if (process.env.AK_ROLE_RUN_DIR === runDirectory) {
|
|
136
142
|
process.env.AK_ROLE_RUN_DIR = nextDirectory;
|
|
137
143
|
}
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
const projectedSessionFile = typeof priorSessionFile === "string" && priorSessionFile.trim() !== ""
|
|
145
|
+
? rewriteRunDirectoryPathValue(priorSessionFile, runDirectory, nextDirectory)
|
|
146
|
+
: sessionFileOf(nextDirectory);
|
|
147
|
+
if (typeof projectedSessionFile === "string") {
|
|
148
|
+
context.sessionManager.setSessionFile?.(projectedSessionFile);
|
|
149
|
+
}
|
|
140
150
|
};
|
|
141
151
|
const relocation = alreadyPlaced
|
|
142
152
|
? undefined
|
package/src/role-envelope.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { createServer, type Server, type Socket } from "node:net";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { sessionFileOf } from "./role-run-placement.ts";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
import {
|
|
@@ -94,6 +94,12 @@ export async function prepareRoleEnvelope(options: {
|
|
|
94
94
|
* Tests may omit → runDirectory default.
|
|
95
95
|
*/
|
|
96
96
|
readonly sessionFile?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Host authority that sealed request.principal. Required for mid-turn run
|
|
99
|
+
* relocate to reseal opaque principal wire (#1183); absent throws on relocate
|
|
100
|
+
* rather than mutating opaque principal or inventing a host-specific bypass.
|
|
101
|
+
*/
|
|
102
|
+
readonly principalAuthority?: import("./host-contracts.ts").DurablePrincipalAuthority;
|
|
97
103
|
}): Promise<PreparedRoleTurn> {
|
|
98
104
|
const { request } = options;
|
|
99
105
|
if (options.socketPath === "") {
|
|
@@ -170,8 +176,9 @@ export async function prepareRoleEnvelope(options: {
|
|
|
170
176
|
};
|
|
171
177
|
const mutableRequest = request as {
|
|
172
178
|
runDirectory: string;
|
|
173
|
-
principal:
|
|
179
|
+
principal: import("./host-contracts.ts").DurablePrincipal;
|
|
174
180
|
};
|
|
181
|
+
const principalAuthority = options.principalAuthority;
|
|
175
182
|
const context: HostContext = {
|
|
176
183
|
cwd: request.cwd,
|
|
177
184
|
mode: "print",
|
|
@@ -199,8 +206,9 @@ export async function prepareRoleEnvelope(options: {
|
|
|
199
206
|
// and non-correctable MCP catch). Submission does not abort the turn.
|
|
200
207
|
},
|
|
201
208
|
};
|
|
202
|
-
// #1171: report-ticket updates HostContext.runDirectory;
|
|
203
|
-
//
|
|
209
|
+
// #1171 / #1183: report-ticket updates HostContext.runDirectory; reseal principal
|
|
210
|
+
// through host authority when provided. Never assign Pi session fields onto
|
|
211
|
+
// opaque/frozen principal wire (DurablePrincipal / #636).
|
|
204
212
|
Object.defineProperty(context, "runDirectory", {
|
|
205
213
|
configurable: true,
|
|
206
214
|
enumerable: true,
|
|
@@ -210,14 +218,15 @@ export async function prepareRoleEnvelope(options: {
|
|
|
210
218
|
set(next: string) {
|
|
211
219
|
if (typeof next !== "string" || next.trim() === "") return;
|
|
212
220
|
if (mutableRequest.runDirectory === next) return;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
if (typeof mutableRequest.principal.sessionFile !== "string") {
|
|
219
|
-
mutableRequest.principal.sessionFile = sessionFile;
|
|
221
|
+
if (principalAuthority === undefined) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
"mid-turn run relocate requires principalAuthority on prepareRoleEnvelope",
|
|
224
|
+
);
|
|
220
225
|
}
|
|
226
|
+
projectTurnRequestLiveRunDirectory(mutableRequest, next, principalAuthority);
|
|
227
|
+
// Live write handle = same host-authority coords as the resealed principal
|
|
228
|
+
// (#1183). Never rebuild default session.jsonl over a non-default seal.
|
|
229
|
+
sessionFile = principalAuthority.decode(mutableRequest.principal).sessionFile;
|
|
221
230
|
},
|
|
222
231
|
});
|
|
223
232
|
const bookCustomMessage = (customType: string, message: { content?: string; details?: unknown }): void => {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sole pure authority for typed run-directory path rewrite / projection.
|
|
3
|
+
* No dossier I/O — run-dossier render and role-run-relocation durable walks
|
|
4
|
+
* both consume this module (avoids a cycle through page readers).
|
|
5
|
+
*
|
|
6
|
+
* Scope is typed machine-consumed path fields only — never free text, never
|
|
7
|
+
* caller file-flag path strings.
|
|
8
|
+
*/
|
|
9
|
+
import { sep } from "node:path";
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
DurablePrincipal,
|
|
13
|
+
DurablePrincipalAuthority,
|
|
14
|
+
} from "./host-contracts.ts";
|
|
15
|
+
import { isRecord } from "./unknown-value.ts";
|
|
16
|
+
|
|
17
|
+
export const ADMITTED_PAGE_PATH_FIELDS = [
|
|
18
|
+
"runDirectory",
|
|
19
|
+
"admittedRequestPath", // pre-#1161 pages only
|
|
20
|
+
"sessionDirectory",
|
|
21
|
+
"sessionFile",
|
|
22
|
+
// #1165/#1168: caller file-flag paths stay as given.
|
|
23
|
+
"mergerInputPath",
|
|
24
|
+
"sourceRunPath",
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export const INVOCATION_PAGE_PATH_FIELDS = [
|
|
28
|
+
"runDirectory",
|
|
29
|
+
"sessionDirectory",
|
|
30
|
+
"sessionFile",
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export const RUN_STATE_PAGE_PATH_FIELDS = [
|
|
34
|
+
"runDirectory",
|
|
35
|
+
"admittedRequestPath", // pre-#1161 pages only
|
|
36
|
+
"sessionDirectory",
|
|
37
|
+
"sessionFile",
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
/** Typed principal session coordinates rewritten when the owning run leaf moves. */
|
|
41
|
+
export const PRINCIPAL_SESSION_PATH_FIELDS = [
|
|
42
|
+
"sessionDirectory",
|
|
43
|
+
"sessionFile",
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
const SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"] as const;
|
|
47
|
+
const SUMMONS_PATH_FIELDS = ["sourceRunPath"] as const;
|
|
48
|
+
|
|
49
|
+
export type RunDirectoryPathRewrite = {
|
|
50
|
+
readonly oldRunDirectory: string;
|
|
51
|
+
readonly newRunDirectory: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Rewrite one path value that points at or under oldRunDirectory. */
|
|
55
|
+
export function rewriteRunDirectoryPathValue(
|
|
56
|
+
value: unknown,
|
|
57
|
+
oldRunDirectory: string,
|
|
58
|
+
newRunDirectory: string,
|
|
59
|
+
): unknown {
|
|
60
|
+
if (typeof value !== "string") return value;
|
|
61
|
+
if (value === oldRunDirectory) return newRunDirectory;
|
|
62
|
+
const prefix = `${oldRunDirectory}${sep}`;
|
|
63
|
+
if (value.startsWith(prefix)) {
|
|
64
|
+
return `${newRunDirectory}${value.slice(oldRunDirectory.length)}`;
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Apply the longest matching oldRunDirectory rewrite. Longer keys win so a
|
|
71
|
+
* nested run path is not partially claimed by a shorter sibling prefix.
|
|
72
|
+
*/
|
|
73
|
+
export function rewriteRunDirectoryPathValueAgainstRewrites(
|
|
74
|
+
value: unknown,
|
|
75
|
+
rewrites: readonly RunDirectoryPathRewrite[],
|
|
76
|
+
): unknown {
|
|
77
|
+
if (typeof value !== "string" || rewrites.length === 0) return value;
|
|
78
|
+
let best: RunDirectoryPathRewrite | undefined;
|
|
79
|
+
for (const rewrite of rewrites) {
|
|
80
|
+
if (
|
|
81
|
+
value === rewrite.oldRunDirectory ||
|
|
82
|
+
value.startsWith(`${rewrite.oldRunDirectory}${sep}`)
|
|
83
|
+
) {
|
|
84
|
+
if (
|
|
85
|
+
best === undefined ||
|
|
86
|
+
rewrite.oldRunDirectory.length > best.oldRunDirectory.length
|
|
87
|
+
) {
|
|
88
|
+
best = rewrite;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (best === undefined) return value;
|
|
93
|
+
return rewriteRunDirectoryPathValue(
|
|
94
|
+
value,
|
|
95
|
+
best.oldRunDirectory,
|
|
96
|
+
best.newRunDirectory,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** In-place rewrite of selected fields on one record (single pair). */
|
|
101
|
+
export function rewriteRunDirectoryPathFields(
|
|
102
|
+
record: Record<string, unknown>,
|
|
103
|
+
fields: readonly string[],
|
|
104
|
+
oldRunDirectory: string,
|
|
105
|
+
newRunDirectory: string,
|
|
106
|
+
): void {
|
|
107
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(record, fields, [
|
|
108
|
+
{ oldRunDirectory, newRunDirectory },
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** In-place rewrite of selected fields against a rewrite set. */
|
|
113
|
+
export function rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
114
|
+
record: Record<string, unknown>,
|
|
115
|
+
fields: readonly string[],
|
|
116
|
+
rewrites: readonly RunDirectoryPathRewrite[],
|
|
117
|
+
): void {
|
|
118
|
+
for (const field of fields) {
|
|
119
|
+
if (field in record) {
|
|
120
|
+
record[field] = rewriteRunDirectoryPathValueAgainstRewrites(
|
|
121
|
+
record[field],
|
|
122
|
+
rewrites,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Principal session paths only — shared live + durable projection after a run move. */
|
|
129
|
+
export function rewritePrincipalSessionPaths(
|
|
130
|
+
principal: Record<string, unknown>,
|
|
131
|
+
oldRunDirectory: string,
|
|
132
|
+
newRunDirectory: string,
|
|
133
|
+
): void {
|
|
134
|
+
rewriteRunDirectoryPathFields(
|
|
135
|
+
principal,
|
|
136
|
+
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
137
|
+
oldRunDirectory,
|
|
138
|
+
newRunDirectory,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Live turn request: move runDirectory and keep principal session paths on the
|
|
144
|
+
* new leaf. Sole path = host authority decode → rewrite coords copy → seal
|
|
145
|
+
* (#1183 / #636). No shape-based in-place rewrite and no silent skip.
|
|
146
|
+
*/
|
|
147
|
+
export function projectTurnRequestLiveRunDirectory(
|
|
148
|
+
request: {
|
|
149
|
+
runDirectory: string;
|
|
150
|
+
principal: DurablePrincipal;
|
|
151
|
+
},
|
|
152
|
+
newRunDirectory: string,
|
|
153
|
+
authority: DurablePrincipalAuthority,
|
|
154
|
+
): void {
|
|
155
|
+
const old = request.runDirectory;
|
|
156
|
+
if (old === newRunDirectory) return;
|
|
157
|
+
const coords = { ...authority.decode(request.principal) };
|
|
158
|
+
rewritePrincipalSessionPaths(
|
|
159
|
+
coords as Record<string, unknown>,
|
|
160
|
+
old,
|
|
161
|
+
newRunDirectory,
|
|
162
|
+
);
|
|
163
|
+
const sealed = authority.seal(coords);
|
|
164
|
+
request.runDirectory = newRunDirectory;
|
|
165
|
+
(request as { principal: DurablePrincipal }).principal = sealed;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Typed notary/source-run locator: only runDirectory is a path. */
|
|
169
|
+
export function rewriteSourceRunLocator(
|
|
170
|
+
value: unknown,
|
|
171
|
+
rewrites: readonly RunDirectoryPathRewrite[],
|
|
172
|
+
): void {
|
|
173
|
+
if (!isRecord(value)) return;
|
|
174
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
175
|
+
value,
|
|
176
|
+
SOURCE_RUN_LOCATOR_FIELDS,
|
|
177
|
+
rewrites,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Same-ticket summons materials: typed source-run; caller attach paths stay as-is (#1165). */
|
|
182
|
+
export function rewriteSummonsMaterials(
|
|
183
|
+
value: unknown,
|
|
184
|
+
rewrites: readonly RunDirectoryPathRewrite[],
|
|
185
|
+
): void {
|
|
186
|
+
if (!isRecord(value)) return;
|
|
187
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
188
|
+
value,
|
|
189
|
+
SUMMONS_PATH_FIELDS,
|
|
190
|
+
rewrites,
|
|
191
|
+
);
|
|
192
|
+
rewriteSourceRunLocator(value.sourceRun, rewrites);
|
|
193
|
+
// #1165: summons.attachmentPaths are caller paths — do not rewrite.
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Project one admitted page in memory after its containing run moved. */
|
|
197
|
+
export function rewriteAdmittedRoleRunPage(
|
|
198
|
+
page: Record<string, unknown>,
|
|
199
|
+
rewrites: readonly RunDirectoryPathRewrite[],
|
|
200
|
+
): void {
|
|
201
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(page, ADMITTED_PAGE_PATH_FIELDS, rewrites);
|
|
202
|
+
rewriteSourceRunLocator(page.sourceRun, rewrites);
|
|
203
|
+
// #1165: admitted attachments are caller paths outside the run directory — do not rewrite.
|
|
204
|
+
if (isRecord(page.principal)) {
|
|
205
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
206
|
+
page.principal,
|
|
207
|
+
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
208
|
+
rewrites,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* #1183: current.json location fields follow the directory being rendered.
|
|
215
|
+
* Fact rows stay as written (append-only); only the rendering projects stored→actual.
|
|
216
|
+
* Returns the same object when already aligned. Caller file-flag paths stay opaque.
|
|
217
|
+
*/
|
|
218
|
+
export function projectRenderedPagePaths(
|
|
219
|
+
page: Record<string, unknown>,
|
|
220
|
+
actualRunDirectory: string,
|
|
221
|
+
): Record<string, unknown> {
|
|
222
|
+
const stored =
|
|
223
|
+
typeof page.runDirectory === "string" && page.runDirectory.trim() !== ""
|
|
224
|
+
? page.runDirectory
|
|
225
|
+
: undefined;
|
|
226
|
+
if (stored === undefined || stored === actualRunDirectory) return page;
|
|
227
|
+
const projected = structuredClone(page);
|
|
228
|
+
const rewrites: readonly RunDirectoryPathRewrite[] = [
|
|
229
|
+
{ oldRunDirectory: stored, newRunDirectory: actualRunDirectory },
|
|
230
|
+
];
|
|
231
|
+
// Admitted field set is the location-field superset of invocation; runState adds
|
|
232
|
+
// principal (covered) and optional currentCourt summons under this run.
|
|
233
|
+
rewriteAdmittedRoleRunPage(projected, rewrites);
|
|
234
|
+
if (isRecord(projected.currentCourt)) {
|
|
235
|
+
rewriteSummonsMaterials(projected.currentCourt.summons, rewrites);
|
|
236
|
+
}
|
|
237
|
+
return projected;
|
|
238
|
+
}
|
|
@@ -1,81 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Durable run-page path rewrite after a run directory moves, plus retained-path
|
|
3
|
+
* matching for live lookup. Pure path rewrite rules live in role-run-path-rewrite.ts
|
|
4
|
+
* (sole authority); this module owns I/O walks that need dossier page readers.
|
|
4
5
|
*
|
|
5
6
|
* Scope is typed machine-consumed path fields only — never free text, never
|
|
6
7
|
* caller file-flag path strings. Nested walk is confined to package-owned
|
|
7
8
|
* `session/` seams.
|
|
8
9
|
*/
|
|
9
10
|
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
-
import { dirname, join
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
11
12
|
|
|
12
13
|
import { activationBookDirectory, physicalPathIdentity } from "./activation-ledger-topology.ts";
|
|
14
|
+
import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE } from "./compliance-transport.ts";
|
|
13
15
|
import { readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
|
|
14
16
|
import { findRoleRunDirectory, roleRunPlacement } from "./role-run-placement.ts";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
import {
|
|
18
|
+
INVOCATION_PAGE_PATH_FIELDS,
|
|
19
|
+
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
20
|
+
RUN_STATE_PAGE_PATH_FIELDS,
|
|
21
|
+
type RunDirectoryPathRewrite,
|
|
22
|
+
projectTurnRequestLiveRunDirectory,
|
|
23
|
+
rewriteAdmittedRoleRunPage,
|
|
24
|
+
rewritePrincipalSessionPaths,
|
|
25
|
+
rewriteRunDirectoryPathFields,
|
|
26
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites,
|
|
27
|
+
rewriteRunDirectoryPathValue,
|
|
28
|
+
rewriteRunDirectoryPathValueAgainstRewrites,
|
|
29
|
+
rewriteSummonsMaterials,
|
|
30
|
+
} from "./role-run-path-rewrite.ts";
|
|
18
31
|
import { readPageSync, updateSectionSync } from "./run-dossier.ts";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
const ADMITTED_PAGE_FIELDS = [
|
|
22
|
-
"runDirectory",
|
|
23
|
-
"admittedRequestPath", // pre-#1161 pages only
|
|
24
|
-
"sessionDirectory",
|
|
25
|
-
"sessionFile",
|
|
26
|
-
// #1165/#1168: caller file-flag paths (requestManifestPath, prerequisitesPath)
|
|
27
|
-
// stay as given. Obsolete copy fields (taskPath/packetPath) are not rewritten —
|
|
28
|
-
// stock volumes keep their bytes; live relocate no longer maintains them.
|
|
29
|
-
"mergerInputPath",
|
|
30
|
-
"sourceRunPath",
|
|
31
|
-
] as const;
|
|
32
|
-
|
|
33
|
-
const INVOCATION_PAGE_FIELDS = [
|
|
34
|
-
"runDirectory",
|
|
35
|
-
"sessionDirectory",
|
|
36
|
-
"sessionFile",
|
|
37
|
-
] as const;
|
|
38
|
-
|
|
39
|
-
const RUN_STATE_PAGE_FIELDS = [
|
|
40
|
-
"runDirectory",
|
|
41
|
-
"admittedRequestPath", // pre-#1161 pages only
|
|
42
|
-
"sessionDirectory",
|
|
43
|
-
"sessionFile",
|
|
44
|
-
] as const;
|
|
32
|
+
import { isEnoent, isRecord } from "./unknown-value.ts";
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
// Re-export the pure path API so existing import sites stay on this module path.
|
|
35
|
+
export {
|
|
36
|
+
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
37
|
+
type RunDirectoryPathRewrite,
|
|
38
|
+
projectTurnRequestLiveRunDirectory,
|
|
39
|
+
rewriteAdmittedRoleRunPage,
|
|
40
|
+
rewritePrincipalSessionPaths,
|
|
41
|
+
rewriteRunDirectoryPathFields,
|
|
42
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites,
|
|
43
|
+
rewriteRunDirectoryPathValue,
|
|
44
|
+
rewriteRunDirectoryPathValueAgainstRewrites,
|
|
45
|
+
} from "./role-run-path-rewrite.ts";
|
|
51
46
|
|
|
52
|
-
const SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"] as const;
|
|
53
|
-
const SUMMONS_PATH_FIELDS = ["sourceRunPath"] as const;
|
|
54
47
|
const OFFICER_POINTER_FIELDS = ["sessionFile", "runDirectory"] as const;
|
|
55
48
|
const SITIAN_RECORD_FIELDS = ["sessionParent"] as const;
|
|
56
49
|
const SESSION_HEADER_FIELDS = ["parentSession"] as const;
|
|
57
50
|
const BINDING_PARENT_FIELDS = ["sessionFile"] as const;
|
|
58
51
|
|
|
59
|
-
export type RunDirectoryPathRewrite = {
|
|
60
|
-
readonly oldRunDirectory: string;
|
|
61
|
-
readonly newRunDirectory: string;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/** Rewrite one path value that points at or under oldRunDirectory. */
|
|
65
|
-
export function rewriteRunDirectoryPathValue(
|
|
66
|
-
value: unknown,
|
|
67
|
-
oldRunDirectory: string,
|
|
68
|
-
newRunDirectory: string,
|
|
69
|
-
): unknown {
|
|
70
|
-
if (typeof value !== "string") return value;
|
|
71
|
-
if (value === oldRunDirectory) return newRunDirectory;
|
|
72
|
-
const prefix = `${oldRunDirectory}${sep}`;
|
|
73
|
-
if (value.startsWith(prefix)) {
|
|
74
|
-
return `${newRunDirectory}${value.slice(oldRunDirectory.length)}`;
|
|
75
|
-
}
|
|
76
|
-
return value;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
52
|
/** Match physical aliases or a normal unbound→ticket bind, never historical migration layouts. */
|
|
80
53
|
export async function retainedRunPathsMatch(
|
|
81
54
|
recordedPath: unknown,
|
|
@@ -109,100 +82,6 @@ export async function retainedRunPathsMatch(
|
|
|
109
82
|
return false;
|
|
110
83
|
}
|
|
111
84
|
|
|
112
|
-
/**
|
|
113
|
-
* Apply the longest matching oldRunDirectory rewrite. Longer keys win so a
|
|
114
|
-
* nested run path is not partially claimed by a shorter sibling prefix.
|
|
115
|
-
*/
|
|
116
|
-
export function rewriteRunDirectoryPathValueAgainstRewrites(
|
|
117
|
-
value: unknown,
|
|
118
|
-
rewrites: readonly RunDirectoryPathRewrite[],
|
|
119
|
-
): unknown {
|
|
120
|
-
if (typeof value !== "string" || rewrites.length === 0) return value;
|
|
121
|
-
let best: RunDirectoryPathRewrite | undefined;
|
|
122
|
-
for (const rewrite of rewrites) {
|
|
123
|
-
if (
|
|
124
|
-
value === rewrite.oldRunDirectory ||
|
|
125
|
-
value.startsWith(`${rewrite.oldRunDirectory}${sep}`)
|
|
126
|
-
) {
|
|
127
|
-
if (
|
|
128
|
-
best === undefined ||
|
|
129
|
-
rewrite.oldRunDirectory.length > best.oldRunDirectory.length
|
|
130
|
-
) {
|
|
131
|
-
best = rewrite;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (best === undefined) return value;
|
|
136
|
-
return rewriteRunDirectoryPathValue(
|
|
137
|
-
value,
|
|
138
|
-
best.oldRunDirectory,
|
|
139
|
-
best.newRunDirectory,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** In-place rewrite of selected fields on one record (single pair). */
|
|
144
|
-
export function rewriteRunDirectoryPathFields(
|
|
145
|
-
record: Record<string, unknown>,
|
|
146
|
-
fields: readonly string[],
|
|
147
|
-
oldRunDirectory: string,
|
|
148
|
-
newRunDirectory: string,
|
|
149
|
-
): void {
|
|
150
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(record, fields, [
|
|
151
|
-
{ oldRunDirectory, newRunDirectory },
|
|
152
|
-
]);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Principal session paths only — shared live + durable projection after a run move. */
|
|
156
|
-
export function rewritePrincipalSessionPaths(
|
|
157
|
-
principal: Record<string, unknown>,
|
|
158
|
-
oldRunDirectory: string,
|
|
159
|
-
newRunDirectory: string,
|
|
160
|
-
): void {
|
|
161
|
-
rewriteRunDirectoryPathFields(
|
|
162
|
-
principal,
|
|
163
|
-
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
164
|
-
oldRunDirectory,
|
|
165
|
-
newRunDirectory,
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Live turn request: move runDirectory and rewrite principal session paths in place.
|
|
171
|
-
* Callers that lack authority.seal use this; admitted refresh seals after the same rewrite.
|
|
172
|
-
*/
|
|
173
|
-
export function projectTurnRequestLiveRunDirectory(
|
|
174
|
-
request: {
|
|
175
|
-
runDirectory: string;
|
|
176
|
-
principal: { sessionDirectory?: string; sessionFile?: string };
|
|
177
|
-
},
|
|
178
|
-
newRunDirectory: string,
|
|
179
|
-
): void {
|
|
180
|
-
const old = request.runDirectory;
|
|
181
|
-
if (old === newRunDirectory) return;
|
|
182
|
-
request.runDirectory = newRunDirectory;
|
|
183
|
-
rewritePrincipalSessionPaths(
|
|
184
|
-
request.principal as Record<string, unknown>,
|
|
185
|
-
old,
|
|
186
|
-
newRunDirectory,
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** In-place rewrite of selected fields against a rewrite set. */
|
|
191
|
-
export function rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
192
|
-
record: Record<string, unknown>,
|
|
193
|
-
fields: readonly string[],
|
|
194
|
-
rewrites: readonly RunDirectoryPathRewrite[],
|
|
195
|
-
): void {
|
|
196
|
-
for (const field of fields) {
|
|
197
|
-
if (field in record) {
|
|
198
|
-
record[field] = rewriteRunDirectoryPathValueAgainstRewrites(
|
|
199
|
-
record[field],
|
|
200
|
-
rewrites,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
85
|
function collectRewrites(input: {
|
|
207
86
|
readonly oldRunDirectory: string;
|
|
208
87
|
readonly newRunDirectory: string;
|
|
@@ -229,51 +108,6 @@ function collectRewrites(input: {
|
|
|
229
108
|
return out;
|
|
230
109
|
}
|
|
231
110
|
|
|
232
|
-
/** Typed notary/source-run locator: only runDirectory is a path. */
|
|
233
|
-
function rewriteSourceRunLocator(
|
|
234
|
-
value: unknown,
|
|
235
|
-
rewrites: readonly RunDirectoryPathRewrite[],
|
|
236
|
-
): void {
|
|
237
|
-
if (!isRecord(value)) return;
|
|
238
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
239
|
-
value,
|
|
240
|
-
SOURCE_RUN_LOCATOR_FIELDS,
|
|
241
|
-
rewrites,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** Same-ticket summons materials: typed source-run; caller attach paths stay as-is (#1165). */
|
|
246
|
-
function rewriteSummonsMaterials(
|
|
247
|
-
value: unknown,
|
|
248
|
-
rewrites: readonly RunDirectoryPathRewrite[],
|
|
249
|
-
): void {
|
|
250
|
-
if (!isRecord(value)) return;
|
|
251
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
252
|
-
value,
|
|
253
|
-
SUMMONS_PATH_FIELDS,
|
|
254
|
-
rewrites,
|
|
255
|
-
);
|
|
256
|
-
rewriteSourceRunLocator(value.sourceRun, rewrites);
|
|
257
|
-
// #1165: summons.attachmentPaths are caller paths — do not rewrite.
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/** Project one admitted page in memory after its containing run moved. */
|
|
261
|
-
export function rewriteAdmittedRoleRunPage(
|
|
262
|
-
page: Record<string, unknown>,
|
|
263
|
-
rewrites: readonly RunDirectoryPathRewrite[],
|
|
264
|
-
): void {
|
|
265
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(page, ADMITTED_PAGE_FIELDS, rewrites);
|
|
266
|
-
rewriteSourceRunLocator(page.sourceRun, rewrites);
|
|
267
|
-
// #1165: admitted attachments are caller paths outside the run directory — do not rewrite.
|
|
268
|
-
if (isRecord(page.principal)) {
|
|
269
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
270
|
-
page.principal,
|
|
271
|
-
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
272
|
-
rewrites,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
111
|
/** Pre-#1161 `session/auditor-roles/<officer>.pointer.json` (migrator input only). */
|
|
278
112
|
async function rewriteLegacyOfficerPointerFile(
|
|
279
113
|
path: string,
|
|
@@ -482,9 +316,9 @@ export async function rewriteRoleRunDurablePages(input: {
|
|
|
482
316
|
const pageRewriters: Readonly<Record<"admitted" | "invocation" | "runState", (page: Record<string, unknown>) => void>> = {
|
|
483
317
|
admitted: (page) => rewriteAdmittedRoleRunPage(page, rewrites),
|
|
484
318
|
invocation: (page) =>
|
|
485
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(page,
|
|
319
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(page, INVOCATION_PAGE_PATH_FIELDS, rewrites),
|
|
486
320
|
runState: (page) => {
|
|
487
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(page,
|
|
321
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(page, RUN_STATE_PAGE_PATH_FIELDS, rewrites);
|
|
488
322
|
if (isRecord(page.principal)) {
|
|
489
323
|
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
490
324
|
page.principal,
|
package/src/role-runtime.ts
CHANGED
|
@@ -16,12 +16,19 @@ import { sitianReport } from "./sitian-facade.ts";
|
|
|
16
16
|
import { sitianReportSafe } from "./host-session-record.ts";
|
|
17
17
|
import { createSubmissionLedgerHost, sealAcceptedSubmission } from "./submission-ledger.ts";
|
|
18
18
|
import { registerFiledSubmissionTool, type FiledSubmissionBeforeAccept } from "./filed-submission.ts";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
registerReportTicketTool,
|
|
21
|
+
reportTicketFromHostContext,
|
|
22
|
+
REPORT_TICKET_TOOL_NAME,
|
|
23
|
+
} from "./report-ticket-tool.ts";
|
|
20
24
|
import type { RoleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
21
25
|
|
|
22
26
|
import { activationTraceRecordSchema, namedActivationCause, type ActivationTraceRecord, type ActivationTraceWriter } from "./activation-trace.ts";
|
|
23
27
|
import { homeFromRunDirectory } from "./activation-ledger-topology.ts";
|
|
24
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
isSafePositiveTicketNumber,
|
|
30
|
+
parseTicketNumber,
|
|
31
|
+
} from "./run-ticket-number.ts";
|
|
25
32
|
import {
|
|
26
33
|
durableSessionPointer,
|
|
27
34
|
resolveBookKeyFromGit,
|
|
@@ -859,11 +866,17 @@ export function createDiaristRoleRuntime(
|
|
|
859
866
|
const status = submitted?.status;
|
|
860
867
|
if (status !== "completed") return parameters;
|
|
861
868
|
const assertion = readDiaristTicketAssertion(submitted);
|
|
862
|
-
const coords = readDiaristRunCoordinates(ctx);
|
|
863
869
|
// #836 7.3: pre-bound ticket is material for the LLM, not an override.
|
|
864
870
|
const ticketNumber = assertion.kind === "ticket" ? assertion.ticketNumber : undefined;
|
|
865
|
-
//
|
|
866
|
-
//
|
|
871
|
+
// #1183: typed ticket assertion is identity acquisition — one mid-turn
|
|
872
|
+
// bind/relocate here, before seal / reask / infra outcome branches.
|
|
873
|
+
// Reuses ak_report_ticket seam; already-placed is a no-op. Host return
|
|
874
|
+
// must not be the first placement (dossier-topology).
|
|
875
|
+
if (ticketNumber !== undefined) {
|
|
876
|
+
await reportTicketFromHostContext(ctx, ticketNumber);
|
|
877
|
+
}
|
|
878
|
+
// Live leaf after possible relocate (commit reads history under runDirectory).
|
|
879
|
+
const coords = readDiaristRunCoordinates(ctx);
|
|
867
880
|
// Strict-schema hosts emit ticketSessions: null for a single ticket.
|
|
868
881
|
const multiTicket = submitted?.ticketSessions != null;
|
|
869
882
|
const singleSessions = submitted && !multiTicket
|
|
@@ -1145,13 +1158,17 @@ export function createRoleRuntimeExtension(
|
|
|
1145
1158
|
await pending;
|
|
1146
1159
|
return pendingNavigatorPresentation?.event;
|
|
1147
1160
|
};
|
|
1148
|
-
projectClosedSubmission = async (closed, context) =>
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1161
|
+
projectClosedSubmission = async (closed, context) => {
|
|
1162
|
+
// #1183: typed ticket identity is acquired on the ledger seam when params
|
|
1163
|
+
// are first read — not here after seal. Closure only projects lifecycle.
|
|
1164
|
+
await projectClosedSubmissionLifecycle(
|
|
1165
|
+
closed,
|
|
1166
|
+
context,
|
|
1167
|
+
navigatorPhase(roleHost, closed.role),
|
|
1168
|
+
() => receiptDelivery.recordAccepted(),
|
|
1169
|
+
settleNavigatorProjection,
|
|
1170
|
+
);
|
|
1171
|
+
};
|
|
1155
1172
|
roleHost.on("input", (_event) => {
|
|
1156
1173
|
const role = roleHost.getFlag(ROLE_FLAG.name);
|
|
1157
1174
|
if (role !== undefined && !admitted) return { action: "handled" as const };
|