@hardkas/core 0.6.0-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
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
3
 
3
4
  /**
4
5
  * Lightweight branded/nominal types for domain-critical strings/numbers.
@@ -328,6 +329,11 @@ type UnknownEventPayload = {
328
329
  readonly type: "unknown";
329
330
  readonly data: Record<string, unknown>;
330
331
  };
332
+ /**
333
+ * Attaches the canonical Event Ledger appender to the core event bus.
334
+ * This guarantees that all formal EventEnvelopes are persisted to events.jsonl.
335
+ */
336
+ declare function attachLedgerAppender(workspaceRoot: string): () => void;
331
337
 
332
338
  /**
333
339
  * @deprecated Use Brand from domain-types.js instead.
@@ -458,6 +464,7 @@ interface AcquireLockArgs {
458
464
  declare const LOCK_ORDER: string[];
459
465
  /**
460
466
  * Acquires a named lock for the workspace.
467
+ * Supports automatic stale lock recovery when the holding process is dead.
461
468
  */
462
469
  declare function acquireLock(args: AcquireLockArgs): Promise<LockHandle>;
463
470
  /**
@@ -553,6 +560,85 @@ declare function readSnapshotManifest(snapshotDir: string): Promise<SnapshotMani
553
560
  */
554
561
  declare function deterministicCompare(a: string, b: string): number;
555
562
 
563
+ type TelemetrySubsystem = "lock" | "fs" | "replay" | "normalization" | "query-store" | "lineage" | "projection" | "unknown";
564
+ type AnomalyType = "LOCK_CONTENTION" | "STALE_LOCK_RECOVERY" | "FS_RETRY" | "NORMALIZATION_COLLISION" | "REPLAY_RECONCILIATION" | "EXTERNAL_MUTATION" | "PATH_TRAVERSAL_ATTEMPT" | "ORPHAN_PROJECTION_RECOVERY";
565
+ type Severity = "low" | "medium" | "high" | "critical";
566
+ interface AnomalyEvent {
567
+ timestamp: string;
568
+ seed?: number | undefined;
569
+ caseId?: string | undefined;
570
+ bucket?: string | undefined;
571
+ anomalyType: AnomalyType;
572
+ severity: Severity;
573
+ subsystem: TelemetrySubsystem;
574
+ details: string;
575
+ sandbox?: string | undefined;
576
+ }
577
+
578
+ declare class TelemetryManager {
579
+ private rootDir;
580
+ private currentContext;
581
+ private preservedSandboxes;
582
+ constructor(rootDir?: string);
583
+ init(rootDir: string): void;
584
+ setContext(context: {
585
+ seed?: number;
586
+ caseId?: string;
587
+ bucket?: string;
588
+ }): void;
589
+ clearContext(): void;
590
+ getContext(): {
591
+ seed?: number;
592
+ caseId?: string;
593
+ bucket?: string;
594
+ };
595
+ logAnomaly(anomalyType: AnomalyType, severity: Severity, subsystem: TelemetrySubsystem, details: string, sandboxOverride?: string): void;
596
+ shouldPreserveSandbox(sandboxDir: string): boolean;
597
+ }
598
+ declare const telemetryContextStorage: AsyncLocalStorage<TelemetryManager>;
599
+ declare const globalTelemetry: TelemetryManager;
600
+ declare function getTelemetry(): TelemetryManager;
601
+ declare class TelemetryProxy {
602
+ logAnomaly(anomalyType: AnomalyType, severity: Severity, subsystem: TelemetrySubsystem, details: string, sandboxOverride?: string): void;
603
+ init(rootDir: string): void;
604
+ setContext(context: {
605
+ seed?: number;
606
+ caseId?: string;
607
+ bucket?: string;
608
+ }): void;
609
+ clearContext(): void;
610
+ getContext(): {
611
+ seed?: number;
612
+ caseId?: string;
613
+ bucket?: string;
614
+ };
615
+ shouldPreserveSandbox(sandboxDir: string): boolean;
616
+ }
617
+ declare const EnvironmentTelemetry: TelemetryProxy;
618
+
619
+ interface RotationResult {
620
+ rotated: boolean;
621
+ archivePath?: string;
622
+ bytesRotated?: number;
623
+ reason?: string;
624
+ }
625
+ declare class TelemetryRotator {
626
+ private static readonly DEFAULT_MAX_SIZE_BYTES;
627
+ /**
628
+ * Rotates the telemetry stream if it exceeds the maximum size.
629
+ * This is a safe operation that renames the active file to an archive directory.
630
+ */
631
+ static rotateIfNeeded(rootDir: string, maxSizeBytes?: number): RotationResult;
632
+ /**
633
+ * Forces a rotation regardless of file size.
634
+ */
635
+ static forceRotate(rootDir: string): RotationResult;
636
+ /**
637
+ * Lists all archived telemetry segments.
638
+ */
639
+ static listArchivedSegments(rootDir: string): string[];
640
+ }
641
+
556
642
  interface DeterministicClock {
557
643
  now(): number;
558
644
  }
@@ -567,6 +653,7 @@ interface RuntimeContext {
567
653
  clock: DeterministicClock;
568
654
  random: DeterministicRandom;
569
655
  ids: IdProvider;
656
+ telemetry: TelemetryManager;
570
657
  }
571
658
  /**
572
659
  * A default system runtime context (for non-deterministic contexts like dev server or CLI entry points)
@@ -574,6 +661,146 @@ interface RuntimeContext {
574
661
  */
575
662
  declare const systemRuntimeContext: RuntimeContext;
576
663
 
