@lmzhen/dsh-evolution-core 0.19.0 → 0.19.2

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/lib/index.js CHANGED
@@ -6049,6 +6049,15 @@ function newRunRegistry(options) {
6049
6049
  const records = [];
6050
6050
  const controllers = /* @__PURE__ */ new Map();
6051
6051
  const ordered = () => [...records].sort((a, b) => b.startedAt - a.startedAt);
6052
+ /** The ONE wording of the orphan verdict — read-time convergence says it in two places. */
6053
+ const ORPHAN_FAILURE = "no live owner process — the run was interrupted before it settled (orphan)";
6054
+ /** A `running` record whose owner is gone, as the index must show it. */
6055
+ const orphanOf = (record) => ({
6056
+ ...record,
6057
+ state: "failed",
6058
+ endedAt: now(),
6059
+ failure: ORPHAN_FAILURE
6060
+ });
6052
6061
  /** Keep every RUNNING record plus the newest terminal ones: memory and the
6053
6062
  * index share one cap, so the two can never disagree about the history. */
6054
6063
  const trim = () => {
@@ -6065,7 +6074,12 @@ function newRunRegistry(options) {
6065
6074
  const onDisk = parseIndex(current);
6066
6075
  const merged = /* @__PURE__ */ new Map();
6067
6076
  for (const record of onDisk.records) merged.set(record.id, record);
6068
- for (const record of records) merged.set(record.id, record);
6077
+ for (const record of records) {
6078
+ const onDiskRow = merged.get(record.id);
6079
+ const settledElsewhere = onDiskRow !== void 0 && TERMINAL.has(onDiskRow.state) && !TERMINAL.has(record.state);
6080
+ if (!controllers.has(record.id) && settledElsewhere) continue;
6081
+ merged.set(record.id, record);
6082
+ }
6069
6083
  const view = [...merged.values()].sort((a, b) => b.startedAt - a.startedAt);
6070
6084
  const live = view.filter((record) => record.state === "running");
6071
6085
  const done = view.filter((record) => TERMINAL.has(record.state)).slice(0, maxRecords);
@@ -6100,8 +6114,25 @@ function newRunRegistry(options) {
6100
6114
  };
6101
6115
  let converged = 0;
6102
6116
  let liveElsewhere = 0;
6117
+ let healed = 0;
6103
6118
  for (const record of parsed.records) {
6104
- if (records.some((item) => item.id === record.id)) continue;
6119
+ const knownIndex = records.findIndex((item) => item.id === record.id);
6120
+ if (knownIndex >= 0) {
6121
+ const known = records[knownIndex];
6122
+ if (!controllers.has(record.id) && !TERMINAL.has(known.state)) {
6123
+ if (TERMINAL.has(record.state)) {
6124
+ delete known.endedAt;
6125
+ delete known.resultRef;
6126
+ delete known.failure;
6127
+ Object.assign(known, record);
6128
+ healed++;
6129
+ } else if (record.pid === void 0 || !isProcessAlive(record.pid)) {
6130
+ records[knownIndex] = orphanOf(record);
6131
+ converged++;
6132
+ }
6133
+ }
6134
+ continue;
6135
+ }
6105
6136
  if (record.state !== "running") {
6106
6137
  records.push(record);
6107
6138
  continue;
@@ -6112,25 +6143,21 @@ function newRunRegistry(options) {
6112
6143
  continue;
6113
6144
  }
6114
6145
  converged++;
6115
- records.push({
6116
- ...record,
6117
- state: "failed",
6118
- endedAt: now(),
6119
- failure: "no live owner process — the run was interrupted before it settled (orphan)"
6120
- });
6146
+ records.push(orphanOf(record));
6121
6147
  }
6122
- if (converged > 0) await persist();
6148
+ if (converged > 0 || healed > 0) await persist();
6123
6149
  const notes = [
6124
6150
  parsed.note,
6125
6151
  converged > 0 ? `${converged} run(s) with no live owner were recorded as failed(orphan)` : void 0,
6126
- liveElsewhere > 0 ? `${liveElsewhere} run(s) are in flight in another process sharing this home` : void 0
6152
+ liveElsewhere > 0 ? `${liveElsewhere} run(s) are in flight in another process sharing this home` : void 0,
6153
+ healed > 0 ? `${healed} run(s) settled by their owner were re-read from the index` : void 0
6127
6154
  ].filter((note) => note !== void 0);
6128
6155
  return {
6129
6156
  ok: true,
6130
6157
  ...notes.length > 0 ? { note: notes.join("; ") } : {}
6131
6158
  };
6132
6159
  },
6133
- begin(kind) {
6160
+ async begin(kind) {
6134
6161
  const id = randomUUID();
6135
6162
  const startedAt = now();
6136
6163
  const controller = new AbortController();
@@ -6142,7 +6169,7 @@ function newRunRegistry(options) {
6142
6169
  startedAt,
6143
6170
  pid: process.pid
6144
6171
  });
6145
- persist();
6172
+ await persist();
6146
6173
  return {
6147
6174
  id,
6148
6175
  kind,
@@ -65,13 +65,23 @@ export interface RunOutcome {
65
65
  }
66
66
  /** The registry surface the commands row drives. */
67
67
  export interface RunRegistry {
68
- /** Converge the persisted index once at mount; see the module doc. */
68
+ /**
69
+ * Converge the persisted index; the commands row calls it before answering any read
70
+ * (0.19.1: it also re-reads a terminal state written by the run's OWNER — see the
71
+ * implementation note below).
72
+ */
69
73
  load(): Promise<{
70
74
  ok: boolean;
71
75
  note?: string;
72
76
  }>;
73
- /** Register a new run, arm its cancellation, and return its handle. */
74
- begin(kind: RunKind): RunHandle;
77
+ /**
78
+ * Register a new run, arm its cancellation, and return its handle.
79
+ *
80
+ * Resolves only once the row is in the shared index (0.19.1, review P2): the detached
81
+ * lifetime starts when the caller answers "started", so a plane that reads that answer
82
+ * must find the run. The cost is one local index write on the ack path (milliseconds).
83
+ */
84
+ begin(kind: RunKind): Promise<RunHandle>;
75
85
  /** Record a run's terminal state (first writer wins). */
76
86
  settle(id: string, outcome: RunOutcome): Promise<void>;
77
87
  /** The live run, when there is one (one kind today — see the implementation note). */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-core",
3
3
  "description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
4
- "version": "0.19.0",
4
+ "version": "0.19.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },