@akagilnc/pi-workflow-roles 0.1.4992 → 0.1.4999

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.
@@ -8,6 +8,18 @@ import { lstat, realpath } from "node:fs/promises";
8
8
  import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
9
9
  import { activationBookDirectory, physicalPathIdentity, resolveActivationLedgerHome, } from "./activation-ledger-topology.js";
10
10
  import { findRunDirectoryById, readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
11
+ async function readRetainedIdentity(runDirectory) {
12
+ try {
13
+ return await readRoleRunIdentity(runDirectory);
14
+ }
15
+ catch (error) {
16
+ if (!(error instanceof TypeError || error instanceof SyntaxError))
17
+ throw error;
18
+ throw new NotarySourceRunError("notary --source-run has invalid retained run-state identity", {
19
+ cause: error,
20
+ });
21
+ }
22
+ }
11
23
  // Run directory leaf: <runId>@<role>. Production uses uuidv7 runIds; offline tracers
12
24
  // may use deterministic non-uuid leaves. Role token stays identifier-shaped.
13
25
  const RUN_DIR_NAME = /^([A-Za-z0-9][A-Za-z0-9._-]*)@([A-Za-z][A-Za-z0-9_-]*)$/;
@@ -94,7 +106,7 @@ export async function resolveNotarySourceRunLocator(options) {
94
106
  }
95
107
  // Authoritative retained record only — legal role/state via shared reader (DRY #14).
96
108
  // Identity envelope only: principal payload is not interpreted here.
97
- const runState = await readRoleRunIdentity(real);
109
+ const runState = await readRetainedIdentity(real);
98
110
  if (runState === undefined) {
99
111
  throw new NotarySourceRunError("notary --source-run lacks retained run-state identity");
100
112
  }
@@ -117,7 +129,7 @@ export async function resolveNotarySourceRunLocator(options) {
117
129
  export async function loadNotarySourceRunLocator(path) {
118
130
  const real = await requireRunDirectory(path, path);
119
131
  const identity = parseRunDirectoryName(basename(real));
120
- const runState = await readRoleRunIdentity(real);
132
+ const runState = await readRetainedIdentity(real);
121
133
  if (runState === undefined) {
122
134
  throw new NotarySourceRunError("notary source-run lacks retained run-state identity");
123
135
  }
@@ -401,14 +401,11 @@ export async function appendPiSessionCustomEntry(authority, principal, customTyp
401
401
  timestamp,
402
402
  })}\n`;
403
403
  await appendFile(sessionFile, pointerLine, "utf8");
404
- try {
405
- sitianReport({
406
- level: "event",
407
- kind: "dispatch-error",
408
- sessionParent: sessionFile,
409
- payload: { customType, data },
410
- source: "pi-role-turn-host",
411
- });
412
- }
413
- catch { }
404
+ sitianReport({
405
+ level: "event",
406
+ kind: "dispatch-error",
407
+ sessionParent: sessionFile,
408
+ payload: { customType, data },
409
+ source: "pi-role-turn-host",
410
+ });
414
411
  }
@@ -17654,16 +17654,13 @@ async function appendPiSessionCustomEntry(authority, principal, customType, data
17654
17654
  })}
17655
17655
  `;
17656
17656
  await appendFile(sessionFile, pointerLine, "utf8");
17657
- try {
17658
- sitianReport({
17659
- level: "event",
17660
- kind: "dispatch-error",
17661
- sessionParent: sessionFile,
17662
- payload: { customType, data },
17663
- source: "pi-role-turn-host"
17664
- });
17665
- } catch {
17666
- }
17657
+ sitianReport({
17658
+ level: "event",
17659
+ kind: "dispatch-error",
17660
+ sessionParent: sessionFile,
17661
+ payload: { customType, data },
17662
+ source: "pi-role-turn-host"
17663
+ });
17667
17664
  }
17668
17665
  var INTERNAL_ROLE_ENTRYPOINT_RELATIVE2, EXPLICIT_INTERNAL_LOAD_PROBE_ARGS, execFileAsync;