664
+ /**
665
+ * Formal Artifact Status Lattice
666
+ *
667
+ * UNKNOWN: Unreadable, ambiguous, partially classified, migration-pending states.
668
+ * PROJECTED: An artifact read from disk / state whose truth has not yet been verified.
669
+ * STALE: An artifact whose dependencies/lineage has drifted since it was verified.
670
+ * VERIFIED: Integrity, signature, and internal capability constraints are verified.
671
+ * REPLAY_VERIFIED: Full lineage and determinism verified via an active replay.
672
+ * CORRUPTED: Irreparable semantic or cryptographic corruption detected.
673
+ * QUARANTINED: Corrupted or malicious artifact safely isolated from runtime.
674
+ */
675
+ type ArtifactStatus = "UNKNOWN" | "PROJECTED" | "STALE" | "VERIFIED" | "REPLAY_VERIFIED" | "CORRUPTED" | "QUARANTINED";
676
+ /**
677
+ * Baseline schema version is 1.
678
+ */
679
+ type SchemaVersion = 1;
680
+ interface SemanticIdentity {
681
+ /** The unique artifact ID */
682
+ artifactId: string;
683
+ /** The semantic hash of the artifact content */
684
+ semanticHash: string;
685
+ /** Formal schema version */
686
+ schemaVersion: SchemaVersion;
687
+ /** Current status in the lattice */
688
+ status: ArtifactStatus;
689
+ }
690
+
691
+ /**
692
+ * Throws a loud runtime error if a transition is invalid.
693
+ * Invariant: `artifact_status_transitions_are_semantically_valid`
694
+ */
695
+ declare function validateStatusTransition(from: ArtifactStatus, to: ArtifactStatus): void;
696
+
697
+ interface ReplayContext {
698
+ semanticHash: string;
699
+ lineageId: string;
700
+ replayHash: string;
701
+ }
702
+ /**
703
+ * Resolves a canonical artifact.
704
+ * Implicit 'latest' is STRICTLY FORBIDDEN.
705
+ * You must provide an explicit artifactId, lineageId, or semanticHash.
706
+ * Invariant: `canonical_resolution_never_depends_on_implicit_latest`
707
+ */
708
+ declare function resolveCanonicalArtifact(params: {
709
+ artifactId?: string;
710
+ lineageId?: string;
711
+ semanticHash?: string;
712
+ }): string;
713
+ /**
714
+ * Verifies that the artifact content integrity is valid.
715
+ */
716
+ declare function verifyArtifactIntegrity(identity: SemanticIdentity, computedHash: string): void;
717
+ /**
718
+ * Verifies the artifact via active replay, isolating it from ambient state.
719
+ * Invariant: `replay_isolated_from_ambient_runtime_state`
720
+ */
721
+ declare function verifyReplay(identity: SemanticIdentity, replayCtx: ReplayContext): ArtifactStatus;
722
+ declare function verifyProjectionFreshness(identity: SemanticIdentity, currentLineageHead: string): boolean;
723
+ declare function classifyArtifactStatus(identity: SemanticIdentity, isReadable: boolean, isCorrupted: boolean): ArtifactStatus;
724
+ declare function resolveLineage(artifactId: string): string[];
725
+ declare function verifyCapabilityBoundary(identity: SemanticIdentity, capability: string): void;
726
+
727
+ interface MigrationResult {
728
+ migratedIdentity: SemanticIdentity;
729
+ success: boolean;
730
+ error?: string;
731
+ }
732
+ /**
733
+ * Validates that an artifact migration preserves identity and lineage semantics.
734
+ * Invariant: `schema_evolution_preserves_semantic_identity`
735
+ */
736
+ declare function verifyMigrationIntegrity(preMigration: SemanticIdentity, postMigration: SemanticIdentity): void;
737
+ /**
738
+ * Handles migrating an artifact to a newer schema version.
739
+ * Currently, only schemaVersion: 1 exists as the baseline.
740
+ */
741
+ declare function migrateArtifact(identity: SemanticIdentity, targetVersion: SchemaVersion): MigrationResult;
742
+ /**
743
+ * Compares lineage before and after migration to ensure continuity.
744
+ */
745
+ declare function comparePrePostMigrationLineage(preLineageId: string, postLineageId: string): void;
746
+
747
+ interface SemanticDriftReport {
748
+ hasDrift: boolean;
749
+ conflictingSubsystem?: string;
750
+ exactReplayCommand?: string;
751
+ severity: "NONE" | "CRITICAL";
752
+ details?: string;
753
+ }
754
+ /**
755
+ * Compares the truth across different subsystems.
756
+ * If multiple subsystems disagree about truth, that is a CRITICAL semantic failure.
757
+ * Invariant: `subsystems_cannot_disagree_about_canonical_truth`
758
+ */
759
+ declare function detectSemanticDrift(dashboardView: SemanticIdentity, queryStoreView: SemanticIdentity, replayView: SemanticIdentity, filesystemView: SemanticIdentity): SemanticDriftReport;
760
+ /**
761
+ * Asserts no semantic drift exists. Fails loudly.
762
+ */
763
+ declare function assertNoSemanticDrift(dashboardView: SemanticIdentity, queryStoreView: SemanticIdentity, replayView: SemanticIdentity, filesystemView: SemanticIdentity): void;
764
+
765
+ declare class AppendCoordinator {
766
+ private static _lastRecovery;
767
+ /**
768
+ * Safely appends a line to a JSONL log under process coordination locks.
769
+ * Performs an immediate fsync to ensure data durability.
770
+ * Also repairs the trailing line if it is corrupted, emitting an anomaly.
771
+ */
772
+ static appendAtomic(filePath: string, line: string, rootDir: string): void;
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.
778
+ */
779
+ static recoverCorruptedTail(filePath: string): {
780
+ repaired: boolean;
781
+ linesDiscarded: number;
782
+ originalTail: string;
783
+ originalSize: number;
784
+ recoveredSize: number;
785
+ reason: string;
786
+ };
787
+ }
788
+
789
+ declare const CURRENT_RUNTIME_VERSION = "0.7.0-alpha";
790
+ declare const MIN_SUPPORTED_VERSION = "0.5.0-alpha";
791
+ interface MigrationStatus {
792
+ needsMigration: boolean;
793
+ canDowngrade: boolean;
794
+ currentVersion: string;
795
+ }
796
+ declare class MigrationManager {
797
+ static checkVersion(rootDir: string): MigrationStatus;
798
+ static migrate(rootDir: string, dryRun?: boolean): void;
799
+ private static writeVersion;
800
+ private static backupWorkspace;
801
+ private static compareSemver;
802
+ }
803
+
577
804
  declare const SOMPI_PER_KAS = 100000000n;
578
805
  declare const kaspaNetworkIdSchema: z.ZodEnum<["mainnet", "testnet-10", "testnet-11", "testnet-12", "simnet", "simnet-1", "devnet", "simulated"]>;
579
806
  type NetworkId = Brand<z.infer<typeof kaspaNetworkIdSchema>, "NetworkId">;
@@ -664,4 +891,4 @@ declare function parseHardkasConfig(input: unknown): HardkasConfig;
664
891
  declare function parseKasToSompi(input: string): bigint;
665
892
  declare function formatSompi(amountSompi: bigint): string;
666
893
 
667
- export { type AcquireLockArgs, type ArtifactId, type ArtifactType, ArtifactTypeSchema, type Brand, type Branded, type ContentHash, type CoreEvent, type CoreEventListener, type CorrelationId, type CorruptionCode, type CorruptionIssue, type CorruptionSeverity, type CreateSnapshotOptions, type DaaScore, type DeterministicClock, type DeterministicDiff, type DeterministicRandom, type EventDomain, type EventEnvelope, type EventId, type EventKind, type EventPayloadByKind, type EventSequence, type ExecutionMode, ExecutionModeSchema, type HardkasConfig, HardkasError, type IdProvider, type IntegrityStatus, type InvariantDomain, type InvariantSeverity, InvariantViolationError, type KaspaAddress, LOCK_ORDER, type LayeredReplayDiff, type LineageId, type LockHandle, type LockMetadata, type NetworkId, NetworkIdSchema, type RpcEndpointId, type RuntimeContext, type RuntimeNoiseDiff, SOMPI_PER_KAS, type SnapshotManifest, type StampedEvent, type StateProvenance, type StructuralDiff, type TxId, type UnknownEventPayload, type WorkflowId, type WriteFileAtomicOptions, acquireLock, artifactTypeSchema, asArtifactId, asContentHash, asCorrelationId, asDaaScore, asEventId, asEventSequence, asKaspaAddress, asLineageId, asNetworkId, asRpcEndpointId, asTxId, asWorkflowId, clearLock, coreEvents, createEventEnvelope, createSnapshot, deterministicCompare, diffReplays, executionModeSchema, formatCorruptionIssue, formatSompi, hardkasConfigSchema, isProcessAlive, kaspaNetworkIdSchema, listLocks, maskSecrets, parseHardkasConfig, parseKasToSompi, readSnapshotManifest, redactSecret, systemRuntimeContext, validateEventEnvelope, withLock, withLocks, writeFileAtomic, writeFileAtomicSync };
894
+ export { type AcquireLockArgs, type AnomalyEvent, type AnomalyType, AppendCoordinator, type ArtifactId, type ArtifactStatus, type ArtifactType, ArtifactTypeSchema, type Brand, type Branded, CURRENT_RUNTIME_VERSION, type ContentHash, type CoreEvent, type CoreEventListener, type CorrelationId, type CorruptionCode, type CorruptionIssue, type CorruptionSeverity, type CreateSnapshotOptions, type DaaScore, type DeterministicClock, type DeterministicDiff, type DeterministicRandom, EnvironmentTelemetry, type EventDomain, type EventEnvelope, type EventId, type EventKind, type EventPayloadByKind, type EventSequence, type ExecutionMode, ExecutionModeSchema, type HardkasConfig, HardkasError, type IdProvider, type IntegrityStatus, type InvariantDomain, type InvariantSeverity, InvariantViolationError, type KaspaAddress, LOCK_ORDER, type LayeredReplayDiff, type LineageId, type LockHandle, type LockMetadata, MIN_SUPPORTED_VERSION, MigrationManager, type MigrationResult, type MigrationStatus, type NetworkId, NetworkIdSchema, type ReplayContext, type RpcEndpointId, type RuntimeContext, type RuntimeNoiseDiff, SOMPI_PER_KAS, type SchemaVersion, type SemanticDriftReport, type SemanticIdentity, type Severity, type SnapshotManifest, type StampedEvent, type StateProvenance, type StructuralDiff, TelemetryManager, TelemetryRotator, type TelemetrySubsystem, type TxId, type UnknownEventPayload, type WorkflowId, type WriteFileAtomicOptions, acquireLock, artifactTypeSchema, asArtifactId, asContentHash, asCorrelationId, asDaaScore, asEventId, asEventSequence, asKaspaAddress, asLineageId, asNetworkId, asRpcEndpointId, asTxId, asWorkflowId, assertNoSemanticDrift, attachLedgerAppender, classifyArtifactStatus, clearLock, comparePrePostMigrationLineage, coreEvents, createEventEnvelope, createSnapshot, detectSemanticDrift, deterministicCompare, diffReplays, executionModeSchema, formatCorruptionIssue, formatSompi, getTelemetry, globalTelemetry, hardkasConfigSchema, isProcessAlive, kaspaNetworkIdSchema, listLocks, maskSecrets, migrateArtifact, parseHardkasConfig, parseKasToSompi, readSnapshotManifest, redactSecret, resolveCanonicalArtifact, resolveLineage, systemRuntimeContext, telemetryContextStorage, validateEventEnvelope, validateStatusTransition, verifyArtifactIntegrity, verifyCapabilityBoundary, verifyMigrationIntegrity, verifyProjectionFreshness, verifyReplay, withLock, withLocks, writeFileAtomic, writeFileAtomicSync };
package/dist/index.js CHANGED
@@ -1,7 +1,296 @@
1
1
  // src/index.ts
2
2
  import { z } from "zod";
3
3
 
4
+ // src/append-coordinator.ts
5
+ import fs from "fs";
6
+ import path2 from "path";
7
+
8
+ // src/telemetry.ts
9
+ import path from "path";
10
+ import crypto2 from "crypto";
11
+ import { AsyncLocalStorage } from "async_hooks";
12
+ var TelemetryManager = class {
13
+ rootDir = null;
14
+ currentContext = {};
15
+ // Track sandboxes that need to be preserved because they hit severe anomalies
16
+ preservedSandboxes = /* @__PURE__ */ new Set();
17
+ constructor(rootDir) {
18
+ if (rootDir) this.rootDir = rootDir;
19
+ }
20
+ init(rootDir) {
21
+ this.rootDir = rootDir;
22
+ }
23
+ setContext(context) {
24
+ this.currentContext = { ...this.currentContext, ...context };
25
+ }
26
+ clearContext() {
27
+ this.currentContext = {};
28
+ }
29
+ getContext() {
30
+ return this.currentContext;
31
+ }
32
+ logAnomaly(anomalyType, severity, subsystem, details, sandboxOverride) {
33
+ const logDir = this.rootDir ? path.join(this.rootDir, ".hardkas", "telemetry") : sandboxOverride ? path.join(sandboxOverride, ".hardkas", "telemetry") : null;
34
+ if (!logDir) return;
35
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
36
+ const runId = this.currentContext.seed ? `run-${this.currentContext.seed}` : "run-core";
37
+ const bucket = this.currentContext.bucket || "core";
38
+ let mappedSeverity = "nominal";
39
+ if (severity === "medium") mappedSeverity = "elevated";
40
+ else if (severity === "high" || severity === "critical") mappedSeverity = "critical";
41
+ const canonicalPayloadRaw = JSON.stringify({
42
+ runId,
43
+ bucket,
44
+ type: anomalyType,
45
+ severity: mappedSeverity,
46
+ caseId: this.currentContext.caseId,
47
+ payload: {
48
+ subsystem,
49
+ details,
50
+ sandbox: sandboxOverride
51
+ }
52
+ });
53
+ const eventHash = crypto2.createHash("sha256").update(canonicalPayloadRaw).digest("hex").slice(0, 32);
54
+ const eventIdRaw = `${eventHash}-${nowStr}`;
55
+ const eventId = crypto2.createHash("sha256").update(eventIdRaw).digest("hex").slice(0, 32);
56
+ const event = {
57
+ schemaVersion: "hardkas.telemetry.v1",
58
+ eventId,
59
+ eventHash,
60
+ timestamp: nowStr,
61
+ source: "core-runtime",
62
+ runId,
63
+ bucket,
64
+ type: anomalyType,
65
+ severity: mappedSeverity,
66
+ caseId: this.currentContext.caseId,
67
+ payload: {
68
+ subsystem,
69
+ details,
70
+ sandbox: sandboxOverride
71
+ }
72
+ };
73
+ if (severity === "high" || severity === "critical" || anomalyType === "REPLAY_RECONCILIATION" || anomalyType === "NORMALIZATION_COLLISION") {
74
+ if (sandboxOverride) {
75
+ this.preservedSandboxes.add(sandboxOverride);
76
+ }
77
+ }
78
+ try {
79
+ const logFile = path.join(logDir, "telemetry.jsonl");
80
+ const root = this.rootDir || sandboxOverride || process.cwd();
81
+ AppendCoordinator.appendAtomic(logFile, JSON.stringify(event), root);
82
+ } catch (err) {
83
+ }
84
+ }
85
+ shouldPreserveSandbox(sandboxDir) {
86
+ return this.preservedSandboxes.has(sandboxDir);
87
+ }
88
+ };
89
+ var telemetryContextStorage = new AsyncLocalStorage();
90
+ var globalTelemetry = new TelemetryManager();
91
+ function getTelemetry() {
92
+ return telemetryContextStorage.getStore() || globalTelemetry;
93
+ }
94
+ var TelemetryProxy = class {
95
+ logAnomaly(anomalyType, severity, subsystem, details, sandboxOverride) {
96
+ return getTelemetry().logAnomaly(anomalyType, severity, subsystem, details, sandboxOverride);
97
+ }
98
+ init(rootDir) {
99
+ return getTelemetry().init(rootDir);
100
+ }
101
+ setContext(context) {
102
+ return getTelemetry().setContext(context);
103
+ }
104
+ clearContext() {
105
+ return getTelemetry().clearContext();
106
+ }
107
+ getContext() {
108
+ return getTelemetry().getContext();
109
+ }
110
+ shouldPreserveSandbox(sandboxDir) {
111
+ return getTelemetry().shouldPreserveSandbox(sandboxDir);
112
+ }
113
+ };
114
+ var EnvironmentTelemetry = new TelemetryProxy();
115
+
116
+ // src/append-coordinator.ts
117
+ var AppendCoordinator = class _AppendCoordinator {
118
+ static _lastRecovery = null;
119
+ /**
120
+ * Safely appends a line to a JSONL log under process coordination locks.
121
+ * Performs an immediate fsync to ensure data durability.
122
+ * Also repairs the trailing line if it is corrupted, emitting an anomaly.
123
+ */
124
+ static appendAtomic(filePath, line, rootDir) {
125
+ const lockDir = path2.join(rootDir, ".hardkas", "locks");
126
+ if (!fs.existsSync(lockDir)) {
127
+ fs.mkdirSync(lockDir, { recursive: true });
128
+ }
129
+ const logBase = path2.basename(filePath);
130
+ const lockPath = path2.join(lockDir, `append-${logBase}.lock`);
131
+ let fd = null;
132
+ let repaired = false;
133
+ let linesDiscarded = 0;
134
+ let originalTail = "";
135
+ try {
136
+ const start = Date.now();
137
+ const timeoutMs = 1e4;
138
+ while (true) {
139
+ try {
140
+ fd = fs.openSync(lockPath, "wx");
141
+ break;
142
+ } catch (e) {
143
+ if (e.code === "EEXIST") {
144
+ if (Date.now() - start > timeoutMs) {
145
+ throw new Error(`[AppendCoordinator] Timeout waiting for lock on ${lockPath}`);
146
+ }
147
+ const sleepMs = 5 + Math.floor(Math.random() * 15);
148
+ const sharedBuf = new Int32Array(new SharedArrayBuffer(4));
149
+ Atomics.wait(sharedBuf, 0, 0, sleepMs);
150
+ continue;
151
+ }
152
+ throw e;
153
+ }
154
+ }
155
+ fs.writeSync(fd, JSON.stringify({ pid: process.pid, time: (/* @__PURE__ */ new Date()).toISOString() }));
156
+ const recovery = _AppendCoordinator.recoverCorruptedTail(filePath);
157
+ if (recovery.repaired) {
158
+ repaired = true;
159
+ linesDiscarded = recovery.linesDiscarded;
160
+ originalTail = recovery.originalTail;
161
+ _AppendCoordinator._lastRecovery = recovery;
162
+ }
163
+ const logDir = path2.dirname(filePath);
164
+ if (!fs.existsSync(logDir)) {
165
+ fs.mkdirSync(logDir, { recursive: true });
166
+ }
167
+ const logFd = fs.openSync(filePath, "a");
168
+ const buffer = Buffer.from(line.endsWith("\n") ? line : line + "\n", "utf-8");
169
+ fs.writeSync(logFd, buffer, 0, buffer.length);
170
+ fs.fsyncSync(logFd);
171
+ fs.closeSync(logFd);
172
+ } finally {
173
+ if (fd !== null) {
174
+ fs.closeSync(fd);
175
+ try {
176
+ fs.unlinkSync(lockPath);
177
+ } catch {
178
+ }
179
+ }
180
+ }
181
+ if (repaired) {
182
+ try {
183
+ const recovery = _AppendCoordinator._lastRecovery;
184
+ const telemetry = getTelemetry();
185
+ telemetry.logAnomaly(
186
+ "EXTERNAL_MUTATION",
187
+ "medium",
188
+ "fs",
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)}..."`,
190
+ rootDir
191
+ );
192
+ } catch {
193
+ }
194
+ }
195
+ }
196
+ /**
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.
201
+ */
202
+ static recoverCorruptedTail(filePath) {
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;
212
+ const stat = fs.statSync(filePath);
213
+ defaultRes.originalSize = stat.size;
214
+ defaultRes.recoveredSize = stat.size;
215
+ if (stat.size === 0) return defaultRes;
216
+ const fd = fs.openSync(filePath, "r");
217
+ try {
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;
288
+ }
289
+ }
290
+ };
291
+
4
292
  // src/events.ts
