@hardkas/core 0.6.1-alpha → 0.7.0-alpha

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/index.d.ts CHANGED
@@ -763,6 +763,7 @@ declare function detectSemanticDrift(dashboardView: SemanticIdentity, queryStore
763
763
  declare function assertNoSemanticDrift(dashboardView: SemanticIdentity, queryStoreView: SemanticIdentity, replayView: SemanticIdentity, filesystemView: SemanticIdentity): void;
764
764
 
765
765
  declare class AppendCoordinator {
766
+ private static _lastRecovery;
766
767
  /**
767
768
  * Safely appends a line to a JSONL log under process coordination locks.
768
769
  * Performs an immediate fsync to ensure data durability.
@@ -771,15 +772,21 @@ declare class AppendCoordinator {
771
772
  static appendAtomic(filePath: string, line: string, rootDir: string): void;
772
773
  /**
773
774
  * Scans a JSONL stream for corruption, truncating malformed trailing lines.
775
+ * Utilizes a backward newline scanning logic with a rolling buffer,
776
+ * supporting lines of arbitrary size and only truncating the last complete
777
+ * valid JSONL boundary if a parse failure is detected.
774
778
  */
775
779
  static recoverCorruptedTail(filePath: string): {
776
780
  repaired: boolean;
777
781
  linesDiscarded: number;
778
782
  originalTail: string;
783
+ originalSize: number;
784
+ recoveredSize: number;
785
+ reason: string;
779
786
  };
780
787
  }
781
788
 
782
- declare const CURRENT_RUNTIME_VERSION = "0.6.1-alpha";
789
+ declare const CURRENT_RUNTIME_VERSION = "0.7.0-alpha";
783
790
  declare const MIN_SUPPORTED_VERSION = "0.5.0-alpha";
784
791
  interface MigrationStatus {
785
792
  needsMigration: boolean;
package/dist/index.js CHANGED
@@ -115,6 +115,7 @@ var EnvironmentTelemetry = new TelemetryProxy();
115
115
 
116
116
  // src/append-coordinator.ts
117
117
  var AppendCoordinator = class _AppendCoordinator {
118
+ static _lastRecovery = null;
118
119
  /**
119
120
  * Safely appends a line to a JSONL log under process coordination locks.
120
121
  * Performs an immediate fsync to ensure data durability.
@@ -157,6 +158,7 @@ var AppendCoordinator = class _AppendCoordinator {
157
158
  repaired = true;
158
159
  linesDiscarded = recovery.linesDiscarded;
159
160
  originalTail = recovery.originalTail;
161
+ _AppendCoordinator._lastRecovery = recovery;
160
162
  }
161
163
  const logDir = path2.dirname(filePath);
162
164
  if (!fs.existsSync(logDir)) {
@@ -178,12 +180,13 @@ var AppendCoordinator = class _AppendCoordinator {
178
180
  }
179
181
  if (repaired) {
180
182
  try {
183
+ const recovery = _AppendCoordinator._lastRecovery;
181
184
  const telemetry = getTelemetry();
182
185
  telemetry.logAnomaly(
183
186
  "EXTERNAL_MUTATION",
184
187
  "medium",
185
188
  "fs",
186
- `Recovered corrupted trailing line in ${logBase}. Discarded ${linesDiscarded} malformed bytes. Original tail snippet: "${originalTail.slice(0, 60)}..."`,
189
+ `Recovered corrupted tail in ${logBase}. Original size: ${recovery.originalSize} bytes, Recovered size: ${recovery.recoveredSize} bytes, Truncated bytes: ${linesDiscarded}. Reason: ${recovery.reason}. Original tail snippet: "${originalTail.slice(0, 60)}..."`,
187
190
  rootDir
188
191
  );
189
192
  } catch {
@@ -192,47 +195,96 @@ var AppendCoordinator = class _AppendCoordinator {
192
195
  }
193
196
  /**
194
197
  * Scans a JSONL stream for corruption, truncating malformed trailing lines.
198
+ * Utilizes a backward newline scanning logic with a rolling buffer,
199
+ * supporting lines of arbitrary size and only truncating the last complete
200
+ * valid JSONL boundary if a parse failure is detected.
195
201
  */
196
202
  static recoverCorruptedTail(filePath) {
197
- if (!fs.existsSync(filePath)) return { repaired: false, linesDiscarded: 0, originalTail: "" };
203
+ const defaultRes = {
204
+ repaired: false,
205
+ linesDiscarded: 0,
206
+ originalTail: "",
207
+ originalSize: 0,
208
+ recoveredSize: 0,
209
+ reason: ""
210
+ };
211
+ if (!fs.existsSync(filePath)) return defaultRes;
198
212
  const stat = fs.statSync(filePath);
199
- if (stat.size === 0) return { repaired: false, linesDiscarded: 0, originalTail: "" };
200
- const TAIL_SIZE = 4096;
201
- const readStart = Math.max(0, stat.size - TAIL_SIZE);
213
+ defaultRes.originalSize = stat.size;
214
+ defaultRes.recoveredSize = stat.size;
215
+ if (stat.size === 0) return defaultRes;
202
216
  const fd = fs.openSync(filePath, "r");
203
- const buf = Buffer.alloc(Math.min(TAIL_SIZE, stat.size));
204
- fs.readSync(fd, buf, 0, buf.length, readStart);
205
- fs.closeSync(fd);
206
- const tail = buf.toString("utf-8");
207
- const lines = tail.split("\n");
208
- if (lines.length === 0) return { repaired: false, linesDiscarded: 0, originalTail: "" };
209
- let lastLine = "";
210
- let lastLineIdx = -1;
211
- for (let i = lines.length - 1; i >= 0; i--) {
212
- const l = lines[i].trim();
213
- if (l) {
214
- lastLine = l;
215
- lastLineIdx = i;
216
- break;
217
- }
218
- }
219
- if (!lastLine) return { repaired: false, linesDiscarded: 0, originalTail: "" };
220
- if (readStart > 0 && lastLineIdx === 0) {
221
- return { repaired: false, linesDiscarded: 0, originalTail: "" };
222
- }
223
217
  try {
224
- JSON.parse(lastLine);
225
- return { repaired: false, linesDiscarded: 0, originalTail: "" };
226
- } catch (err) {
227
- const linesAfterCorrupt = lines.slice(lastLineIdx).join("\n");
228
- const bytesToRemove = Buffer.byteLength(linesAfterCorrupt, "utf-8");
229
- const truncateTo = stat.size - bytesToRemove;
230
- fs.truncateSync(filePath, truncateTo > 0 ? truncateTo : 0);
231
- return {
232
- repaired: true,
233
- linesDiscarded: stat.size - truncateTo,
234
- originalTail: lastLine
235
- };
218
+ let lastCharPos = -1;
219
+ let precedingNewlinePos = -1;
220
+ const CHUNK_SIZE = 64 * 1024;
221
+ let position = stat.size;
222
+ const buffer = Buffer.alloc(CHUNK_SIZE);
223
+ outer1: while (position > 0) {
224
+ const readLength = Math.min(CHUNK_SIZE, position);
225
+ position -= readLength;
226
+ fs.readSync(fd, buffer, 0, readLength, position);
227
+ for (let i = readLength - 1; i >= 0; i--) {
228
+ const charCode = buffer[i];
229
+ if (charCode !== 32 && charCode !== 9 && charCode !== 10 && charCode !== 13) {
230
+ lastCharPos = position + i;
231
+ break outer1;
232
+ }
233
+ }
234
+ }
235
+ if (lastCharPos === -1) {
236
+ fs.closeSync(fd);
237
+ fs.truncateSync(filePath, 0);
238
+ return {
239
+ repaired: true,
240
+ linesDiscarded: stat.size,
241
+ originalTail: "",
242
+ originalSize: stat.size,
243
+ recoveredSize: 0,
244
+ reason: "File only contained whitespaces or newlines"
245
+ };
246
+ }
247
+ position = lastCharPos;
248
+ outer2: while (position > 0) {
249
+ const readLength = Math.min(CHUNK_SIZE, position);
250
+ position -= readLength;
251
+ fs.readSync(fd, buffer, 0, readLength, position);
252
+ for (let i = readLength - 1; i >= 0; i--) {
253
+ if (buffer[i] === 10) {
254
+ precedingNewlinePos = position + i;
255
+ break outer2;
256
+ }
257
+ }
258
+ }
259
+ const lastLineStart = precedingNewlinePos === -1 ? 0 : precedingNewlinePos + 1;
260
+ const lastLineEnd = lastCharPos + 1;
261
+ const lastLineLength = lastLineEnd - lastLineStart;
262
+ const lastLineBuf = Buffer.alloc(lastLineLength);
263
+ fs.readSync(fd, lastLineBuf, 0, lastLineLength, lastLineStart);
264
+ fs.closeSync(fd);
265
+ const lastLine = lastLineBuf.toString("utf-8");
266
+ try {
267
+ JSON.parse(lastLine);
268
+ return defaultRes;
269
+ } catch (err) {
270
+ const truncateTo = lastLineStart;
271
+ const discardedBytes = stat.size - truncateTo;
272
+ fs.truncateSync(filePath, truncateTo);
273
+ return {
274
+ repaired: true,
275
+ linesDiscarded: discardedBytes,
276
+ originalTail: lastLine,
277
+ originalSize: stat.size,
278
+ recoveredSize: truncateTo,
279
+ reason: err instanceof Error ? err.message : "Invalid JSON syntax"
280
+ };
281
+ }
282
+ } catch (e) {
283
+ try {
284
+ fs.closeSync(fd);
285
+ } catch {
286
+ }
287
+ throw e;
236
288
  }
237
289
  }
238
290
  };
@@ -827,7 +879,7 @@ async function createSnapshot(options) {
827
879
  const manifest = {
828
880
  snapshotVersion: 1,
829
881
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
830
- hardkasVersion: "0.6.1-alpha",
882
+ hardkasVersion: "0.7.0-alpha",
831
883
  stateAuthority: "filesystem",
832
884
  projectionAuthority: "sqlite",
833
885
  deterministicScope,
@@ -1060,7 +1112,7 @@ Resolution Command: ${report.exactReplayCommand}`
1060
1112
  // src/migrations.ts
1061
1113
  import fs6 from "fs";
1062
1114
  import path8 from "path";
1063
- var CURRENT_RUNTIME_VERSION = "0.6.1-alpha";
1115
+ var CURRENT_RUNTIME_VERSION = "0.7.0-alpha";
1064
1116
  var MIN_SUPPORTED_VERSION = "0.5.0-alpha";
1065
1117
  var MigrationManager = class {
1066
1118
  static checkVersion(rootDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/core",
3
- "version": "0.6.1-alpha",
3
+ "version": "0.7.0-alpha",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",