17669
17666
  var init_role_turn_host = __esm({
@@ -19004,7 +19001,7 @@ function parseCurrentCourtState(raw) {
19004
19001
  ...summons === void 0 ? {} : { summons }
19005
19002
  };
19006
19003
  }
19007
- async function readRoleRunStateDisk(runDirectory) {
19004
+ async function readRoleRunStateRaw(runDirectory) {
19008
19005
  let raw;
19009
19006
  try {
19010
19007
  raw = JSON.parse(await readFile12(join15(runDirectory, RUN_STATE_FILE), "utf8"));
@@ -19012,24 +19009,40 @@ async function readRoleRunStateDisk(runDirectory) {
19012
19009
  if (errorCodeOf2(error) === "ENOENT") return void 0;
19013
19010
  throw error;
19014
19011
  }
19012
+ return raw;
19013
+ }
19014
+ function parseRoleRunIdentity(raw, runDirectory) {
19015
19015
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
19016
- return void 0;
19016
+ throw new TypeError("Invalid run-state.json");
19017
19017
  }
19018
19018
  const record4 = raw;
19019
19019
  if (typeof record4.runId !== "string" || record4.runId.trim() === "") {
19020
- return void 0;
19020
+ throw new TypeError("Invalid run-state.json");
19021
19021
  }
19022
19022
  const role = typeof record4.role === "string" ? packagedRoleMetadata(record4.role)?.role : void 0;
19023
19023
  if (role === void 0) {
19024
- return void 0;
19024
+ throw new TypeError("Invalid run-state.json");
19025
19025
  }
19026
19026
  if (record4.state !== "admitted" && record4.state !== "running" && record4.state !== "resumable" && record4.state !== "terminal") {
19027
- return void 0;
19027
+ throw new TypeError("Invalid run-state.json");
19028
19028
  }
19029
- if (typeof record4.bookKey !== "string") return void 0;
19030
- if (typeof record4.projectRoot !== "string") return void 0;
19031
- if (typeof record4.sessionDirectory !== "string") return void 0;
19032
- if (typeof record4.admittedRequestPath !== "string") return void 0;
19029
+ if (typeof record4.bookKey !== "string") throw new TypeError("Invalid run-state.json");
19030
+ return {
19031
+ runId: record4.runId,
19032
+ role,
19033
+ state: record4.state,
19034
+ bookKey: record4.bookKey,
19035
+ runDirectory
19036
+ };
19037
+ }
19038
+ async function readRoleRunStateDisk(runDirectory) {
19039
+ const raw = await readRoleRunStateRaw(runDirectory);
19040
+ if (raw === void 0) return void 0;
19041
+ const identity = parseRoleRunIdentity(raw, runDirectory);
19042
+ const record4 = raw;
19043
+ if (typeof record4.projectRoot !== "string") throw new TypeError("Invalid run-state.json");
19044
+ if (typeof record4.sessionDirectory !== "string") throw new TypeError("Invalid run-state.json");
19045
+ if (typeof record4.admittedRequestPath !== "string") throw new TypeError("Invalid run-state.json");
19033
19046
  const storedRunDirectory = typeof record4.runDirectory === "string" && record4.runDirectory.trim() !== "" ? record4.runDirectory : runDirectory;
19034
19047
  const runDir = runDirectory;
19035
19048
  const principalWire = {
@@ -19052,10 +19065,10 @@ async function readRoleRunStateDisk(runDirectory) {
19052
19065
  const phase = record4.phase === "plan" || record4.phase === "apply" ? record4.phase : void 0;
19053
19066
  const currentCourt = parseCurrentCourtState(record4.currentCourt);
19054
19067
  return {
19055
- runId: record4.runId,
19056
- role,
19057
- state: record4.state,
19058
- bookKey: record4.bookKey,
19068
+ runId: identity.runId,
19069
+ role: identity.role,
19070
+ state: identity.state,
19071
+ bookKey: identity.bookKey,
19059
19072
  projectRoot: record4.projectRoot,
19060
19073
  runDirectory: runDir,
19061
19074
  admittedRequestPath: rewriteRunDirectoryPathValue(
@@ -19115,15 +19128,8 @@ function materializeRoleRunFromDisk(disk, authority) {
19115
19128
  }
19116
19129
  }
19117
19130
  async function readRoleRunIdentity(runDirectory) {
19118
- const disk = await readRoleRunStateDisk(runDirectory);
19119
- if (disk === void 0) return void 0;
19120
- return {
19121
- runId: disk.runId,
19122
- role: disk.role,
19123
- bookKey: disk.bookKey,
19124
- runDirectory: disk.runDirectory,
19125
- state: disk.state
19126
- };
19131
+ const raw = await readRoleRunStateRaw(runDirectory);
19132
+ return raw === void 0 ? void 0 : parseRoleRunIdentity(raw, runDirectory);
19127
19133
  }
19128
19134
  async function markRunAdmitted(admitted, authority) {
19129
19135
  const { sessionDirectory, sessionFile } = authority.decode(admitted.principal);
@@ -20126,6 +20132,16 @@ var init_run_lifecycle = __esm({
20126
20132
  // src/notary-source-run.ts
20127
20133
  import { dirname as dirname10, isAbsolute as isAbsolute6, join as join16, resolve as resolve7, basename as basename7 } from "node:path";
20128
20134
  import { lstat as lstat3, realpath as realpath3 } from "node:fs/promises";
20135
+ async function readRetainedIdentity(runDirectory) {
20136
+ try {
20137
+ return await readRoleRunIdentity(runDirectory);
20138
+ } catch (error) {
20139
+ if (!(error instanceof TypeError || error instanceof SyntaxError)) throw error;
20140
+ throw new NotarySourceRunError("notary --source-run has invalid retained run-state identity", {
20141
+ cause: error
20142
+ });
20143
+ }
20144
+ }
20129
20145
  function parseRunDirectoryName(name) {
20130
20146
  const match = RUN_DIR_NAME.exec(name);
20131
20147
  if (match === null) return void 0;
@@ -20200,7 +20216,7 @@ async function resolveNotarySourceRunLocator(options) {
20200
20216
  "notary --source-run must resolve to a retained run under the project machine-ledger book"
20201
20217
  );
20202
20218
  }
20203
- const runState = await readRoleRunIdentity(real);
20219
+ const runState = await readRetainedIdentity(real);
20204
20220
  if (runState === void 0) {
20205
20221
  throw new NotarySourceRunError(
20206
20222
  "notary --source-run lacks retained run-state identity"
@@ -27694,7 +27710,7 @@ function resumeTurnRequestProjectionOptions(admitted, request, env, summonsPrepa
27694
27710
  };
27695
27711
  }
27696
27712
  function roleTurnOptions(env, admitted, continuation) {
27697
- const correlationId = admitted.correlationId ?? env.correlationId;
27713
+ const correlationId = env.correlationId ?? admitted.correlationId;
27698
27714
  return {
27699
27715
  packageRoot: env.packageRoot,
27700
27716
  home: env.home,
@@ -28296,10 +28312,12 @@ async function summonPublicRole(options) {
28296
28312
  const {
28297
28313
  loadCredentialProviders: loadCredentialProviders2,
28298
28314
  loadPublicCliConfig: loadPublicCliConfig2,
28299
- resolveEffectiveSeat: resolveEffectiveSeat2
28315
+ resolveEffectiveSeat: resolveEffectiveSeat2,
28316
+ validatePublicCliConfigAxes: validatePublicCliConfigAxes2
28300
28317
  } = await Promise.resolve().then(() => (init_config2(), config_exports));
28301
28318
  const credentials = options.credentials ?? await loadCredentialProviders2(agentDir);
28302
28319
  const config = await loadPublicCliConfig2(home);
28320
+ validatePublicCliConfigAxes2(config, packageRoot2);
28303
28321
  const resolvedSeat = resolveEffectiveSeat2(config, options.role, credentials);
28304
28322
  const inheritedSelection = options.model === void 0 ? void 0 : {
28305
28323
  provider: options.model.provider,
@@ -29914,15 +29932,7 @@ function isRecord12(value) {
29914
29932
  return typeof value === "object" && value !== null && !Array.isArray(value);
29915
29933
  }
29916
29934
  async function readExistingRunLifecycleState(runDirectory) {
29917
- try {
29918
- const raw = JSON.parse(
29919
- await readFile20(join34(runDirectory, "run-state.json"), "utf8")
29920
- );
29921
- if (!isRecord12(raw) || typeof raw.state !== "string") return void 0;
29922
- return raw.state;
29923
- } catch {
29924
- return void 0;
29925
- }
29935
+ return (await readRoleRunIdentity(runDirectory))?.state;
29926
29936
  }
29927
29937
  function ghostLeaseCheckFromAutopsy(autopsy) {
29928
29938
  switch (autopsy.verdict) {
@@ -29957,13 +29967,6 @@ function parseRunDirectoryName2(name) {
29957
29967
  if (at <= 0 || at === name.length - 1) return void 0;
29958
29968
  return { runId: name.slice(0, at), role: name.slice(at + 1) };
29959
29969
  }
29960
- function tryResolveBookKeyFromProjectRoot(projectRoot) {
29961
- try {
29962
- return resolveBookKeyFromGit(projectRoot);
29963
- } catch {
29964
- return void 0;
29965
- }
29966
- }
29967
29970
  async function listLedgerBookNames(booksRoot) {
29968
29971
  try {
29969
29972
  const entries = await readdir8(booksRoot, { withFileTypes: true });
@@ -30144,7 +30147,13 @@ async function classifyScopedRun(input) {
30144
30147
  reasons.push(errorText3(error));
30145
30148
  }
30146
30149
  }
30147
- const lifecycle = await readExistingRunLifecycleState(input.runDirectory);
30150
+ let lifecycle;
30151
+ try {
30152
+ lifecycle = await readExistingRunLifecycleState(input.runDirectory);
30153
+ } catch (error) {
30154
+ missingSources.push("run-state");
30155
+ reasons.push(errorText3(error));
30156
+ }
30148
30157
  let ghostLeg;
30149
30158
  if (lifecycle === "admitted" || lifecycle === "running") {
30150
30159
  const ghost = await classifyGhostCandidate({
@@ -30253,8 +30262,8 @@ async function scanAnalystIssueRuns(input) {
30253
30262
  wholeBook = true;
30254
30263
  }
30255
30264
  } else if (input.projectRoot !== void 0) {
30256
- const resolved = tryResolveBookKeyFromProjectRoot(input.projectRoot);
30257
- if (resolved !== void 0) {
30265
+ const resolved = resolveAnalystBookKey(input.projectRoot);
30266
+ if (!isSyntheticAnalystBookKey(resolved)) {
30258
30267
  bookNames = [resolved];
30259
30268
  wholeBook = true;
30260
30269
  } else {
@@ -30330,7 +30339,7 @@ var LIVE_RUN_STATES;
30330
30339
  var init_analyst_ledger = __esm({
30331
30340
  "src/analyst-ledger.ts"() {
30332
30341
  "use strict";
30333
- init_activation_ledger_git();
30342
+ init_analyst_book_key();
30334
30343
  init_activation_ledger_topology();
30335
30344
  init_role_run_placement();
30336
30345
  init_run_lifecycle();
@@ -30998,12 +31007,6 @@ function isRecord14(value) {
30998
31007
  function wallMsFromSpan2(startedAt, endedAt) {
30999
31008
  return Date.parse(endedAt) - Date.parse(startedAt);
31000
31009
  }
31001
- function readOutcomeStatus(body) {
31002
- if (!isRecord14(body.outcome)) return void 0;
31003
- const status = body.outcome.status;
31004
- if (typeof status !== "string" || status.trim() === "") return void 0;
31005
- return status;
31006
- }
31007
31010
  function readClassCount(body) {
31008
31011
  if (!isRecord14(body.outcome)) return void 0;
31009
31012
  if (!isRecord14(body.outcome.decisiveFacts)) return void 0;
@@ -31023,7 +31026,7 @@ function projectTerminal(facts) {
31023
31026
  if (facts.terminal.file === "audit-incomplete.json") {
31024
31027
  return { kind: "death", channel: "audit-incomplete" };
31025
31028
  }
31026
- const status = readOutcomeStatus(facts.terminal.body);
31029
+ const status = extractStatus(facts.terminal.body);
31027
31030
  const classCount = readClassCount(facts.terminal.body);
31028
31031
  const receiptStatus = status ?? "unparsed";
31029
31032
  if (classCount === void 0) {
@@ -31092,6 +31095,7 @@ var roundTimelineFamily, round_timeline_default;
31092
31095
  var init_round_timeline = __esm({
31093
31096
  "src/analyst-metric-families/round-timeline.ts"() {
31094
31097
  "use strict";
31098
+ init_acceptance_success_rework();
31095
31099
  roundTimelineFamily = {
31096
31100
  id: "round-timeline",
31097
31101
  contribute(input) {
@@ -877,7 +877,7 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
877
877
  }
878
878
  /** Shared new-turn and in-call auto-resume projection. Seat code supplies prompt and activation. */
879
879
  export function roleTurnOptions(env, admitted, continuation) {
880
- const correlationId = admitted.correlationId ?? env.correlationId;
880
+ const correlationId = env.correlationId ?? admitted.correlationId;
881
881
  return {
882
882
  packageRoot: env.packageRoot,
883
883
  home: env.home,
@@ -132,7 +132,7 @@ function parseCurrentCourtState(raw) {
132
132
  ...(summons === undefined ? {} : { summons }),
133
133
  };
134
134
  }
135
- async function readRoleRunStateDisk(runDirectory) {
135
+ async function readRoleRunStateRaw(runDirectory) {
136
136
  let raw;
137
137
  try {
138
138
  raw = JSON.parse(await readFile(join(runDirectory, RUN_STATE_FILE), "utf8"));
@@ -148,31 +148,48 @@ async function readRoleRunStateDisk(runDirectory) {
148
148
  return undefined;
149
149
  throw error;
150
150
  }
151
+ return raw;
152
+ }
153
+ function parseRoleRunIdentity(raw, runDirectory) {
151
154
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
152
- return undefined;
155
+ throw new TypeError("Invalid run-state.json");
153
156
  }
154
157
  const record = raw;
155
158
  if (typeof record.runId !== "string" || record.runId.trim() === "") {
156
- return undefined;
159
+ throw new TypeError("Invalid run-state.json");
157
160
  }
158
161
  const role = typeof record.role === "string" ? packagedRoleMetadata(record.role)?.role : undefined;
159
162
  if (role === undefined) {
160
- return undefined;
163
+ throw new TypeError("Invalid run-state.json");
161
164
  }
162
165
  if (record.state !== "admitted" &&
163
166
  record.state !== "running" &&
164
167
  record.state !== "resumable" &&
165
168
  record.state !== "terminal") {
166
- return undefined;
169
+ throw new TypeError("Invalid run-state.json");
167
170
  }
168
171
  if (typeof record.bookKey !== "string")
172
+ throw new TypeError("Invalid run-state.json");
173
+ return {
174
+ runId: record.runId,
175
+ role,
176
+ state: record.state,
177
+ bookKey: record.bookKey,
178
+ runDirectory,
179
+ };
180
+ }
181
+ async function readRoleRunStateDisk(runDirectory) {
182
+ const raw = await readRoleRunStateRaw(runDirectory);
183
+ if (raw === undefined)
169
184
  return undefined;
185
+ const identity = parseRoleRunIdentity(raw, runDirectory);
186
+ const record = raw;
170
187
  if (typeof record.projectRoot !== "string")
171
- return undefined;
188
+ throw new TypeError("Invalid run-state.json");
172
189
  if (typeof record.sessionDirectory !== "string")
173
- return undefined;
190
+ throw new TypeError("Invalid run-state.json");
174
191
  if (typeof record.admittedRequestPath !== "string")
175
- return undefined;
192
+ throw new TypeError("Invalid run-state.json");
176
193
  const storedRunDirectory = typeof record.runDirectory === "string" && record.runDirectory.trim() !== ""
177
194
  ? record.runDirectory
178
195
  : runDirectory;
@@ -201,10 +218,10 @@ async function readRoleRunStateDisk(runDirectory) {
201
218
  : undefined;
202
219
  const currentCourt = parseCurrentCourtState(record.currentCourt);
203
220
  return {
204
- runId: record.runId,
205
- role,
206
- state: record.state,
207
- bookKey: record.bookKey,
221
+ runId: identity.runId,
222
+ role: identity.role,
223
+ state: identity.state,
224
+ bookKey: identity.bookKey,
208
225
  projectRoot: record.projectRoot,
209
226
  runDirectory: runDir,
210
227
  admittedRequestPath: rewriteRunDirectoryPathValue(record.admittedRequestPath, storedRunDirectory, runDirectory),
@@ -273,16 +290,8 @@ export async function readRoleRunState(runDirectory, authority) {
273
290
  * Used by notary locator / role peek that never consume session coordinates.
274
291
  */
275
292
  export async function readRoleRunIdentity(runDirectory) {
276
- const disk = await readRoleRunStateDisk(runDirectory);
277
- if (disk === undefined)
278
- return undefined;
279
- return {
280
- runId: disk.runId,
281
- role: disk.role,
282
- bookKey: disk.bookKey,
283
- runDirectory: disk.runDirectory,
284
- state: disk.state,
285
- };
293
+ const raw = await readRoleRunStateRaw(runDirectory);
294
+ return raw === undefined ? undefined : parseRoleRunIdentity(raw, runDirectory);
286
295
  }
287
296
  export async function markRunAdmitted(admitted, authority) {
288
297
  const { sessionDirectory, sessionFile } = authority.decode(admitted.principal);
@@ -148,10 +148,12 @@ async function summonPublicRole(options) {
148
148
  const {
149
149
  loadCredentialProviders,
150
150
  loadPublicCliConfig,
151
- resolveEffectiveSeat
151
+ resolveEffectiveSeat,
152
+ validatePublicCliConfigAxes
152
153
  } = await import("./public-cli/config.js");
153
154
  const credentials = options.credentials ?? await loadCredentialProviders(agentDir);
154
155
  const config = await loadPublicCliConfig(home);
156
+ validatePublicCliConfigAxes(config, packageRoot);
155
157
  const resolvedSeat = resolveEffectiveSeat(config, options.role, credentials);
156
158
  const inheritedSelection = options.model === void 0 ? void 0 : {
157
159
  provider: options.model.provider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.4992",
3
+ "version": "0.1.4999",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -24,7 +24,7 @@
24
24
 
25
25
  **三、相抵** 票面,相关票与在飞分支与法源有无抵触。
26
26
 
27
- **四、缺失** 法源要求的,票面有没有漏:适用 ADR 未被引用即缺失——漏引的要一次列全、给出路径,不要这轮点一份、下轮再点一份;引用写 `docs/adr/...md` 路径即可,票面不必复述其决定;只有票面与某条决定相抵、或把某条决定留给别的票时,才须在票面写明「由 #x 承接该条」;验收面须覆盖票面每条主张;前置产物、决定、答复须在;承重主张的源头不存在、不支持、已过时,同为缺失。缺失列表在能力范围内一次扫完,尽量避免因缺失导致多轮往返。
27
+ **四、缺失** 法源要求的,票面有没有漏:适用法只查两类:票面与某条 ADR 决定相抵的,和把某条决定留给别的票承接的,票面须写明该条与「由 #x 承接」;这两类之外的 ADR 没引不算缺失,不得为此封驳;验收面须覆盖票面每条主张;票面里每一条行为都须答得出《送庭前三问》第一问——由哪条接缝、哪条 run 担它:暂停的是谁、resume 谁、谁接着走这类没有名字的行为,即缺失;前置产物、决定、答复须在;承重主张的源头不存在、不支持、已过时,同为缺失。缺失列表在能力范围内一次扫完,尽量避免因缺失导致多轮往返。
28
28
 
29
29
  **五、擅加** 票面有没有超出法源的机制、约束、验收、术语。
30
30
 
@@ -15,14 +15,14 @@
15
15
  import { readdir, readFile } from "node:fs/promises";
16
16
  import { basename, join } from "node:path";
17
17
 
18
- import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
18
+ import { isSyntheticAnalystBookKey, resolveAnalystBookKey } from "./analyst-book-key.ts";
19
19
  import {
20
20
  pathContainedIn,
21
21
  physicalPathIdentity,
22
22
  resolveActivationLedgerHome,
23
23
  } from "./activation-ledger-topology.ts";
24
24
  import { listBookRunDirectories } from "./role-run-placement.ts";
25
- import { autopsyWriterLock } from "./public-cli/run-lifecycle.ts";
25
+ import { autopsyWriterLock, readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
26
26
  import { readRunTicketNumber } from "./run-ticket-number.ts";
27
27
  import {
28
28
  extractSessionModelSequence,
@@ -77,15 +77,7 @@ const LIVE_RUN_STATES = new Set(["admitted", "running", "resumable"]);
77
77
  async function readExistingRunLifecycleState(
78
78
  runDirectory: string,
79
79
  ): Promise<string | undefined> {
80
- try {
81
- const raw: unknown = JSON.parse(
82
- await readFile(join(runDirectory, "run-state.json"), "utf8"),
83
- );
84
- if (!isRecord(raw) || typeof raw.state !== "string") return undefined;
85
- return raw.state;
86
- } catch {
87
- return undefined;
88
- }
80
+ return (await readRoleRunIdentity(runDirectory))?.state;
89
81
  }
90
82
 
91
83
  /**
@@ -165,15 +157,6 @@ type InvocationScopeFields = {
165
157
  readonly ticketNumber?: number;
166
158
  };
167
159
 
168
- /** Best-effort book key from a projectRoot git common-dir; undefined when not a git tree. */
169
- function tryResolveBookKeyFromProjectRoot(projectRoot: string): string | undefined {
170
- try {
171
- return resolveBookKeyFromGit(projectRoot);
172
- } catch {
173
- return undefined;
174
- }
175
- }
176
-
177
160
  async function listLedgerBookNames(booksRoot: string): Promise<string[]> {
178
161
  try {
179
162
  const entries = await readdir(booksRoot, { withFileTypes: true });
@@ -509,7 +492,13 @@ async function classifyScopedRun(input: {
509
492
  // #855: writer-lease ghost check is independent of terminal artifact status.
510
493
  // present → still emit metrics; unreadable → still enter missingSources;
511
494
  // admitted|running + non-live lease → still list in ghostLegs (ticket #4).
512
- const lifecycle = await readExistingRunLifecycleState(input.runDirectory);
495
+ let lifecycle: string | undefined;
496
+ try {
497
+ lifecycle = await readExistingRunLifecycleState(input.runDirectory);
498
+ } catch (error) {
499
+ missingSources.push("run-state");
500
+ reasons.push(errorText(error));
501
+ }
513
502
  let ghostLeg: AnalystGhostLeg | undefined;
514
503
  if (lifecycle === "admitted" || lifecycle === "running") {
515
504
  const ghost = await classifyGhostCandidate({
@@ -667,8 +656,8 @@ export async function scanAnalystIssueRuns(input: {
667
656
  wholeBook = true;
668
657
  }
669
658
  } else if (input.projectRoot !== undefined) {
670
- const resolved = tryResolveBookKeyFromProjectRoot(input.projectRoot);
671
- if (resolved !== undefined) {
659
+ const resolved = resolveAnalystBookKey(input.projectRoot);
660
+ if (!isSyntheticAnalystBookKey(resolved)) {
672
661
  bookNames = [resolved];
673
662
  wholeBook = true;
674
663
  } else {
@@ -117,7 +117,7 @@ function findCollectorGroups(body: Record<string, unknown>): unknown {
117
117
  }
118
118
 
119
119
  /** Extract role receipt status from canonical terminal body faces. */
120
- function extractStatus(body: Record<string, unknown>): string | undefined {
120
+ export function extractStatus(body: Record<string, unknown>): string | undefined {
121
121
  const outcome = body.outcome;
122
122
  if (isRecord(outcome) && typeof outcome.status === "string" && outcome.status.trim() !== "") {
123
123
  return outcome.status;
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import type { AnalystReadableRunFacts } from "../analyst-ledger.ts";
12
12
  import type { AnalystMetricFamilyModule } from "../analyst-metric-family.ts";
13
+ import { extractStatus } from "./acceptance-success-rework.ts";
13
14
  import type {
14
15
  AnalystFirstFrameAt,
15
16
  AnalystMissingSource,
@@ -73,13 +74,6 @@ function wallMsFromSpan(startedAt: string, endedAt: string): number {
73
74
  return Date.parse(endedAt) - Date.parse(startedAt);
74
75
  }
75
76
 
76
- function readOutcomeStatus(body: Record<string, unknown>): string | undefined {
77
- if (!isRecord(body.outcome)) return undefined;
78
- const status = body.outcome.status;
79
- if (typeof status !== "string" || status.trim() === "") return undefined;
80
- return status;
81
- }
82
-
83
77
  function readClassCount(body: Record<string, unknown>): number | undefined {
84
78
  if (!isRecord(body.outcome)) return undefined;
85
79
  if (!isRecord(body.outcome.decisiveFacts)) return undefined;
@@ -101,7 +95,7 @@ function projectTerminal(facts: AnalystReadableRunFacts): AnalystRoundTimelineTe
101
95
  return { kind: "death", channel: "audit-incomplete" };
102
96
  }
103
97
  // report.json — receipt face; classCount only when producer wrote a number.
104
- const status = readOutcomeStatus(facts.terminal.body);
98
+ const status = extractStatus(facts.terminal.body);
105
99
  const classCount = readClassCount(facts.terminal.body);
106
100
  const receiptStatus = status ?? "unparsed";
107
101
  if (classCount === undefined) {
@@ -21,6 +21,7 @@ import { composeAnalystMetricFamilySections } from "./analyst-metric-family.ts";
21
21
 
22
22
  /** Required run sources that may render a loud unreadable exclusion. */
23
23
  export type AnalystMissingSource =
24
+ | "run-state"
24
25
  | "session-timeline"
25
26
  | "tool-association"
26
27
  | "terminal-artifact"
@@ -217,7 +217,10 @@ function buildMethodContext(activation: CollectorActivation): string {
217
217
  function parsePositiveTicket(raw: unknown, label: string): number | undefined {
218
218
  if (raw === undefined || raw === null) return undefined;
219
219
  if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) return raw;
220
- if (typeof raw === "string" && /^[1-9]\d*$/.test(raw.trim())) return Number(raw.trim());
220
+ if (typeof raw === "string" && /^[1-9]\d*$/.test(raw.trim())) {
221
+ const value = Number(raw.trim());
222
+ if (Number.isSafeInteger(value)) return value;
223
+ }
221
224
  throw new CollectorTargetBindError(`通进司绑定 ${label} 须为正安全整数`);
222
225
  }
223
226
 
@@ -15,6 +15,17 @@ import {
15
15
  import type { NotarySourceRunLocator } from "./notary-contracts.ts";
16
16
  import { findRunDirectoryById, readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
17
17
 
18
+ async function readRetainedIdentity(runDirectory: string) {
19
+ try {
20
+ return await readRoleRunIdentity(runDirectory);
21
+ } catch (error) {
22
+ if (!(error instanceof TypeError || error instanceof SyntaxError)) throw error;
23
+ throw new NotarySourceRunError("notary --source-run has invalid retained run-state identity", {
24
+ cause: error,
25
+ });
26
+ }
27
+ }
28
+
18
29
  // Run directory leaf: <runId>@<role>. Production uses uuidv7 runIds; offline tracers
19
30
  // may use deterministic non-uuid leaves. Role token stays identifier-shaped.
20
31
  const RUN_DIR_NAME =
@@ -131,7 +142,7 @@ export async function resolveNotarySourceRunLocator(options: {
131
142
 
132
143
  // Authoritative retained record only — legal role/state via shared reader (DRY #14).
133
144
  // Identity envelope only: principal payload is not interpreted here.
134
- const runState = await readRoleRunIdentity(real);
145
+ const runState = await readRetainedIdentity(real);
135
146
  if (runState === undefined) {
136
147
  throw new NotarySourceRunError(
137
148
  "notary --source-run lacks retained run-state identity",
@@ -166,7 +177,7 @@ export async function loadNotarySourceRunLocator(
166
177
  ): Promise<NotarySourceRunLocator> {
167
178
  const real = await requireRunDirectory(path, path);
168
179
  const identity = parseRunDirectoryName(basename(real))!;
169
- const runState = await readRoleRunIdentity(real);
180
+ const runState = await readRetainedIdentity(real);
170
181
  if (runState === undefined) {
171
182
  throw new NotarySourceRunError(
172
183
  "notary source-run lacks retained run-state identity",
@@ -521,13 +521,11 @@ export async function appendPiSessionCustomEntry(
521
521
  timestamp,
522
522
  })}\n`;
523
523
  await appendFile(sessionFile, pointerLine, "utf8");
524
- try {
525
- sitianReport({
526
- level: "event",
527
- kind: "dispatch-error",
528
- sessionParent: sessionFile,
529
- payload: { customType, data },
530
- source: "pi-role-turn-host",
531
- });
532
- } catch {}
524
+ sitianReport({
525
+ level: "event",
526
+ kind: "dispatch-error",
527
+ sessionParent: sessionFile,
528
+ payload: { customType, data },
529
+ source: "pi-role-turn-host",
530
+ });
533
531
  }