293
+ import path3 from "path";
5
294
  var CoreEventBus = class {
6
295
  listeners = [];
7
296
  on(listener) {
@@ -66,6 +355,25 @@ function validateEventEnvelope(event) {
66
355
  if (typeof event.payload !== "object") return false;
67
356
  return true;
68
357
  }
358
+ function attachLedgerAppender(workspaceRoot) {
359
+ const seenEventIds = /* @__PURE__ */ new Set();
360
+ const eventsFile = path3.join(workspaceRoot, "events.jsonl");
361
+ return coreEvents.on((event) => {
362
+ if (seenEventIds.has(event.eventId)) {
363
+ return;
364
+ }
365
+ seenEventIds.add(event.eventId);
366
+ if (seenEventIds.size > 1e5) {
367
+ const iterator = seenEventIds.keys();
368
+ for (let i = 0; i < 1e4; i++) seenEventIds.delete(iterator.next().value);
369
+ }
370
+ const payload = JSON.stringify(event) + "\n";
371
+ try {
372
+ AppendCoordinator.appendAtomic(eventsFile, payload, workspaceRoot);
373
+ } catch (e) {
374
+ }
375
+ });
376
+ }
69
377
 
70
378
  // src/domain-types.ts
71
379
  var asTxId = (id) => id;
@@ -116,34 +424,35 @@ function redactSecret(value) {
116
424
  }
117
425
 
118
426
  // src/fs.ts
119
- import fs from "fs";
120
- import path from "path";
121
- import crypto2 from "crypto";
427
+ import fs2 from "fs";
428
+ import path4 from "path";
429
+ import crypto3 from "crypto";
122
430
  async function writeFileAtomic(targetPath, data, options = {}) {
123
- const dir = path.dirname(targetPath);
124
- const base = path.basename(targetPath);
125
- const tempPath = path.join(dir, `.tmp.${base}.${crypto2.randomUUID()}`);
431
+ const dir = path4.dirname(targetPath);
432
+ const base = path4.basename(targetPath);
433
+ const tempPath = path4.join(dir, `.tmp.${base}.${crypto3.randomUUID()}`);
126
434
  let fd = null;
127
435
  try {
128
- if (!fs.existsSync(dir)) {
129
- fs.mkdirSync(dir, { recursive: true });
436
+ if (!fs2.existsSync(dir)) {
437
+ fs2.mkdirSync(dir, { recursive: true });
130
438
  }
131
- fd = fs.openSync(tempPath, "w", options.mode);
439
+ fd = fs2.openSync(tempPath, "w", options.mode);
132
440
  const buffer = typeof data === "string" ? Buffer.from(data, options.encoding || "utf-8") : data;
133
- fs.writeSync(fd, buffer, 0, buffer.length);
134
- fs.fsyncSync(fd);
135
- fs.closeSync(fd);
441
+ fs2.writeSync(fd, buffer, 0, buffer.length);
442
+ fs2.fsyncSync(fd);
443
+ fs2.closeSync(fd);
136
444
  fd = null;
137
445
  let attempts = 0;
138
446
  const maxAttempts = process.platform === "win32" ? 5 : 1;
139
447
  while (attempts < maxAttempts) {
140
448
  try {
141
- fs.renameSync(tempPath, targetPath);
449
+ fs2.renameSync(tempPath, targetPath);
142
450
  break;
143
451
  } catch (e) {
144
452
  attempts++;
145
453
  if (attempts >= maxAttempts) throw e;
146
454
  if (e.code === "EPERM" || e.code === "EBUSY") {
455
+ EnvironmentTelemetry.logAnomaly("FS_RETRY", "low", "fs", `Retrying rename of ${targetPath} due to ${e.code}`);
147
456
  await new Promise((resolve) => setTimeout(resolve, 10 * attempts));
148
457
  continue;
149
458
  }
@@ -153,11 +462,11 @@ async function writeFileAtomic(targetPath, data, options = {}) {
153
462
  if (options.fsyncParent && process.platform !== "win32") {
154
463
  let dirFd = null;
155
464
  try {
156
- dirFd = fs.openSync(dir, "r");
157
- fs.fsyncSync(dirFd);
465
+ dirFd = fs2.openSync(dir, "r");
466
+ fs2.fsyncSync(dirFd);
158
467
  } catch (e) {
159
468
  } finally {
160
- if (dirFd !== null) fs.closeSync(dirFd);
469
+ if (dirFd !== null) fs2.closeSync(dirFd);
161
470
  }
162
471
  }
163
472
  } catch (err) {
@@ -167,45 +476,46 @@ async function writeFileAtomic(targetPath, data, options = {}) {
167
476
  { cause: err }
168
477
  );
169
478
  } finally {
170
- if (fs.existsSync(tempPath)) {
479
+ if (fs2.existsSync(tempPath)) {
171
480
  try {
172
- fs.unlinkSync(tempPath);
481
+ fs2.unlinkSync(tempPath);
173
482
  } catch (e) {
174
483
  }
175
484
  }
176
485
  if (fd !== null) {
177
486
  try {
178
- fs.closeSync(fd);
487
+ fs2.closeSync(fd);
179
488
  } catch (e) {
180
489
  }
181
490
  }
182
491
  }
183
492
  }
184
493
  function writeFileAtomicSync(targetPath, data, options = {}) {
185
- const dir = path.dirname(targetPath);
186
- const base = path.basename(targetPath);
187
- const tempPath = path.join(dir, `.tmp.${base}.${crypto2.randomUUID()}`);
494
+ const dir = path4.dirname(targetPath);
495
+ const base = path4.basename(targetPath);
496
+ const tempPath = path4.join(dir, `.tmp.${base}.${crypto3.randomUUID()}`);
188
497
  let fd = null;
189
498
  try {
190
- if (!fs.existsSync(dir)) {
191
- fs.mkdirSync(dir, { recursive: true });
499
+ if (!fs2.existsSync(dir)) {
500
+ fs2.mkdirSync(dir, { recursive: true });
192
501
  }
193
- fd = fs.openSync(tempPath, "w", options.mode);
502
+ fd = fs2.openSync(tempPath, "w", options.mode);
194
503
  const buffer = typeof data === "string" ? Buffer.from(data, options.encoding || "utf-8") : data;
195
- fs.writeSync(fd, buffer, 0, buffer.length);
196
- fs.fsyncSync(fd);
197
- fs.closeSync(fd);
504
+ fs2.writeSync(fd, buffer, 0, buffer.length);
505
+ fs2.fsyncSync(fd);
506
+ fs2.closeSync(fd);
198
507
  fd = null;
199
508
  let attempts = 0;
200
509
  const maxAttempts = process.platform === "win32" ? 5 : 1;
201
510
  while (attempts < maxAttempts) {
202
511
  try {
203
- fs.renameSync(tempPath, targetPath);
512
+ fs2.renameSync(tempPath, targetPath);
204
513
  break;
205
514
  } catch (e) {
206
515
  attempts++;
207
516
  if (attempts >= maxAttempts) throw e;
208
517
  if (e.code === "EPERM" || e.code === "EBUSY") {
518
+ EnvironmentTelemetry.logAnomaly("FS_RETRY", "low", "fs", `Retrying rename sync of ${targetPath} due to ${e.code}`);
209
519
  continue;
210
520
  }
211
521
  throw e;
@@ -214,11 +524,11 @@ function writeFileAtomicSync(targetPath, data, options = {}) {
214
524
  if (options.fsyncParent && process.platform !== "win32") {
215
525
  let dirFd = null;
216
526
  try {
217
- dirFd = fs.openSync(dir, "r");
218
- fs.fsyncSync(dirFd);
527
+ dirFd = fs2.openSync(dir, "r");
528
+ fs2.fsyncSync(dirFd);
219
529
  } catch (e) {
220
530
  } finally {
221
- if (dirFd !== null) fs.closeSync(dirFd);
531
+ if (dirFd !== null) fs2.closeSync(dirFd);
222
532
  }
223
533
  }
224
534
  } catch (err) {
@@ -228,15 +538,15 @@ function writeFileAtomicSync(targetPath, data, options = {}) {
228
538
  { cause: err }
229
539
  );
230
540
  } finally {
231
- if (fs.existsSync(tempPath)) {
541
+ if (fs2.existsSync(tempPath)) {
232
542
  try {
233
- fs.unlinkSync(tempPath);
543
+ fs2.unlinkSync(tempPath);
234
544
  } catch (e) {
235
545
  }
236
546
  }
237
547
  if (fd !== null) {
238
548
  try {
239
- fs.closeSync(fd);
549
+ fs2.closeSync(fd);
240
550
  } catch (e) {
241
551
  }
242
552
  }
@@ -262,8 +572,8 @@ function formatCorruptionIssue(issue) {
262
572
  }
263
573
 
264
574
  // src/lock.ts
265
- import fs2 from "fs";
266
- import path2 from "path";
575
+ import fs3 from "fs";
576
+ import path5 from "path";
267
577
  import os from "os";
268
578
  var LOCK_ORDER = [
269
579
  "workspace",
@@ -274,13 +584,14 @@ var LOCK_ORDER = [
274
584
  "query-store"
275
585
  ];
276
586
  async function acquireLock(args) {
277
- const lockDir = path2.join(args.rootDir, ".hardkas", "locks");
278
- const lockPath = path2.join(lockDir, `${args.name}.lock`);
587
+ const lockDir = path5.join(args.rootDir, ".hardkas", "locks");
588
+ const lockPath = path5.join(lockDir, `${args.name}.lock`);
279
589
  const timeoutMs = args.timeoutMs ?? 3e4;
280
590
  const pollMs = args.pollMs ?? 250;
281
591
  const start = Date.now();
282
- if (!fs2.existsSync(lockDir)) {
283
- fs2.mkdirSync(lockDir, { recursive: true });
592
+ let staleRecoveryAttempted = false;
593
+ if (!fs3.existsSync(lockDir)) {
594
+ fs3.mkdirSync(lockDir, { recursive: true });
284
595
  }
285
596
  while (true) {
286
597
  try {
@@ -294,18 +605,18 @@ async function acquireLock(args) {
294
605
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
295
606
  expiresAt: null
296
607
  };
297
- const fd = fs2.openSync(lockPath, "wx");
298
- fs2.writeSync(fd, JSON.stringify(metadata, null, 2));
299
- fs2.closeSync(fd);
608
+ const fd = fs3.openSync(lockPath, "wx");
609
+ fs3.writeSync(fd, JSON.stringify(metadata, null, 2));
610
+ fs3.closeSync(fd);
300
611
  return {
301
612
  path: lockPath,
302
613
  metadata,
303
614
  release: async () => {
304
- if (fs2.existsSync(lockPath)) {
615
+ if (fs3.existsSync(lockPath)) {
305
616
  try {
306
- const current = JSON.parse(fs2.readFileSync(lockPath, "utf-8"));
617
+ const current = JSON.parse(fs3.readFileSync(lockPath, "utf-8"));
307
618
  if (current.pid === process.pid) {
308
- fs2.unlinkSync(lockPath);
619
+ fs3.unlinkSync(lockPath);
309
620
  }
310
621
  } catch (e) {
311
622
  }
@@ -316,12 +627,49 @@ async function acquireLock(args) {
316
627
  if (e.code === "EEXIST") {
317
628
  let existingMetadata = null;
318
629
  try {
319
- existingMetadata = JSON.parse(fs2.readFileSync(lockPath, "utf-8"));
630
+ existingMetadata = JSON.parse(fs3.readFileSync(lockPath, "utf-8"));
320
631
  } catch (err) {
632
+ const LOCK_CREATION_GRACE_MS = 2e3;
633
+ let stats = null;
634
+ try {
635
+ stats = fs3.statSync(lockPath);
636
+ } catch {
637
+ continue;
638
+ }
639
+ const ageMs = Date.now() - stats.mtimeMs;
640
+ if (ageMs < LOCK_CREATION_GRACE_MS) {
641
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
642
+ continue;
643
+ }
644
+ if (!staleRecoveryAttempted) {
645
+ staleRecoveryAttempted = true;
646
+ try {
647
+ fs3.unlinkSync(lockPath);
648
+ EnvironmentTelemetry.logAnomaly("STALE_LOCK_RECOVERY", "medium", "lock", `Recovered corrupted lock file at ${lockPath} (Age: ${ageMs}ms)`, args.rootDir);
649
+ continue;
650
+ } catch {
651
+ throw new HardkasError("LOCK_METADATA_INVALID", `Lock file at ${lockPath} is corrupted and cannot be recovered.`, { cause: err });
652
+ }
653
+ }
321
654
  throw new HardkasError("LOCK_METADATA_INVALID", `Lock file at ${lockPath} is corrupted.`, { cause: err });
322
655
  }
323
656
  if (existingMetadata) {
324
- const isAlive = isProcessAlive(existingMetadata.pid);
657
+ const isLocal = existingMetadata.hostname === os.hostname();
658
+ const isAlive = isLocal ? isProcessAlive(existingMetadata.pid) : true;
659
+ if (!isAlive && !staleRecoveryAttempted) {
660
+ staleRecoveryAttempted = true;
661
+ try {
662
+ fs3.unlinkSync(lockPath);
663
+ EnvironmentTelemetry.logAnomaly("STALE_LOCK_RECOVERY", "medium", "lock", `Recovered lock held by dead process (PID: ${existingMetadata.pid})`, args.rootDir);
664
+ continue;
665
+ } catch (unlinkErr) {
666
+ throw new HardkasError(
667
+ "STALE_LOCK",
668
+ `Workspace is locked by a dead process (PID: ${existingMetadata.pid}). Failed to auto-recover: ${unlinkErr}`,
669
+ { cause: existingMetadata }
670
+ );
671
+ }
672
+ }
325
673
  if (!isAlive) {
326
674
  throw new HardkasError(
327
675
  "STALE_LOCK",
@@ -330,6 +678,7 @@ async function acquireLock(args) {
330
678
  );
331
679
  }
332
680
  if (args.wait && Date.now() - start < timeoutMs) {
681
+ EnvironmentTelemetry.logAnomaly("LOCK_CONTENTION", "low", "lock", `Waiting for lock ${args.name} held by PID ${existingMetadata.pid}`, args.rootDir);
333
682
  await new Promise((resolve) => setTimeout(resolve, pollMs));
334
683
  continue;
335
684
  }
@@ -375,20 +724,22 @@ function isProcessAlive(pid) {
375
724
  process.kill(pid, 0);
376
725
  return true;
377
726
  } catch (e) {
378
- return e.code !== "ESRCH";
727
+ if (e.code === "EPERM") return true;
728
+ if (e.code === "ESRCH") return false;
729
+ return true;
379
730
  }
380
731
  }
381
732
  function listLocks(rootDir) {
382
- const lockDir = path2.join(rootDir, ".hardkas", "locks");
383
- if (!fs2.existsSync(lockDir)) return [];
384
- const files = fs2.readdirSync(lockDir).filter((f) => f.endsWith(".lock"));
733
+ const lockDir = path5.join(rootDir, ".hardkas", "locks");
734
+ if (!fs3.existsSync(lockDir)) return [];
735
+ const files = fs3.readdirSync(lockDir).filter((f) => f.endsWith(".lock"));
385
736
  const result = [];
386
737
  for (const file of files) {
387
- const lockPath = path2.join(lockDir, file);
738
+ const lockPath = path5.join(lockDir, file);
388
739
  try {
389
- const metadata = JSON.parse(fs2.readFileSync(lockPath, "utf-8"));
740
+ const metadata = JSON.parse(fs3.readFileSync(lockPath, "utf-8"));
390
741
  result.push({
391
- name: path2.basename(file, ".lock"),
742
+ name: path5.basename(file, ".lock"),
392
743
  metadata,
393
744
  path: lockPath,
394
745
  isAlive: metadata.hostname === os.hostname() ? isProcessAlive(metadata.pid) : true
@@ -400,15 +751,15 @@ function listLocks(rootDir) {
400
751
  return result;
401
752
  }
402
753
  function clearLock(rootDir, name, options = {}) {
403
- const lockDir = path2.join(rootDir, ".hardkas", "locks");
404
- const lockPath = path2.join(lockDir, `${name}.lock`);
405
- if (!fs2.existsSync(lockPath)) return { cleared: false, reason: "Lock not found" };
754
+ const lockDir = path5.join(rootDir, ".hardkas", "locks");
755
+ const lockPath = path5.join(lockDir, `${name}.lock`);
756
+ if (!fs3.existsSync(lockPath)) return { cleared: false, reason: "Lock not found" };
406
757
  let metadata;
407
758
  try {
408
- metadata = JSON.parse(fs2.readFileSync(lockPath, "utf-8"));
759
+ metadata = JSON.parse(fs3.readFileSync(lockPath, "utf-8"));
409
760
  } catch (e) {
410
761
  if (options.force) {
411
- fs2.unlinkSync(lockPath);
762
+ fs3.unlinkSync(lockPath);
412
763
  return { cleared: true };
413
764
  }
414
765
  return { cleared: false, reason: "Corrupt metadata (use --force to clear)" };
@@ -421,7 +772,7 @@ function clearLock(rootDir, name, options = {}) {
421
772
  } else if (!options.force) {
422
773
  return { cleared: false, reason: "Lock is potentially active. Use --force or --if-dead." };
423
774
  }
424
- fs2.unlinkSync(lockPath);
775
+ fs3.unlinkSync(lockPath);
425
776
  return { cleared: true };
426
777
  }
427
778
 
@@ -479,31 +830,31 @@ function diffReplays(replayA, replayB) {
479
830
  }
480
831
 
481
832
  // src/snapshot.ts
482
- import fs3 from "fs/promises";
483
- import path3 from "path";
833
+ import fs4 from "fs/promises";
834
+ import path6 from "path";
484
835
  async function createSnapshot(options) {
485
836
  const { hardkasDir, outputDir, deterministicScope = "local-only" } = options;
486
- await fs3.mkdir(outputDir, { recursive: true });
487
- await fs3.mkdir(path3.join(outputDir, "artifacts"), { recursive: true });
488
- await fs3.mkdir(path3.join(outputDir, "projections"), { recursive: true });
489
- await fs3.mkdir(path3.join(outputDir, "events"), { recursive: true });
490
- await fs3.mkdir(path3.join(outputDir, "replay"), { recursive: true });
491
- await fs3.mkdir(path3.join(outputDir, "metadata"), { recursive: true });
837
+ await fs4.mkdir(outputDir, { recursive: true });
838
+ await fs4.mkdir(path6.join(outputDir, "artifacts"), { recursive: true });
839
+ await fs4.mkdir(path6.join(outputDir, "projections"), { recursive: true });
840
+ await fs4.mkdir(path6.join(outputDir, "events"), { recursive: true });
841
+ await fs4.mkdir(path6.join(outputDir, "replay"), { recursive: true });
842
+ await fs4.mkdir(path6.join(outputDir, "metadata"), { recursive: true });
492
843
  let included = 0;
493
844
  let excluded = 0;
494
845
  let corrupted = 0;
495
- const artifactsDir = path3.join(hardkasDir, "artifacts");
846
+ const artifactsDir = path6.join(hardkasDir, "artifacts");
496
847
  try {
497
- const list = await fs3.readdir(artifactsDir);
848
+ const list = await fs4.readdir(artifactsDir);
498
849
  for (const f of list) {
499
850
  if (f.endsWith(".json")) {
500
- const src = path3.join(artifactsDir, f);
501
- const dest = path3.join(outputDir, "artifacts", f);
851
+ const src = path6.join(artifactsDir, f);
852
+ const dest = path6.join(outputDir, "artifacts", f);
502
853
  try {
503
- const content = await fs3.readFile(src, "utf-8");
854
+ const content = await fs4.readFile(src, "utf-8");
504
855
  const parsed = JSON.parse(content);
505
856
  if (parsed.schema && parsed.schema.startsWith("hardkas.")) {
506
- await fs3.copyFile(src, dest);
857
+ await fs4.copyFile(src, dest);
507
858
  included++;
508
859
  } else {
509
860
  excluded++;
@@ -516,19 +867,19 @@ async function createSnapshot(options) {
516
867
  } catch {
517
868
  }
518
869
  try {
519
- const eventsLog = path3.join(hardkasDir, "events.jsonl");
520
- await fs3.copyFile(eventsLog, path3.join(outputDir, "events", "events.jsonl"));
870
+ const eventsLog = path6.join(hardkasDir, "events.jsonl");
871
+ await fs4.copyFile(eventsLog, path6.join(outputDir, "events", "events.jsonl"));
521
872
  } catch {
522
873
  }
523
874
  try {
524
- const dbPath = path3.join(hardkasDir, "store.db");
525
- await fs3.copyFile(dbPath, path3.join(outputDir, "projections", "store.db"));
875
+ const dbPath = path6.join(hardkasDir, "store.db");
876
+ await fs4.copyFile(dbPath, path6.join(outputDir, "projections", "store.db"));
526
877
  } catch {
527
878
  }
528
879
  const manifest = {
529
880
  snapshotVersion: 1,
530
881
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
531
- hardkasVersion: "0.6.0-alpha",
882
+ hardkasVersion: "0.7.0-alpha",
532
883
  stateAuthority: "filesystem",
533
884
  projectionAuthority: "sqlite",
534
885
  deterministicScope,
@@ -537,16 +888,16 @@ async function createSnapshot(options) {
537
888
  excludedArtifacts: excluded,
538
889
  corruptedArtifacts: corrupted
539
890
  };
540
- await fs3.writeFile(
541
- path3.join(outputDir, "manifest.json"),
891
+ await fs4.writeFile(
892
+ path6.join(outputDir, "manifest.json"),
542
893
  JSON.stringify(manifest, null, 2),
543
894
  "utf-8"
544
895
  );
545
896
  return manifest;
546
897
  }
547
898
  async function readSnapshotManifest(snapshotDir) {
548
- const manifestPath = path3.join(snapshotDir, "manifest.json");
549
- const content = await fs3.readFile(manifestPath, "utf-8");
899
+ const manifestPath = path6.join(snapshotDir, "manifest.json");
900
+ const content = await fs4.readFile(manifestPath, "utf-8");
550
901
  return JSON.parse(content);
551
902
  }
552
903
 
@@ -555,6 +906,65 @@ function deterministicCompare(a, b) {
555
906
  return a < b ? -1 : a > b ? 1 : 0;
556
907
  }
557
908
 
909
+ // src/retention.ts
910
+ import fs5 from "fs";
911
+ import path7 from "path";
912
+ var TelemetryRotator = class {
913
+ static DEFAULT_MAX_SIZE_BYTES = 10 * 1024 * 1024;
914
+ // 10MB
915
+ /**
916
+ * Rotates the telemetry stream if it exceeds the maximum size.
917
+ * This is a safe operation that renames the active file to an archive directory.
918
+ */
919
+ static rotateIfNeeded(rootDir, maxSizeBytes = this.DEFAULT_MAX_SIZE_BYTES) {
920
+ const telemetryDir = path7.join(rootDir, ".hardkas", "telemetry");
921
+ const activeFile = path7.join(telemetryDir, "telemetry.jsonl");
922
+ if (!fs5.existsSync(activeFile)) {
923
+ return { rotated: false, reason: "File does not exist" };
924
+ }
925
+ const stats = fs5.statSync(activeFile);
926
+ if (stats.size < maxSizeBytes) {
927
+ return { rotated: false, reason: `File size (${stats.size}) is below threshold (${maxSizeBytes})` };
928
+ }
929
+ return this.forceRotate(rootDir);
930
+ }
931
+ /**
932
+ * Forces a rotation regardless of file size.
933
+ */
934
+ static forceRotate(rootDir) {
935
+ const telemetryDir = path7.join(rootDir, ".hardkas", "telemetry");
936
+ const activeFile = path7.join(telemetryDir, "telemetry.jsonl");
937
+ if (!fs5.existsSync(activeFile)) {
938
+ return { rotated: false, reason: "File does not exist" };
939
+ }
940
+ const archiveDir = path7.join(telemetryDir, "archive");
941
+ if (!fs5.existsSync(archiveDir)) {
942
+ fs5.mkdirSync(archiveDir, { recursive: true });
943
+ }
944
+ const stats = fs5.statSync(activeFile);
945
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
946
+ const archiveFile = path7.join(archiveDir, `telemetry-${timestamp}.jsonl`);
947
+ try {
948
+ fs5.renameSync(activeFile, archiveFile);
949
+ return {
950
+ rotated: true,
951
+ archivePath: archiveFile,
952
+ bytesRotated: stats.size
953
+ };
954
+ } catch (err) {
955
+ return { rotated: false, reason: `Rename failed: ${err.message}` };
956
+ }
957
+ }
958
+ /**
959
+ * Lists all archived telemetry segments.
960
+ */
961
+ static listArchivedSegments(rootDir) {
962
+ const archiveDir = path7.join(rootDir, ".hardkas", "telemetry", "archive");
963
+ if (!fs5.existsSync(archiveDir)) return [];
964
+ return fs5.readdirSync(archiveDir).filter((f) => f.startsWith("telemetry-") && f.endsWith(".jsonl")).sort();
965
+ }
966
+ };
967
+
558
968
  // src/runtime-context.ts
559
969
  var systemRuntimeContext = {
560
970
  clock: {
@@ -566,6 +976,210 @@ var systemRuntimeContext = {
566
976
  ids: {
567
977
  execution: () => `exec_${Date.now().toString(36)}`,
568
978
  workflow: () => `wf_${Date.now().toString(36)}`
979
+ },
980
+ telemetry: globalTelemetry
981
+ };
982
+
983
+ // src/semantics/status.ts
984
+ var LEGAL_TRANSITIONS = {
985
+ UNKNOWN: ["PROJECTED", "QUARANTINED", "CORRUPTED"],
986
+ PROJECTED: ["VERIFIED", "CORRUPTED"],
987
+ VERIFIED: ["STALE", "REPLAY_VERIFIED", "CORRUPTED"],
988
+ STALE: ["VERIFIED", "REPLAY_VERIFIED", "CORRUPTED"],
989
+ REPLAY_VERIFIED: ["STALE", "CORRUPTED"],
990
+ CORRUPTED: ["QUARANTINED"],
991
+ QUARANTINED: []
992
+ // Terminal state
993
+ };
994
+ function validateStatusTransition(from, to) {
995
+ if (from === to) return;
996
+ const allowed = LEGAL_TRANSITIONS[from];
997
+ if (!allowed.includes(to)) {
998
+ throw new Error(
999
+ `[CRITICAL SEMANTIC ERROR] Illegal artifact status transition attempted: ${from} -> ${to}. This is a violation of the semantic artifact status lattice.`
1000
+ );
1001
+ }
1002
+ }
1003
+
1004
+ // src/semantics/api.ts
1005
+ function resolveCanonicalArtifact(params) {
1006
+ if (!params.artifactId && !params.lineageId && !params.semanticHash) {
1007
+ throw new Error(
1008
+ `[CRITICAL SEMANTIC ERROR] Implicit resolution forbidden. You must pin resolution by providing an explicit artifactId, lineageId, or semanticHash.`
1009
+ );
1010
+ }
1011
+ return params.artifactId || params.semanticHash || params.lineageId || "";
1012
+ }
1013
+ function verifyArtifactIntegrity(identity, computedHash) {
1014
+ if (identity.semanticHash !== computedHash) {
1015
+ throw new Error(
1016
+ `[CRITICAL SEMANTIC ERROR] Integrity mismatch for artifact ${identity.artifactId}: expected hash ${identity.semanticHash}, got ${computedHash}`
1017
+ );
1018
+ }
1019
+ }
1020
+ function verifyReplay(identity, replayCtx) {
1021
+ if (identity.semanticHash !== replayCtx.semanticHash) {
1022
+ throw new Error(`[CRITICAL SEMANTIC ERROR] Replay semantic hash divergence: expected ${identity.semanticHash}, got ${replayCtx.semanticHash}`);
1023
+ }
1024
+ validateStatusTransition(identity.status, "REPLAY_VERIFIED");
1025
+ return "REPLAY_VERIFIED";
1026
+ }
1027
+ function verifyProjectionFreshness(identity, currentLineageHead) {
1028
+ return true;
1029
+ }
1030
+ function classifyArtifactStatus(identity, isReadable, isCorrupted) {
1031
+ if (isCorrupted) return "CORRUPTED";
1032
+ if (!isReadable) return "UNKNOWN";
1033
+ if (identity.status === "UNKNOWN") return "PROJECTED";
1034
+ return identity.status;
1035
+ }
1036
+ function resolveLineage(artifactId) {
1037
+ return [artifactId];
1038
+ }
1039
+ function verifyCapabilityBoundary(identity, capability) {
1040
+ }
1041
+
1042
+ // src/semantics/migration.ts
1043
+ function verifyMigrationIntegrity(preMigration, postMigration) {
1044
+ if (preMigration.artifactId !== postMigration.artifactId) {
1045
+ throw new Error(`[CRITICAL SEMANTIC ERROR] Migration unexpectedly altered canonical artifact ID: ${preMigration.artifactId} -> ${postMigration.artifactId}`);
1046
+ }
1047
+ if (postMigration.schemaVersion < preMigration.schemaVersion) {
1048
+ throw new Error(`[CRITICAL SEMANTIC ERROR] Invalid migration: schema downgraded from ${preMigration.schemaVersion} to ${postMigration.schemaVersion}`);
1049
+ }
1050
+ }
1051
+ function migrateArtifact(identity, targetVersion) {
1052
+ if (identity.schemaVersion === targetVersion) {
1053
+ return { migratedIdentity: identity, success: true };
1054
+ }
1055
+ return {
1056
+ migratedIdentity: identity,
1057
+ success: false,
1058
+ error: `No migration path from ${identity.schemaVersion} to ${targetVersion}`
1059
+ };
1060
+ }
1061
+ function comparePrePostMigrationLineage(preLineageId, postLineageId) {
1062
+ if (preLineageId !== postLineageId) {
1063
+ throw new Error(`[CRITICAL SEMANTIC ERROR] Lineage broken across schema migration: ${preLineageId} -> ${postLineageId}`);
1064
+ }
1065
+ }
1066
+
1067
+ // src/semantics/drift.ts
1068
+ function detectSemanticDrift(dashboardView, queryStoreView, replayView, filesystemView) {
1069
+ const views = {
1070
+ Dashboard: dashboardView,
1071
+ QueryStore: queryStoreView,
1072
+ Replay: replayView,
1073
+ Filesystem: filesystemView
1074
+ };
1075
+ let referenceView = filesystemView;
1076
+ for (const [subsystem, view] of Object.entries(views)) {
1077
+ if (view.semanticHash !== referenceView.semanticHash) {
1078
+ return {
1079
+ hasDrift: true,
1080
+ conflictingSubsystem: subsystem,
1081
+ exactReplayCommand: `hardkas verify-replay --artifact ${referenceView.artifactId}`,
1082
+ severity: "CRITICAL",
1083
+ details: `Hash mismatch: ${subsystem} (${view.semanticHash}) vs Reference (${referenceView.semanticHash})`
1084
+ };
1085
+ }
1086
+ if (view.status !== referenceView.status) {
1087
+ if (subsystem === "Dashboard" && view.status === "VERIFIED" && replayView.status === "STALE") {
1088
+ return {
1089
+ hasDrift: true,
1090
+ conflictingSubsystem: subsystem,
1091
+ exactReplayCommand: `hardkas verify-replay --artifact ${referenceView.artifactId}`,
1092
+ severity: "CRITICAL",
1093
+ details: `Dashboard claims VERIFIED but Replay claims STALE.`
1094
+ };
1095
+ }
1096
+ }
1097
+ }
1098
+ return { hasDrift: false, severity: "NONE" };
1099
+ }
1100
+ function assertNoSemanticDrift(dashboardView, queryStoreView, replayView, filesystemView) {
1101
+ const report = detectSemanticDrift(dashboardView, queryStoreView, replayView, filesystemView);
1102
+ if (report.hasDrift) {
1103
+ throw new Error(
1104
+ `[CRITICAL SEMANTIC DRIFT] Subsystem disagreement detected.
1105
+ Conflicting Subsystem: ${report.conflictingSubsystem}
1106
+ Details: ${report.details}
1107
+ Resolution Command: ${report.exactReplayCommand}`
1108
+ );
1109
+ }
1110
+ }
1111
+
1112
+ // src/migrations.ts
1113
+ import fs6 from "fs";
1114
+ import path8 from "path";
1115
+ var CURRENT_RUNTIME_VERSION = "0.7.0-alpha";
1116
+ var MIN_SUPPORTED_VERSION = "0.5.0-alpha";
1117
+ var MigrationManager = class {
1118
+ static checkVersion(rootDir) {
1119
+ const versionFile = path8.join(rootDir, ".hardkas", "version.json");
1120
+ if (!fs6.existsSync(versionFile)) {
1121
+ this.writeVersion(rootDir, CURRENT_RUNTIME_VERSION);
1122
+ return { needsMigration: false, canDowngrade: true, currentVersion: CURRENT_RUNTIME_VERSION };
1123
+ }
1124
+ try {
1125
+ const data = JSON.parse(fs6.readFileSync(versionFile, "utf-8"));
1126
+ const wsVersion = data.runtimeVersion || "0.0.0";
1127
+ if (wsVersion === CURRENT_RUNTIME_VERSION) {
1128
+ return { needsMigration: false, canDowngrade: true, currentVersion: wsVersion };
1129
+ }
1130
+ if (this.compareSemver(wsVersion, CURRENT_RUNTIME_VERSION) > 0) {
1131
+ return { needsMigration: false, canDowngrade: false, currentVersion: wsVersion };
1132
+ }
1133
+ if (this.compareSemver(wsVersion, MIN_SUPPORTED_VERSION) < 0) {
1134
+ throw new HardkasError("MIGRATION_UNSUPPORTED", `Workspace version ${wsVersion} is too old to migrate to ${CURRENT_RUNTIME_VERSION}`);
1135
+ }
1136
+ return { needsMigration: true, canDowngrade: true, currentVersion: wsVersion };
1137
+ } catch (err) {
1138
+ if (err instanceof HardkasError) throw err;
1139
+ throw new HardkasError("MIGRATION_ERROR", `Failed to parse version.json: ${err.message}`);
1140
+ }
1141
+ }
1142
+ static migrate(rootDir, dryRun = false) {
1143
+ const status = this.checkVersion(rootDir);
1144
+ if (!status.canDowngrade) {
1145
+ throw new HardkasError("DOWNGRADE_REFUSED", `Cannot safely downgrade from workspace version ${status.currentVersion} to runtime version ${CURRENT_RUNTIME_VERSION}.`);
1146
+ }
1147
+ if (!status.needsMigration) return;
1148
+ if (dryRun) {
1149
+ console.log(`[DRY-RUN] Would migrate workspace from ${status.currentVersion} to ${CURRENT_RUNTIME_VERSION}`);
1150
+ return;
1151
+ }
1152
+ this.backupWorkspace(rootDir);
1153
+ try {
1154
+ this.writeVersion(rootDir, CURRENT_RUNTIME_VERSION);
1155
+ } catch (err) {
1156
+ getTelemetry().logAnomaly("EXTERNAL_MUTATION", "critical", "projection", `Migration failed: ${err.message}`);
1157
+ throw new HardkasError("MIGRATION_FAILED", `Migration failed, workspace might be corrupted: ${err.message}`);
1158
+ }
1159
+ }
1160
+ static writeVersion(rootDir, version) {
1161
+ const versionFile = path8.join(rootDir, ".hardkas", "version.json");
1162
+ if (!fs6.existsSync(path8.dirname(versionFile))) {
1163
+ fs6.mkdirSync(path8.dirname(versionFile), { recursive: true });
1164
+ }
1165
+ fs6.writeFileSync(versionFile, JSON.stringify({ runtimeVersion: version }, null, 2));
1166
+ }
1167
+ static backupWorkspace(rootDir) {
1168
+ const hardkasDir = path8.join(rootDir, ".hardkas");
1169
+ const backupDir = path8.join(rootDir, `.hardkas-backup-${Date.now()}`);
1170
+ if (fs6.existsSync(hardkasDir)) {
1171
+ fs6.cpSync(hardkasDir, backupDir, { recursive: true });
1172
+ }
1173
+ }
1174
+ static compareSemver(v1, v2) {
1175
+ const parse = (v) => v.replace("-alpha", "").split(".").map(Number);
1176
+ const p1 = parse(v1);
1177
+ const p2 = parse(v2);
1178
+ for (let i = 0; i < 3; i++) {
1179
+ if ((p1[i] || 0) > (p2[i] || 0)) return 1;
1180
+ if ((p1[i] || 0) < (p2[i] || 0)) return -1;
1181
+ }
1182
+ return 0;
569
1183
  }
570
1184
  };
571
1185
 
@@ -661,13 +1275,20 @@ function formatSompi(amountSompi) {
661
1275
  return `${sign}${whole}.${fractional.toString().padStart(8, "0")} KAS`;
662
1276
  }
663
1277
  export {
1278
+ AppendCoordinator,
664
1279
  ArtifactTypeSchema,
1280
+ CURRENT_RUNTIME_VERSION,
1281
+ EnvironmentTelemetry,
665
1282
  ExecutionModeSchema,
666
1283
  HardkasError,
667
1284
  InvariantViolationError,
668
1285
  LOCK_ORDER,
1286
+ MIN_SUPPORTED_VERSION,
1287
+ MigrationManager,
669
1288
  NetworkIdSchema,
670
1289
  SOMPI_PER_KAS,
1290
+ TelemetryManager,
1291
+ TelemetryRotator,
671
1292
  acquireLock,
672
1293
  artifactTypeSchema,
673
1294
  asArtifactId,
@@ -682,26 +1303,43 @@ export {
682
1303
  asRpcEndpointId,
683
1304
  asTxId,
684
1305
  asWorkflowId,
1306
+ assertNoSemanticDrift,
1307
+ attachLedgerAppender,
1308
+ classifyArtifactStatus,
685
1309
  clearLock,
1310
+ comparePrePostMigrationLineage,
686
1311
  coreEvents,
687
1312
  createEventEnvelope,
688
1313
  createSnapshot,
1314
+ detectSemanticDrift,
689
1315
  deterministicCompare,
690
1316
  diffReplays,
691
1317
  executionModeSchema,
692
1318
  formatCorruptionIssue,
693
1319
  formatSompi,
1320
+ getTelemetry,
1321
+ globalTelemetry,
694
1322
  hardkasConfigSchema,
695
1323
  isProcessAlive,
696
1324
  kaspaNetworkIdSchema,
697
1325
  listLocks,
698
1326
  maskSecrets,
1327
+ migrateArtifact,
699
1328
  parseHardkasConfig,
700
1329
  parseKasToSompi,
701
1330
  readSnapshotManifest,
702
1331
  redactSecret,
1332
+ resolveCanonicalArtifact,
1333
+ resolveLineage,
703
1334
  systemRuntimeContext,
1335
+ telemetryContextStorage,
704
1336
  validateEventEnvelope,
1337
+ validateStatusTransition,
1338
+ verifyArtifactIntegrity,
1339
+ verifyCapabilityBoundary,
1340
+ verifyMigrationIntegrity,
1341
+ verifyProjectionFreshness,
1342
+ verifyReplay,
705
1343
  withLock,
706
1344
  withLocks,
707
1345
  writeFileAtomic,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/core",
3
- "version": "0.6.0-alpha",
3
+ "version": "0.7.0-alpha",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",