@akagilnc/pi-workflow-roles 0.1.5683 → 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/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
|
@@ -9,6 +9,7 @@ import { runDirectoryFromHostContext } from "./host-contracts.js";
|
|
|
9
9
|
import { piDurablePrincipalAuthority } from "./pi/durable-principal.js";
|
|
10
10
|
import { bindAdmittedTicketNumber, relocateAdmittedRunToTicket, } from "./public-cli/invocation.js";
|
|
11
11
|
import { isUnboundRunDirectory, sessionDirectoryOf, sessionFileOf } from "./role-run-placement.js";
|
|
12
|
+
import { rewriteRunDirectoryPathValue } from "./role-run-path-rewrite.js";
|
|
12
13
|
import { readPageSync } from "./run-dossier.js";
|
|
13
14
|
import { readDeclaredTicketNumber } from "./run-ticket-number.js";
|
|
14
15
|
import { isRecord } from "./unknown-value.js";
|
|
@@ -89,6 +90,11 @@ export async function reportTicketFromHostContext(context, rawTicketNumber, auth
|
|
|
89
90
|
// Live handles belong with rename ownership — before derived renderCurrentSync
|
|
90
91
|
// (#1171 F4-R2). Reuse relocate's heldLease.relocate seam; keep raw I/O throw.
|
|
91
92
|
const applyLiveHandles = (nextDirectory) => {
|
|
93
|
+
// Capture live session handle before ownership transfer. After relocate the
|
|
94
|
+
// write handle must share host-authority coords with the principal (#1183):
|
|
95
|
+
// project the prior path onto the new leaf — never rebuild default
|
|
96
|
+
// session.jsonl over a non-default seal (and do not trust a setter rebuild).
|
|
97
|
+
const priorSessionFile = context.sessionManager.getSessionFile?.();
|
|
92
98
|
context.runDirectory = nextDirectory;
|
|
93
99
|
// Same-leg spawn env only (#1171 F1): never overwrite another leg's ambient
|
|
94
100
|
// AK_ROLE_RUN_DIR in a shared process. HostContext is the per-turn authority
|
|
@@ -96,8 +102,12 @@ export async function reportTicketFromHostContext(context, rawTicketNumber, auth
|
|
|
96
102
|
if (process.env.AK_ROLE_RUN_DIR === runDirectory) {
|
|
97
103
|
process.env.AK_ROLE_RUN_DIR = nextDirectory;
|
|
98
104
|
}
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
const projectedSessionFile = typeof priorSessionFile === "string" && priorSessionFile.trim() !== ""
|
|
106
|
+
? rewriteRunDirectoryPathValue(priorSessionFile, runDirectory, nextDirectory)
|
|
107
|
+
: sessionFileOf(nextDirectory);
|
|
108
|
+
if (typeof projectedSessionFile === "string") {
|
|
109
|
+
context.sessionManager.setSessionFile?.(projectedSessionFile);
|
|
110
|
+
}
|
|
101
111
|
};
|
|
102
112
|
const relocation = alreadyPlaced
|
|
103
113
|
? undefined
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
import { isRecord } from "./unknown-value.js";
|
|
11
|
+
export const ADMITTED_PAGE_PATH_FIELDS = [
|
|
12
|
+
"runDirectory",
|
|
13
|
+
"admittedRequestPath", // pre-#1161 pages only
|
|
14
|
+
"sessionDirectory",
|
|
15
|
+
"sessionFile",
|
|
16
|
+
// #1165/#1168: caller file-flag paths stay as given.
|
|
17
|
+
"mergerInputPath",
|
|
18
|
+
"sourceRunPath",
|
|
19
|
+
];
|
|
20
|
+
export const INVOCATION_PAGE_PATH_FIELDS = [
|
|
21
|
+
"runDirectory",
|
|
22
|
+
"sessionDirectory",
|
|
23
|
+
"sessionFile",
|
|
24
|
+
];
|
|
25
|
+
export const RUN_STATE_PAGE_PATH_FIELDS = [
|
|
26
|
+
"runDirectory",
|
|
27
|
+
"admittedRequestPath", // pre-#1161 pages only
|
|
28
|
+
"sessionDirectory",
|
|
29
|
+
"sessionFile",
|
|
30
|
+
];
|
|
31
|
+
/** Typed principal session coordinates rewritten when the owning run leaf moves. */
|
|
32
|
+
export const PRINCIPAL_SESSION_PATH_FIELDS = [
|
|
33
|
+
"sessionDirectory",
|
|
34
|
+
"sessionFile",
|
|
35
|
+
];
|
|
36
|
+
const SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"];
|
|
37
|
+
const SUMMONS_PATH_FIELDS = ["sourceRunPath"];
|
|
38
|
+
/** Rewrite one path value that points at or under oldRunDirectory. */
|
|
39
|
+
export function rewriteRunDirectoryPathValue(value, oldRunDirectory, newRunDirectory) {
|
|
40
|
+
if (typeof value !== "string")
|
|
41
|
+
return value;
|
|
42
|
+
if (value === oldRunDirectory)
|
|
43
|
+
return newRunDirectory;
|
|
44
|
+
const prefix = `${oldRunDirectory}${sep}`;
|
|
45
|
+
if (value.startsWith(prefix)) {
|
|
46
|
+
return `${newRunDirectory}${value.slice(oldRunDirectory.length)}`;
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Apply the longest matching oldRunDirectory rewrite. Longer keys win so a
|
|
52
|
+
* nested run path is not partially claimed by a shorter sibling prefix.
|
|
53
|
+
*/
|
|
54
|
+
export function rewriteRunDirectoryPathValueAgainstRewrites(value, rewrites) {
|
|
55
|
+
if (typeof value !== "string" || rewrites.length === 0)
|
|
56
|
+
return value;
|
|
57
|
+
let best;
|
|
58
|
+
for (const rewrite of rewrites) {
|
|
59
|
+
if (value === rewrite.oldRunDirectory ||
|
|
60
|
+
value.startsWith(`${rewrite.oldRunDirectory}${sep}`)) {
|
|
61
|
+
if (best === undefined ||
|
|
62
|
+
rewrite.oldRunDirectory.length > best.oldRunDirectory.length) {
|
|
63
|
+
best = rewrite;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (best === undefined)
|
|
68
|
+
return value;
|
|
69
|
+
return rewriteRunDirectoryPathValue(value, best.oldRunDirectory, best.newRunDirectory);
|
|
70
|
+
}
|
|
71
|
+
/** In-place rewrite of selected fields on one record (single pair). */
|
|
72
|
+
export function rewriteRunDirectoryPathFields(record, fields, oldRunDirectory, newRunDirectory) {
|
|
73
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(record, fields, [
|
|
74
|
+
{ oldRunDirectory, newRunDirectory },
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
/** In-place rewrite of selected fields against a rewrite set. */
|
|
78
|
+
export function rewriteRunDirectoryPathFieldsAgainstRewrites(record, fields, rewrites) {
|
|
79
|
+
for (const field of fields) {
|
|
80
|
+
if (field in record) {
|
|
81
|
+
record[field] = rewriteRunDirectoryPathValueAgainstRewrites(record[field], rewrites);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Principal session paths only — shared live + durable projection after a run move. */
|
|
86
|
+
export function rewritePrincipalSessionPaths(principal, oldRunDirectory, newRunDirectory) {
|
|
87
|
+
rewriteRunDirectoryPathFields(principal, PRINCIPAL_SESSION_PATH_FIELDS, oldRunDirectory, newRunDirectory);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Live turn request: move runDirectory and keep principal session paths on the
|
|
91
|
+
* new leaf. Sole path = host authority decode → rewrite coords copy → seal
|
|
92
|
+
* (#1183 / #636). No shape-based in-place rewrite and no silent skip.
|
|
93
|
+
*/
|
|
94
|
+
export function projectTurnRequestLiveRunDirectory(request, newRunDirectory, authority) {
|
|
95
|
+
const old = request.runDirectory;
|
|
96
|
+
if (old === newRunDirectory)
|
|
97
|
+
return;
|
|
98
|
+
const coords = { ...authority.decode(request.principal) };
|
|
99
|
+
rewritePrincipalSessionPaths(coords, old, newRunDirectory);
|
|
100
|
+
const sealed = authority.seal(coords);
|
|
101
|
+
request.runDirectory = newRunDirectory;
|
|
102
|
+
request.principal = sealed;
|
|
103
|
+
}
|
|
104
|
+
/** Typed notary/source-run locator: only runDirectory is a path. */
|
|
105
|
+
export function rewriteSourceRunLocator(value, rewrites) {
|
|
106
|
+
if (!isRecord(value))
|
|
107
|
+
return;
|
|
108
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(value, SOURCE_RUN_LOCATOR_FIELDS, rewrites);
|
|
109
|
+
}
|
|
110
|
+
/** Same-ticket summons materials: typed source-run; caller attach paths stay as-is (#1165). */
|
|
111
|
+
export function rewriteSummonsMaterials(value, rewrites) {
|
|
112
|
+
if (!isRecord(value))
|
|
113
|
+
return;
|
|
114
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(value, SUMMONS_PATH_FIELDS, rewrites);
|
|
115
|
+
rewriteSourceRunLocator(value.sourceRun, rewrites);
|
|
116
|
+
// #1165: summons.attachmentPaths are caller paths — do not rewrite.
|
|
117
|
+
}
|
|
118
|
+
/** Project one admitted page in memory after its containing run moved. */
|
|
119
|
+
export function rewriteAdmittedRoleRunPage(page, rewrites) {
|
|
120
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(page, ADMITTED_PAGE_PATH_FIELDS, rewrites);
|
|
121
|
+
rewriteSourceRunLocator(page.sourceRun, rewrites);
|
|
122
|
+
// #1165: admitted attachments are caller paths outside the run directory — do not rewrite.
|
|
123
|
+
if (isRecord(page.principal)) {
|
|
124
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(page.principal, PRINCIPAL_SESSION_PATH_FIELDS, rewrites);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* #1183: current.json location fields follow the directory being rendered.
|
|
129
|
+
* Fact rows stay as written (append-only); only the rendering projects stored→actual.
|
|
130
|
+
* Returns the same object when already aligned. Caller file-flag paths stay opaque.
|
|
131
|
+
*/
|
|
132
|
+
export function projectRenderedPagePaths(page, actualRunDirectory) {
|
|
133
|
+
const stored = typeof page.runDirectory === "string" && page.runDirectory.trim() !== ""
|
|
134
|
+
? page.runDirectory
|
|
135
|
+
: undefined;
|
|
136
|
+
if (stored === undefined || stored === actualRunDirectory)
|
|
137
|
+
return page;
|
|
138
|
+
const projected = structuredClone(page);
|
|
139
|
+
const rewrites = [
|
|
140
|
+
{ oldRunDirectory: stored, newRunDirectory: actualRunDirectory },
|
|
141
|
+
];
|
|
142
|
+
// Admitted field set is the location-field superset of invocation; runState adds
|
|
143
|
+
// principal (covered) and optional currentCourt summons under this run.
|
|
144
|
+
rewriteAdmittedRoleRunPage(projected, rewrites);
|
|
145
|
+
if (isRecord(projected.currentCourt)) {
|
|
146
|
+
rewriteSummonsMaterials(projected.currentCourt.summons, rewrites);
|
|
147
|
+
}
|
|
148
|
+
return projected;
|
|
149
|
+
}
|
|
@@ -1,64 +1,27 @@
|
|
|
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
|
import { activationBookDirectory, physicalPathIdentity } from "./activation-ledger-topology.js";
|
|
13
|
+
import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE } from "./compliance-transport.js";
|
|
12
14
|
import { readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
|
|
13
15
|
import { findRoleRunDirectory, roleRunPlacement } from "./role-run-placement.js";
|
|
14
|
-
import {
|
|
16
|
+
import { INVOCATION_PAGE_PATH_FIELDS, PRINCIPAL_SESSION_PATH_FIELDS, RUN_STATE_PAGE_PATH_FIELDS, projectTurnRequestLiveRunDirectory, rewriteAdmittedRoleRunPage, rewritePrincipalSessionPaths, rewriteRunDirectoryPathFields, rewriteRunDirectoryPathFieldsAgainstRewrites, rewriteRunDirectoryPathValue, rewriteRunDirectoryPathValueAgainstRewrites, rewriteSummonsMaterials, } from "./role-run-path-rewrite.js";
|
|
15
17
|
import { readPageSync, updateSectionSync } from "./run-dossier.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"admittedRequestPath", // pre-#1161 pages only
|
|
20
|
-
"sessionDirectory",
|
|
21
|
-
"sessionFile",
|
|
22
|
-
// #1165/#1168: caller file-flag paths (requestManifestPath, prerequisitesPath)
|
|
23
|
-
// stay as given. Obsolete copy fields (taskPath/packetPath) are not rewritten —
|
|
24
|
-
// stock volumes keep their bytes; live relocate no longer maintains them.
|
|
25
|
-
"mergerInputPath",
|
|
26
|
-
"sourceRunPath",
|
|
27
|
-
];
|
|
28
|
-
const INVOCATION_PAGE_FIELDS = [
|
|
29
|
-
"runDirectory",
|
|
30
|
-
"sessionDirectory",
|
|
31
|
-
"sessionFile",
|
|
32
|
-
];
|
|
33
|
-
const RUN_STATE_PAGE_FIELDS = [
|
|
34
|
-
"runDirectory",
|
|
35
|
-
"admittedRequestPath", // pre-#1161 pages only
|
|
36
|
-
"sessionDirectory",
|
|
37
|
-
"sessionFile",
|
|
38
|
-
];
|
|
39
|
-
/** Typed principal session coordinates rewritten when the owning run leaf moves. */
|
|
40
|
-
export const PRINCIPAL_SESSION_PATH_FIELDS = [
|
|
41
|
-
"sessionDirectory",
|
|
42
|
-
"sessionFile",
|
|
43
|
-
];
|
|
44
|
-
const SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"];
|
|
45
|
-
const SUMMONS_PATH_FIELDS = ["sourceRunPath"];
|
|
18
|
+
import { isEnoent, isRecord } from "./unknown-value.js";
|
|
19
|
+
// Re-export the pure path API so existing import sites stay on this module path.
|
|
20
|
+
export { PRINCIPAL_SESSION_PATH_FIELDS, projectTurnRequestLiveRunDirectory, rewriteAdmittedRoleRunPage, rewritePrincipalSessionPaths, rewriteRunDirectoryPathFields, rewriteRunDirectoryPathFieldsAgainstRewrites, rewriteRunDirectoryPathValue, rewriteRunDirectoryPathValueAgainstRewrites, } from "./role-run-path-rewrite.js";
|
|
46
21
|
const OFFICER_POINTER_FIELDS = ["sessionFile", "runDirectory"];
|
|
47
22
|
const SITIAN_RECORD_FIELDS = ["sessionParent"];
|
|
48
23
|
const SESSION_HEADER_FIELDS = ["parentSession"];
|
|
49
24
|
const BINDING_PARENT_FIELDS = ["sessionFile"];
|
|
50
|
-
/** Rewrite one path value that points at or under oldRunDirectory. */
|
|
51
|
-
export function rewriteRunDirectoryPathValue(value, oldRunDirectory, newRunDirectory) {
|
|
52
|
-
if (typeof value !== "string")
|
|
53
|
-
return value;
|
|
54
|
-
if (value === oldRunDirectory)
|
|
55
|
-
return newRunDirectory;
|
|
56
|
-
const prefix = `${oldRunDirectory}${sep}`;
|
|
57
|
-
if (value.startsWith(prefix)) {
|
|
58
|
-
return `${newRunDirectory}${value.slice(oldRunDirectory.length)}`;
|
|
59
|
-
}
|
|
60
|
-
return value;
|
|
61
|
-
}
|
|
62
25
|
/** Match physical aliases or a normal unbound→ticket bind, never historical migration layouts. */
|
|
63
26
|
export async function retainedRunPathsMatch(recordedPath, currentPath, currentRunDirectory) {
|
|
64
27
|
if (recordedPath === currentPath)
|
|
@@ -93,56 +56,6 @@ export async function retainedRunPathsMatch(recordedPath, currentPath, currentRu
|
|
|
93
56
|
}
|
|
94
57
|
return false;
|
|
95
58
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Apply the longest matching oldRunDirectory rewrite. Longer keys win so a
|
|
98
|
-
* nested run path is not partially claimed by a shorter sibling prefix.
|
|
99
|
-
*/
|
|
100
|
-
export function rewriteRunDirectoryPathValueAgainstRewrites(value, rewrites) {
|
|
101
|
-
if (typeof value !== "string" || rewrites.length === 0)
|
|
102
|
-
return value;
|
|
103
|
-
let best;
|
|
104
|
-
for (const rewrite of rewrites) {
|
|
105
|
-
if (value === rewrite.oldRunDirectory ||
|
|
106
|
-
value.startsWith(`${rewrite.oldRunDirectory}${sep}`)) {
|
|
107
|
-
if (best === undefined ||
|
|
108
|
-
rewrite.oldRunDirectory.length > best.oldRunDirectory.length) {
|
|
109
|
-
best = rewrite;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (best === undefined)
|
|
114
|
-
return value;
|
|
115
|
-
return rewriteRunDirectoryPathValue(value, best.oldRunDirectory, best.newRunDirectory);
|
|
116
|
-
}
|
|
117
|
-
/** In-place rewrite of selected fields on one record (single pair). */
|
|
118
|
-
export function rewriteRunDirectoryPathFields(record, fields, oldRunDirectory, newRunDirectory) {
|
|
119
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(record, fields, [
|
|
120
|
-
{ oldRunDirectory, newRunDirectory },
|
|
121
|
-
]);
|
|
122
|
-
}
|
|
123
|
-
/** Principal session paths only — shared live + durable projection after a run move. */
|
|
124
|
-
export function rewritePrincipalSessionPaths(principal, oldRunDirectory, newRunDirectory) {
|
|
125
|
-
rewriteRunDirectoryPathFields(principal, PRINCIPAL_SESSION_PATH_FIELDS, oldRunDirectory, newRunDirectory);
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Live turn request: move runDirectory and rewrite principal session paths in place.
|
|
129
|
-
* Callers that lack authority.seal use this; admitted refresh seals after the same rewrite.
|
|
130
|
-
*/
|
|
131
|
-
export function projectTurnRequestLiveRunDirectory(request, newRunDirectory) {
|
|
132
|
-
const old = request.runDirectory;
|
|
133
|
-
if (old === newRunDirectory)
|
|
134
|
-
return;
|
|
135
|
-
request.runDirectory = newRunDirectory;
|
|
136
|
-
rewritePrincipalSessionPaths(request.principal, old, newRunDirectory);
|
|
137
|
-
}
|
|
138
|
-
/** In-place rewrite of selected fields against a rewrite set. */
|
|
139
|
-
export function rewriteRunDirectoryPathFieldsAgainstRewrites(record, fields, rewrites) {
|
|
140
|
-
for (const field of fields) {
|
|
141
|
-
if (field in record) {
|
|
142
|
-
record[field] = rewriteRunDirectoryPathValueAgainstRewrites(record[field], rewrites);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
59
|
function collectRewrites(input) {
|
|
147
60
|
const own = {
|
|
148
61
|
oldRunDirectory: input.oldRunDirectory,
|
|
@@ -163,29 +76,6 @@ function collectRewrites(input) {
|
|
|
163
76
|
}
|
|
164
77
|
return out;
|
|
165
78
|
}
|
|
166
|
-
/** Typed notary/source-run locator: only runDirectory is a path. */
|
|
167
|
-
function rewriteSourceRunLocator(value, rewrites) {
|
|
168
|
-
if (!isRecord(value))
|
|
169
|
-
return;
|
|
170
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(value, SOURCE_RUN_LOCATOR_FIELDS, rewrites);
|
|
171
|
-
}
|
|
172
|
-
/** Same-ticket summons materials: typed source-run; caller attach paths stay as-is (#1165). */
|
|
173
|
-
function rewriteSummonsMaterials(value, rewrites) {
|
|
174
|
-
if (!isRecord(value))
|
|
175
|
-
return;
|
|
176
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(value, SUMMONS_PATH_FIELDS, rewrites);
|
|
177
|
-
rewriteSourceRunLocator(value.sourceRun, rewrites);
|
|
178
|
-
// #1165: summons.attachmentPaths are caller paths — do not rewrite.
|
|
179
|
-
}
|
|
180
|
-
/** Project one admitted page in memory after its containing run moved. */
|
|
181
|
-
export function rewriteAdmittedRoleRunPage(page, rewrites) {
|
|
182
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(page, ADMITTED_PAGE_FIELDS, rewrites);
|
|
183
|
-
rewriteSourceRunLocator(page.sourceRun, rewrites);
|
|
184
|
-
// #1165: admitted attachments are caller paths outside the run directory — do not rewrite.
|
|
185
|
-
if (isRecord(page.principal)) {
|
|
186
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(page.principal, PRINCIPAL_SESSION_PATH_FIELDS, rewrites);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
79
|
/** Pre-#1161 `session/auditor-roles/<officer>.pointer.json` (migrator input only). */
|
|
190
80
|
async function rewriteLegacyOfficerPointerFile(path, rewrites) {
|
|
191
81
|
const page = JSON.parse(await readFile(path, "utf8"));
|
|
@@ -371,9 +261,9 @@ export async function rewriteRoleRunDurablePages(input) {
|
|
|
371
261
|
// One rewriter per page kind; each keeps its own field list.
|
|
372
262
|
const pageRewriters = {
|
|
373
263
|
admitted: (page) => rewriteAdmittedRoleRunPage(page, rewrites),
|
|
374
|
-
invocation: (page) => rewriteRunDirectoryPathFieldsAgainstRewrites(page,
|
|
264
|
+
invocation: (page) => rewriteRunDirectoryPathFieldsAgainstRewrites(page, INVOCATION_PAGE_PATH_FIELDS, rewrites),
|
|
375
265
|
runState: (page) => {
|
|
376
|
-
rewriteRunDirectoryPathFieldsAgainstRewrites(page,
|
|
266
|
+
rewriteRunDirectoryPathFieldsAgainstRewrites(page, RUN_STATE_PAGE_PATH_FIELDS, rewrites);
|
|
377
267
|
if (isRecord(page.principal)) {
|
|
378
268
|
rewriteRunDirectoryPathFieldsAgainstRewrites(page.principal, PRINCIPAL_SESSION_PATH_FIELDS, rewrites);
|
|
379
269
|
}
|
package/dist/run-dossier.js
CHANGED
|
@@ -20,6 +20,7 @@ import { randomUUID } from "node:crypto";
|
|
|
20
20
|
import { readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
21
21
|
import { basename, join } from "node:path";
|
|
22
22
|
import { sessionFileOf } from "./role-run-placement.js";
|
|
23
|
+
import { projectRenderedPagePaths } from "./role-run-path-rewrite.js";
|
|
23
24
|
import { RUN_CURRENT_FILE, RUN_HISTORY_FILE, RUN_LOG_FILE, RUN_STATE_FILE, OFFICER_POINTER_RECORD_KIND } from "./run-dossier-files.js";
|
|
24
25
|
import { appendSitianRecord } from "./sitian-appender.js";
|
|
25
26
|
import { parseSitianRecordText } from "./sitian-reader.js";
|
|
@@ -144,8 +145,16 @@ function render(runDirectory, history, state, log, unreadable) {
|
|
|
144
145
|
const whole = {};
|
|
145
146
|
for (const section of ["invocation", "admitted", "runState", "terminal"]) {
|
|
146
147
|
const page = lastPayloadOfKind(state, PAGE_ROW_KIND[section]);
|
|
147
|
-
if (page
|
|
148
|
+
if (page === undefined)
|
|
149
|
+
continue;
|
|
150
|
+
// #1183: location fields track the directory being rendered; fact rows stay put.
|
|
151
|
+
if ((section === "invocation" || section === "admitted" || section === "runState")
|
|
152
|
+
&& isRecord(page)) {
|
|
153
|
+
whole[section] = projectRenderedPagePaths(page, runDirectory);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
148
156
|
whole[section] = page;
|
|
157
|
+
}
|
|
149
158
|
}
|
|
150
159
|
let latest;
|
|
151
160
|
for (const row of history) {
|
package/dist/session-identity.js
CHANGED
|
@@ -16,6 +16,7 @@ export const HOST_SESSION_ID_RECORD_KIND = "host-session-id";
|
|
|
16
16
|
export function createSessionIdentityAuthority(authority, hostName) {
|
|
17
17
|
const runDirectoryOf = (principal) => dirname(authority.decode(principal).sessionDirectory);
|
|
18
18
|
return {
|
|
19
|
+
principalAuthority: authority,
|
|
19
20
|
resolveSessionFile(principal) {
|
|
20
21
|
return authority.decode(principal).sessionFile;
|
|
21
22
|
},
|
|
@@ -6,7 +6,9 @@ import { readSitianRecords, resolveSitianRecordPathInLedger, sitianReport } from
|
|
|
6
6
|
import { findRunDirectoryById } from "./public-cli/run-lifecycle.js";
|
|
7
7
|
import { isCorrectableExecuteError } from "./submission-correctable-error.js";
|
|
8
8
|
import { failOnInfrastructureFailureDeclaration, infrastructureFailureDiagnostic } from "./package-contracts/terminating-infrastructure.js";
|
|
9
|
+
import { reportTicketFromHostContext } from "./report-ticket-tool.js";
|
|
9
10
|
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.js";
|
|
11
|
+
import { readDeclaredTicketNumber } from "./run-ticket-number.js";
|
|
10
12
|
import { isRecord, errorText } from "./unknown-value.js";
|
|
11
13
|
/**
|
|
12
14
|
* Admitted run identity for the ledger subject.
|
|
@@ -374,6 +376,15 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
|
|
|
374
376
|
host.registerTool({
|
|
375
377
|
...tool,
|
|
376
378
|
async execute(toolCallId, params, signal, update, context) {
|
|
379
|
+
// #1183: valid typed ticket on the submission is identity acquisition —
|
|
380
|
+
// bind/relocate once here, before reask / ledger restore / seal outcomes.
|
|
381
|
+
// Host return must not be the first placement (dossier-topology).
|
|
382
|
+
const typedTicket = isRecord(params)
|
|
383
|
+
? readDeclaredTicketNumber(params.ticketNumber)
|
|
384
|
+
: undefined;
|
|
385
|
+
if (typedTicket !== undefined) {
|
|
386
|
+
await reportTicketFromHostContext(context, typedTicket);
|
|
387
|
+
}
|
|
377
388
|
const runId = runIdentity(context);
|
|
378
389
|
const attemptId = attemptIdentity(context, runId);
|
|
379
390
|
const state = await stateFor(context, runId);
|
package/package.json
CHANGED
|
@@ -42,6 +42,7 @@ function createComposedAcpRoleTurnHost(
|
|
|
42
42
|
request,
|
|
43
43
|
dependencies: config.roleRuntimeDependencies,
|
|
44
44
|
sessionFile: config.sessionIdentity.resolveSessionFile(request.principal),
|
|
45
|
+
principalAuthority: config.sessionIdentity.principalAuthority,
|
|
45
46
|
socketPath: config.socketPath?.(request) ?? `/tmp/ak-acp-mcp-${randomUUID()}.sock`,
|
|
46
47
|
}),
|
|
47
48
|
});
|
|
@@ -382,7 +382,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
382
382
|
// MCP advertises tool.parameters; prepared.jsonSchema is the headless
|
|
383
383
|
// draft-07 stamp of that clone — record the delivered parameters face.
|
|
384
384
|
const { $schema: _headlessStamp, ...deliveredSchema } = prepared.jsonSchema;
|
|
385
|
-
await recordTurnDelivery(await syncTurnRequestLivePlacement(request), {
|
|
385
|
+
await recordTurnDelivery(await syncTurnRequestLivePlacement(request, config.sessionIdentity.principalAuthority), {
|
|
386
386
|
systemPrompt: systemPromptOverride,
|
|
387
387
|
outputSchema: deliveredSchema,
|
|
388
388
|
}, "acp-host");
|
|
@@ -503,7 +503,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
503
503
|
outcome = { ...facts, ...(knownFailure === undefined ? {} : { knownFailure }) };
|
|
504
504
|
}
|
|
505
505
|
if (config.hostName !== "hermes" && sessionOpened && sessionId !== undefined) {
|
|
506
|
-
const live = await trySyncTurnRequestLivePlacement(request);
|
|
506
|
+
const live = await trySyncTurnRequestLivePlacement(request, config.sessionIdentity.principalAuthority);
|
|
507
507
|
if ("runDirectory" in live) {
|
|
508
508
|
try {
|
|
509
509
|
copyAndRecordHostDossier({
|
|
@@ -7,6 +7,7 @@ import { existsSync } from "node:fs";
|
|
|
7
7
|
import { basename } from "node:path";
|
|
8
8
|
|
|
9
9
|
import type {
|
|
10
|
+
DurablePrincipalAuthority,
|
|
10
11
|
RoleTurnHost,
|
|
11
12
|
RoleTurnKnownFailure,
|
|
12
13
|
RoleTurnRequest,
|
|
@@ -46,10 +47,12 @@ export async function resolveLiveRunDirectoryPath(
|
|
|
46
47
|
/**
|
|
47
48
|
* Prefer the request path when it still exists; otherwise re-locate by run id
|
|
48
49
|
* (resolveLiveRunDirectoryPath). Mutates the live request so later turn-record /
|
|
49
|
-
* exit-copy / fault notes stay on the new leaf.
|
|
50
|
+
* exit-copy / fault notes stay on the new leaf. Authority is required so opaque
|
|
51
|
+
* principal wire is resealed — never shape-guessed in place (#1183).
|
|
50
52
|
*/
|
|
51
53
|
export async function syncTurnRequestLivePlacement(
|
|
52
54
|
request: RoleTurnRequest,
|
|
55
|
+
authority: DurablePrincipalAuthority,
|
|
53
56
|
): Promise<string> {
|
|
54
57
|
const live = await resolveLiveRunDirectoryPath(request.runDirectory, request.home);
|
|
55
58
|
if (live === undefined) {
|
|
@@ -59,13 +62,7 @@ export async function syncTurnRequestLivePlacement(
|
|
|
59
62
|
);
|
|
60
63
|
}
|
|
61
64
|
if (live !== request.runDirectory) {
|
|
62
|
-
projectTurnRequestLiveRunDirectory(
|
|
63
|
-
request as {
|
|
64
|
-
runDirectory: string;
|
|
65
|
-
principal: { sessionDirectory?: string; sessionFile?: string };
|
|
66
|
-
},
|
|
67
|
-
live,
|
|
68
|
-
);
|
|
65
|
+
projectTurnRequestLiveRunDirectory(request, live, authority);
|
|
69
66
|
}
|
|
70
67
|
return live;
|
|
71
68
|
}
|
|
@@ -76,37 +73,54 @@ export async function syncTurnRequestLivePlacement(
|
|
|
76
73
|
*/
|
|
77
74
|
export async function trySyncTurnRequestLivePlacement(
|
|
78
75
|
request: RoleTurnRequest,
|
|
76
|
+
authority: DurablePrincipalAuthority,
|
|
79
77
|
): Promise<{ readonly runDirectory: string } | { readonly resolveError: unknown }> {
|
|
80
78
|
try {
|
|
81
|
-
return { runDirectory: await syncTurnRequestLivePlacement(request) };
|
|
79
|
+
return { runDirectory: await syncTurnRequestLivePlacement(request, authority) };
|
|
82
80
|
} catch (resolveError) {
|
|
83
81
|
return { resolveError };
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
/**
|
|
88
|
-
* Retain a package fault on the live leaf when locatable.
|
|
89
|
-
*
|
|
90
|
-
* the
|
|
86
|
+
* Retain a package fault on the live leaf when locatable. Directory lookup only —
|
|
87
|
+
* does not mutate live principal identity (#1183). Lookup failure must not replace
|
|
88
|
+
* the original fault or block later cleanup.
|
|
91
89
|
*/
|
|
92
90
|
export async function retainPackageFaultBesideLivePlacement(
|
|
93
91
|
request: RoleTurnRequest,
|
|
94
92
|
input: { readonly diagnostic: string; readonly error?: unknown },
|
|
95
93
|
): Promise<void> {
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
const resolveDiagnostic =
|
|
99
|
-
`live run path resolve failed beside fault: ${describeErrorIdentity(live.resolveError)}`;
|
|
94
|
+
let runDirectory = request.runDirectory;
|
|
95
|
+
if (!existsSync(runDirectory)) {
|
|
100
96
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
const found = await resolveLiveRunDirectoryPath(request.runDirectory, request.home);
|
|
98
|
+
if (found === undefined) {
|
|
99
|
+
const resolveDiagnostic =
|
|
100
|
+
`live run path resolve failed beside fault: admitted run missing under ${request.home}`;
|
|
101
|
+
try {
|
|
102
|
+
process.stderr.write(`${input.diagnostic}\n`);
|
|
103
|
+
process.stderr.write(`${resolveDiagnostic}\n`);
|
|
104
|
+
} catch {
|
|
105
|
+
// Best-effort presentation beside an already-chosen terminal.
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
runDirectory = found;
|
|
110
|
+
} catch (resolveError) {
|
|
111
|
+
const resolveDiagnostic =
|
|
112
|
+
`live run path resolve failed beside fault: ${describeErrorIdentity(resolveError)}`;
|
|
113
|
+
try {
|
|
114
|
+
process.stderr.write(`${input.diagnostic}\n`);
|
|
115
|
+
process.stderr.write(`${resolveDiagnostic}\n`);
|
|
116
|
+
} catch {
|
|
117
|
+
// Best-effort presentation beside an already-chosen terminal.
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
105
120
|
}
|
|
106
|
-
return;
|
|
107
121
|
}
|
|
108
122
|
await retainPackageFault({
|
|
109
|
-
runDirectory
|
|
123
|
+
runDirectory,
|
|
110
124
|
diagnostic: input.diagnostic,
|
|
111
125
|
...(Object.hasOwn(input, "error") ? { error: input.error } : {}),
|
|
112
126
|
});
|
|
@@ -54,6 +54,7 @@ export function createProductionHeadlessRoleTurnHost(
|
|
|
54
54
|
request,
|
|
55
55
|
dependencies: roleRuntimeDependencies,
|
|
56
56
|
sessionFile: sessionIdentity.resolveSessionFile(request.principal),
|
|
57
|
+
principalAuthority,
|
|
57
58
|
// Same MCP relay as ACP so intermediate AK tools stay reachable;
|
|
58
59
|
// headless adapter projects the row into --mcp-config.
|
|
59
60
|
socketPath: `/tmp/ak-headless-mcp-${randomUUID()}.sock`,
|
|
@@ -564,7 +564,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
564
564
|
}
|
|
565
565
|
|
|
566
566
|
// What this CLI start was given: one history record per start.
|
|
567
|
-
await recordTurnDelivery(await syncTurnRequestLivePlacement(request), {
|
|
567
|
+
await recordTurnDelivery(await syncTurnRequestLivePlacement(request, config.sessionIdentity.principalAuthority), {
|
|
568
568
|
systemPrompt,
|
|
569
569
|
...(startedWithSchema === undefined ? {} : { outputSchema: startedWithSchema }),
|
|
570
570
|
}, "headless-host");
|
|
@@ -748,7 +748,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
748
748
|
} finally {
|
|
749
749
|
// Cleanup must not depend on live-path sync: lookup failure stays beside
|
|
750
750
|
// the original cleanup/copy work and never skips rm(inputsDirectory).
|
|
751
|
-
const live = await trySyncTurnRequestLivePlacement(request);
|
|
751
|
+
const live = await trySyncTurnRequestLivePlacement(request, config.sessionIdentity.principalAuthority);
|
|
752
752
|
const liveRunDirectory = "runDirectory" in live ? live.runDirectory : undefined;
|
|
753
753
|
if (liveRunDirectory === undefined && "resolveError" in live) {
|
|
754
754
|
await retainPackageFaultBesideLivePlacement(request, {
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -500,7 +500,7 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
500
500
|
// bind projects host.sessions; copy records the landing. Host adapters do
|
|
501
501
|
// not write current.json — public bind/settlement seams own that render.
|
|
502
502
|
try {
|
|
503
|
-
await syncTurnRequestLivePlacement(request);
|
|
503
|
+
await syncTurnRequestLivePlacement(request, config.principalAuthority);
|
|
504
504
|
const { sessionFile, sessionDirectory } = config.principalAuthority.decode(request.principal);
|
|
505
505
|
const sessionId = await readPiSessionHeaderId(sessionFile);
|
|
506
506
|
if (sessionId !== undefined) {
|
|
@@ -75,4 +75,6 @@ export type SessionIdentityAuthority = Readonly<{
|
|
|
75
75
|
bind(principal: RoleTurnRequest["principal"], sessionId: string): Promise<void>;
|
|
76
76
|
/** Durable principal session path for layout ownership — not a rebuild source (#617 DK-4). */
|
|
77
77
|
resolveSessionFile(principal: RoleTurnRequest["principal"]): string;
|
|
78
|
+
/** Host authority that sealed the principal — mid-turn relocate reseals through it (#1183). */
|
|
79
|
+
readonly principalAuthority: import("./host-contracts.ts").DurablePrincipalAuthority;
|
|
78
80
|
}>;
|
|
@@ -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
|