@hilbras/omninode 2.0.0-alpha.11 → 2.0.0-alpha.13

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
@@ -1995,6 +1995,26 @@ type PlannerConfigYaml = z.infer<typeof plannerConfigSchema>;
1995
1995
  type ProjectConfigYaml = z.infer<typeof projectConfigSchema>;
1996
1996
  type AppConfigYaml = z.infer<typeof appConfigSchema>;
1997
1997
 
1998
+ /**
1999
+ * Inline secret detection (roadmap §17, Secrets).
2000
+ *
2001
+ * OmniNode configuration must never contain literal credentials — only
2002
+ * environment-variable *references*. This scanner runs before the config is
2003
+ * parsed and reports anything that looks like an inline secret, so the file
2004
+ * is rejected before it can be committed or persisted.
2005
+ */
2006
+ interface SecretFinding {
2007
+ /** 1-based line number in the source. */
2008
+ line: number;
2009
+ /** The offending key, e.g. `api_key`. */
2010
+ key: string;
2011
+ /** Redacted excerpt — never echoes the value. */
2012
+ excerpt: string;
2013
+ }
2014
+ declare function scanForInlineSecrets(source: string): SecretFinding[];
2015
+ /** Formats findings as an actionable configuration error message. */
2016
+ declare function describeSecretFindings(file: string, findings: SecretFinding[]): string;
2017
+
1998
2018
  interface AppConfig {
1999
2019
  project: {
2000
2020
  name: string;
@@ -2391,27 +2411,55 @@ declare class RoleRegistry {
2391
2411
  /** Builds a role registry from the project configuration. */
2392
2412
  declare function buildRoleRegistry(config: AppConfig): RoleRegistry;
2393
2413
 
2414
+ /** Current on-disk schema version. */
2415
+ declare const SCHEMA_VERSION = 2;
2394
2416
  interface JsonDocument<T> {
2395
- /** Bumped when the persisted shape changes in a breaking way (v2 Phase 12). */
2396
2417
  schemaVersion?: number;
2397
2418
  items: T[];
2398
2419
  }
2420
+ type DocumentState = {
2421
+ status: "missing";
2422
+ } | {
2423
+ status: "empty";
2424
+ } | {
2425
+ status: "v1-legacy";
2426
+ } | {
2427
+ status: "current";
2428
+ schemaVersion: number;
2429
+ count: number;
2430
+ } | {
2431
+ status: "future";
2432
+ schemaVersion: number;
2433
+ } | {
2434
+ status: "corrupt";
2435
+ detail: string;
2436
+ };
2399
2437
  interface JsonFileStoreOptions {
2400
2438
  directory?: string;
2401
2439
  fileName: string;
2440
+ /** Schema version to write (defaults to the current version). */
2402
2441
  schemaVersion?: number;
2403
- /** Called when a persisted file cannot be parsed. */
2442
+ /** Called when a persisted file is corrupt (after quarantine). */
2404
2443
  onCorrupt?: (path: string, error: unknown) => void;
2405
- }
2444
+ /** Disable file moves (used by dry runs / tests that inspect in place). */
2445
+ quarantine?: boolean;
2446
+ }
2447
+ /** Durably writes a file: temp → fsync → rename → directory fsync. */
2448
+ declare function writeFileAtomic(target: string, contents: string): Promise<void>;
2449
+ /** Reads and classifies a persisted document without modifying it. */
2450
+ declare function inspectDocument(path: string): Promise<DocumentState>;
2451
+ /** Reads a document, migrating v1 shapes and quarantining corruption. */
2452
+ declare function readDocument<T>(path: string, onCorrupt?: (path: string, error: unknown) => void, quarantine?: boolean): Promise<T[]>;
2406
2453
  declare class JsonFileStore<T> {
2407
2454
  private readonly filePath;
2408
2455
  private readonly schemaVersion;
2409
2456
  private readonly onCorrupt?;
2457
+ private readonly quarantine;
2410
2458
  private readonly log;
2411
2459
  private queue;
2412
2460
  constructor(options: JsonFileStoreOptions);
2413
2461
  get path(): string;
2414
- /** All items, or [] when missing/corrupt. */
2462
+ /** All items, or [] when missing/migrated-from-v1/corrupt. */
2415
2463
  protected readAll(): Promise<T[]>;
2416
2464
  protected writeAll(items: T[]): Promise<void>;
2417
2465
  /** Serializes an async operation against this file. */
@@ -2676,6 +2724,8 @@ interface CombinedMeta {
2676
2724
  pipelineRunId?: string;
2677
2725
  taskIds?: string[];
2678
2726
  }
2727
+ /** Serialized size budget for a single report (roadmap §17 — output limits). */
2728
+ declare const MAX_REPORT_BYTES: number;
2679
2729
  interface CollectionDiagnostics {
2680
2730
  collected: number;
2681
2731
  accepted: number;
@@ -3017,11 +3067,9 @@ declare function createDefaultChatFn(providers: AppConfig["project"]["providers"
3017
3067
  declare function buildPipelineEngine(config: AppConfig, log?: Logger): PipelineEngine;
3018
3068
 
3019
3069
  /**
3020
- * Persistence layer (v2 Phase 1): one entry point to every local store.
3021
- *
3022
- * All stores are interfaces with a local JSON default
3023
- * (TaskStore, PipelineRunStore, ReportStore, PlanStore, IMemoryProvider,
3024
- * AuditSink) — see docs/API.md for the inventory and stability status.
3070
+ * Persistence layer (v2 Phase 12): one entry point to every local store,
3071
+ * the canonical storage interface names, and the migration path that upgrades
3072
+ * v1 data in place.
3025
3073
  */
3026
3074
 
3027
3075
  /** Bundles every store for a single project directory. */
@@ -3034,7 +3082,26 @@ declare class ProjectStores {
3034
3082
  readonly audit: FileAuditLog;
3035
3083
  constructor(directory?: string);
3036
3084
  }
3085
+ interface MigrationReport {
3086
+ file: string;
3087
+ state: DocumentState["status"];
3088
+ before?: number;
3089
+ after?: number;
3090
+ migrated: boolean;
3091
+ note?: string;
3092
+ }
3093
+ /** Every persisted store file OmniNode manages in a project. */
3094
+ declare const STORE_FILES: readonly ["tasks.json", "pipelines.json", "reports.json", "plans.json", "memory.json"];
3095
+ /**
3096
+ * Migration (roadmap §16): inspects every store file, reports its schema
3097
+ * state, and — unless `checkOnly` — rewrites v1 documents at the current
3098
+ * schema version. Idempotent and safe: unknown/future versions are reported,
3099
+ * never rewritten.
3100
+ */
3101
+ declare function migrateProjectStores(directory?: string, options?: {
3102
+ checkOnly?: boolean;
3103
+ }): Promise<MigrationReport[]>;
3037
3104
 
3038
- declare const OMNINODE_VERSION = "2.0.0-alpha.11";
3105
+ declare const OMNINODE_VERSION = "2.0.0-alpha.13";
3039
3106
 
3040
- export { AgentAdapterRegistry, type AgentConfig, type AgentConfigYaml, type AgentDescriptor, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AggregateOptions, type AggregatedFinding, type AggregatedRecommendation, type AppConfig, type AppConfigYaml, type ArtifactKind, type ArtifactPayload, type AskedQuestion, type AuditAction, type AuditEvent, type AuditSink, type ChatFn, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatStreamChunk, type ChatUsage, type CollectionDiagnostics, type CombinedMeta, type CombinedReport, type CombinedReportFilter, type Confidence, ConfigError, type ConflictPosition, ConsoleLogSink, type CreateTaskInput, type CwdValidation, DEFAULT_MAX_OUTPUT_BYTES, type DecodeResult, type DiscoveryResult, type EnvPolicy, type EnvelopeMeta, type Evidence, type EvidenceInput, type EvidenceKind, type ExecutionRecord, type ExtractionMeta, FileAuditLog, FilePipelineRunStore, FilePlanStore, FileReportStore, FileTaskStore, type Finding, type FindingOrigin, type FindingSeverity, type FindingSource, type HelloResult, HeuristicPlanner, type HttpRequestOptions, type HttpResponse, type IAgent, type IAgentAdapter, type IChatProvider, type IMemoryProvider, type IPipelineExecutor, type IPlanner, type IProvider, type JsonDocument, JsonFileStore, type JsonFileStoreOptions, type LoadConfigOptions, LocalMemoryProvider, type LogLevel, type LogSink, Logger, type LoggerOptions, MAX_CONTEXT_CHARS, MAX_MESSAGE_BYTES, type MemoryCategory, type MemoryContextQuery, type MemoryEntry, MemoryError, type MemoryProviderConfig, type MemoryProviderMetadata, type MemoryQuery, type MemoryScope, MemoryService, type MemoryServiceOptions, type MemoryTaskContext, type MessageEnvelope, type Modality, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelPlanner, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, PROTOCOL_V2, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunAttempt, type PipelineRunFilter, type PipelineRunOptions, type PipelineRunStatus, type PipelineRunStore, type PipelineStep, type PipelineStepCondition, type PipelineStepConfigYaml, type PipelineStepKind, type PipelineStepRun, type Plan, type PlanFilter, type PlanJsonResult, type PlanRequest, type PlanStep, type PlanStore, type PlanValidationIssue, type PlanValidationResult, type PlannerConfigYaml, PlannerError, ProcessAgent, ProcessAgentAdapter, type Project, type ProjectConfigYaml, ProjectStores, type ProtocolAgentToNodeType, ProtocolDecoder, type ProtocolEnvelope, ProtocolError, type ProtocolMessageType, type ProtocolNodeToAgentType, ProtocolSession, type ProtocolViolation, type ProtocolViolationCode, type ProviderAuthentication, type ProviderCapability, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderErrorKind, type ProviderHealth, type ProviderStatus, type ProviderType, type QuestionPayload, type QuestionResponder, type RememberaConfig, RememberaMemoryProvider, type Report, type ReportArtifact, type ReportConflict, type ReportFilter, type ReportPayload, ReportService, type ReportStore, type ReportValidationIssue, type ReportValidationResult, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, SUPPORTED_PROTOCOL_VERSIONS, type SessionIds, TERMINAL_TASK_STATUSES, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskPayload, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, aggregateReports, appConfigSchema, authHeaders, buildAgentRegistry, buildChildEnv, buildHello, buildHelloAck, buildPipelineEngine, buildPlanner, buildPlannerContext, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createMemoryProvider, createProvider, createTaskEngine, decodeMessage, decodeStream, defaultMetadata, defaultProjectConfigYaml, defaultSearch, describeExit, detectConflicts, encodeMessage, envelope, expandEnvRefs, extractMessage, extractReportFromText, findConfigFile, isNetworkError, isOmniNodeError, isRetryable, isSupportedProtocol, isTimeoutError, keywords, killTree, kindFromStatus, loadConfig, logger, newMessageId, normalizeEvidence, normalizeFinding, parseHello, parsePlanJson, parseRetryAfter, pipelineConfigSchema, pipelineStepConfigSchema, planJsonSchema, plannerConfigSchema, projectConfigSchema, providerConfigSchema, providerHttpError, providerTransportError, registryKey, requestJson, requireCompletion, resolveApiKey, resolveEnvPolicy, roleConfigSchema, sanitizeChunk, similar, validateEnvelope, validatePlan, validateReport, validateWorkingDirectory };
3107
+ export { AgentAdapterRegistry, type AgentConfig, type AgentConfigYaml, type AgentDescriptor, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AggregateOptions, type AggregatedFinding, type AggregatedRecommendation, type AppConfig, type AppConfigYaml, type ArtifactKind, type ArtifactPayload, type AskedQuestion, type AuditAction, type AuditEvent, type AuditSink, type AuditSink as AuditStore, type ChatFn, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatStreamChunk, type ChatUsage, type CollectionDiagnostics, type CombinedMeta, type CombinedReport, type CombinedReportFilter, type Confidence, ConfigError, type ConflictPosition, ConsoleLogSink, type CreateTaskInput, type CwdValidation, DEFAULT_MAX_OUTPUT_BYTES, type DecodeResult, type DiscoveryResult, type DocumentState, type EnvPolicy, type EnvelopeMeta, type Evidence, type EvidenceInput, type EvidenceKind, type ExecutionRecord, type ExtractionMeta, FileAuditLog, FilePipelineRunStore, FilePlanStore, FileReportStore, FileTaskStore, type Finding, type FindingOrigin, type FindingSeverity, type FindingSource, type HelloResult, HeuristicPlanner, type HttpRequestOptions, type HttpResponse, type IAgent, type IAgentAdapter, type IChatProvider, type IMemoryProvider, type IPipelineExecutor, type IPlanner, type IProvider, type JsonDocument, JsonFileStore, type JsonFileStoreOptions, type LoadConfigOptions, LocalMemoryProvider, type LogLevel, type LogSink, Logger, type LoggerOptions, MAX_CONTEXT_CHARS, MAX_MESSAGE_BYTES, MAX_REPORT_BYTES, type MemoryCategory, type MemoryContextQuery, type MemoryEntry, MemoryError, type MemoryProviderConfig, type MemoryProviderMetadata, type MemoryQuery, type MemoryScope, MemoryService, type MemoryServiceOptions, LocalMemoryProvider as MemoryStore, type MemoryTaskContext, type MessageEnvelope, type MigrationReport, type Modality, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelPlanner, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, PROTOCOL_V2, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunAttempt, type PipelineRunFilter, type PipelineRunOptions, type PipelineRunStatus, type PipelineRunStore, type PipelineStep, type PipelineStepCondition, type PipelineStepConfigYaml, type PipelineStepKind, type PipelineStepRun, type PipelineRunStore as PipelineStore, type Plan, type PlanFilter, type PlanJsonResult, type PlanRequest, type PlanStep, type PlanStore, type PlanValidationIssue, type PlanValidationResult, type PlannerConfigYaml, PlannerError, ProcessAgent, ProcessAgentAdapter, type Project, type ProjectConfigYaml, ProjectStores, type ProtocolAgentToNodeType, ProtocolDecoder, type ProtocolEnvelope, ProtocolError, type ProtocolMessageType, type ProtocolNodeToAgentType, ProtocolSession, type ProtocolViolation, type ProtocolViolationCode, type ProviderAuthentication, type ProviderCapability, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderErrorKind, type ProviderHealth, type ProviderStatus, type ProviderType, type QuestionPayload, type QuestionResponder, type RememberaConfig, RememberaMemoryProvider, type Report, type ReportArtifact, type ReportConflict, type ReportFilter, type ReportPayload, ReportService, type ReportStore, type ReportValidationIssue, type ReportValidationResult, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, SCHEMA_VERSION, STORE_FILES, SUPPORTED_PROTOCOL_VERSIONS, type SecretFinding, type SessionIds, TERMINAL_TASK_STATUSES, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskPayload, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, aggregateReports, appConfigSchema, authHeaders, buildAgentRegistry, buildChildEnv, buildHello, buildHelloAck, buildPipelineEngine, buildPlanner, buildPlannerContext, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createMemoryProvider, createProvider, createTaskEngine, decodeMessage, decodeStream, defaultMetadata, defaultProjectConfigYaml, defaultSearch, describeExit, describeSecretFindings, detectConflicts, encodeMessage, envelope, expandEnvRefs, extractMessage, extractReportFromText, findConfigFile, inspectDocument, isNetworkError, isOmniNodeError, isRetryable, isSupportedProtocol, isTimeoutError, keywords, killTree, kindFromStatus, loadConfig, logger, migrateProjectStores, newMessageId, normalizeEvidence, normalizeFinding, parseHello, parsePlanJson, parseRetryAfter, pipelineConfigSchema, pipelineStepConfigSchema, planJsonSchema, plannerConfigSchema, projectConfigSchema, providerConfigSchema, providerHttpError, providerTransportError, readDocument, registryKey, requestJson, requireCompletion, resolveApiKey, resolveEnvPolicy, roleConfigSchema, sanitizeChunk, scanForInlineSecrets, similar, validateEnvelope, validatePlan, validateReport, validateWorkingDirectory, writeFileAtomic };
package/dist/index.js CHANGED
@@ -202,6 +202,39 @@ var appConfigSchema = z.object({
202
202
  logging: z.object({ level: z.enum(["debug", "info", "warn", "error"]) }).strict().optional()
203
203
  }).strict();
204
204
 
205
+ // src/config/secrets.ts
206
+ var SAFE_KEY_SUFFIXES = ["_env", "_env_var", "_file", "_path", "_ref", "_id", "_url", "_command"];
207
+ var SECRET_KEY = /^\s*([A-Za-z0-9_-]*(?:api[_-]?key|token|password|passwd|secret|credential)[A-Za-z0-9_-]*)\s*:\s*(.*)$/i;
208
+ var SAFE_VALUE = /^\s*(?:\$\{[^}]*\}|\$[A-Z_][A-Z0-9_]*|<[^>]*>|""|''|null|~)\s*$/i;
209
+ function scanForInlineSecrets(source) {
210
+ const findings = [];
211
+ source.split("\n").forEach((line, index) => {
212
+ const trimmed = line.trim();
213
+ if (trimmed.startsWith("#")) return;
214
+ const match = SECRET_KEY.exec(line);
215
+ if (!match) return;
216
+ const key = match[1] ?? "";
217
+ const rawValue = (match[2] ?? "").trim().replace(/,\s*$/, "");
218
+ const keyIsReference = SAFE_KEY_SUFFIXES.some((suffix) => key.toLowerCase().endsWith(suffix));
219
+ if (keyIsReference) return;
220
+ const value = rawValue.replace(/^["']|["']$/g, "");
221
+ if (value.length === 0 || SAFE_VALUE.test(rawValue)) return;
222
+ if (value.length < 8) return;
223
+ findings.push({
224
+ line: index + 1,
225
+ key,
226
+ excerpt: `${key}: ${value.slice(0, 3)}\u2026 (${value.length} chars, redacted)`
227
+ });
228
+ });
229
+ return findings;
230
+ }
231
+ function describeSecretFindings(file, findings) {
232
+ const lines = findings.map((f) => ` line ${f.line}: ${f.excerpt}`).join("\n");
233
+ return `${file} appears to contain ${findings.length} inline secret(s):
234
+ ${lines}
235
+ OmniNode never stores credentials in configuration. Replace each value with an environment-variable reference, e.g. api_key_env_var: MY_API_KEY.`;
236
+ }
237
+
205
238
  // src/config/loader.ts
206
239
  import { existsSync, readFileSync } from "fs";
207
240
  import { parse as parseYaml } from "yaml";
@@ -238,6 +271,10 @@ function loadConfig(options = {}) {
238
271
  );
239
272
  }
240
273
  const raw = readFileSync(path, "utf8");
274
+ const secretFindings = scanForInlineSecrets(raw);
275
+ if (secretFindings.length > 0) {
276
+ throw new ConfigError("CONFIG_INVALID", describeSecretFindings(path, secretFindings));
277
+ }
241
278
  const expanded = expandEnvRefs(raw, env, path);
242
279
  let data;
243
280
  try {
@@ -1820,51 +1857,119 @@ function buildRoleRegistry(config) {
1820
1857
  }
1821
1858
 
1822
1859
  // src/persistence/json-file-store.ts
1823
- import { mkdir, readFile, rename, writeFile } from "fs/promises";
1860
+ import { open, mkdir, readFile, rename, stat } from "fs/promises";
1824
1861
  import { dirname } from "path";
1862
+ var SCHEMA_VERSION = 2;
1863
+ async function writeFileAtomic(target, contents) {
1864
+ const tmp = `${target}.tmp`;
1865
+ await mkdir(dirname(target), { recursive: true });
1866
+ const handle = await open(tmp, "w");
1867
+ try {
1868
+ await handle.writeFile(contents, "utf8");
1869
+ await handle.sync();
1870
+ } finally {
1871
+ await handle.close();
1872
+ }
1873
+ await rename(tmp, target);
1874
+ try {
1875
+ const dir = await open(dirname(target), "r");
1876
+ try {
1877
+ await dir.sync();
1878
+ } finally {
1879
+ await dir.close();
1880
+ }
1881
+ } catch {
1882
+ }
1883
+ }
1884
+ async function inspectDocument(path) {
1885
+ let raw;
1886
+ try {
1887
+ await stat(path);
1888
+ } catch {
1889
+ return { status: "missing" };
1890
+ }
1891
+ try {
1892
+ raw = await readFile(path, "utf8");
1893
+ } catch (error) {
1894
+ return { status: "corrupt", detail: `unreadable: ${error instanceof Error ? error.message : String(error)}` };
1895
+ }
1896
+ if (raw.trim().length === 0) return { status: "empty" };
1897
+ let parsed;
1898
+ try {
1899
+ parsed = JSON.parse(raw);
1900
+ } catch (error) {
1901
+ return { status: "corrupt", detail: error instanceof Error ? error.message : "invalid JSON" };
1902
+ }
1903
+ if (Array.isArray(parsed)) return { status: "v1-legacy" };
1904
+ if (parsed === null || typeof parsed !== "object") return { status: "corrupt", detail: "not an object" };
1905
+ const doc = parsed;
1906
+ if (!Array.isArray(doc.items)) return { status: "corrupt", detail: "missing items array" };
1907
+ const version = typeof doc.schemaVersion === "number" ? doc.schemaVersion : 1;
1908
+ if (version > SCHEMA_VERSION) return { status: "future", schemaVersion: version };
1909
+ if (version < SCHEMA_VERSION) return { status: "v1-legacy" };
1910
+ return { status: "current", schemaVersion: version, count: doc.items.length };
1911
+ }
1912
+ async function readDocument(path, onCorrupt, quarantine = true) {
1913
+ const state = await inspectDocument(path);
1914
+ if (state.status === "missing" || state.status === "empty") return [];
1915
+ if (state.status === "future") {
1916
+ const error = new Error(
1917
+ `${path} was written by a newer OmniNode (schema v${state.schemaVersion} > v${SCHEMA_VERSION})`
1918
+ );
1919
+ onCorrupt?.(path, error);
1920
+ return [];
1921
+ }
1922
+ let raw;
1923
+ try {
1924
+ raw = await readFile(path, "utf8");
1925
+ } catch (error) {
1926
+ onCorrupt?.(path, error);
1927
+ return [];
1928
+ }
1929
+ try {
1930
+ const parsed = JSON.parse(raw);
1931
+ if (Array.isArray(parsed)) return parsed;
1932
+ return parsed.items ?? [];
1933
+ } catch (error) {
1934
+ if (quarantine) {
1935
+ const quarantinePath = `${path}.corrupt-${Date.now()}`;
1936
+ try {
1937
+ await rename(path, quarantinePath);
1938
+ logger.child({ component: "persistence" }).warn(
1939
+ `Quarantined unreadable store file ${path} \u2192 ${quarantinePath}`
1940
+ );
1941
+ } catch {
1942
+ }
1943
+ }
1944
+ onCorrupt?.(path, error);
1945
+ return [];
1946
+ }
1947
+ }
1825
1948
  var JsonFileStore = class {
1826
1949
  filePath;
1827
1950
  schemaVersion;
1828
1951
  onCorrupt;
1952
+ quarantine;
1829
1953
  log;
1830
1954
  queue = Promise.resolve();
1831
1955
  constructor(options) {
1832
1956
  this.filePath = `${options.directory ?? `${process.cwd()}/.omninode`}/${options.fileName}`;
1833
- this.schemaVersion = options.schemaVersion ?? 1;
1957
+ this.schemaVersion = options.schemaVersion ?? SCHEMA_VERSION;
1834
1958
  this.onCorrupt = options.onCorrupt;
1959
+ this.quarantine = options.quarantine ?? true;
1835
1960
  this.log = logger.child({ component: "persistence", file: options.fileName });
1836
1961
  }
1837
1962
  get path() {
1838
1963
  return this.filePath;
1839
1964
  }
1840
- /** All items, or [] when missing/corrupt. */
1965
+ /** All items, or [] when missing/migrated-from-v1/corrupt. */
1841
1966
  async readAll() {
1842
- let raw;
1843
- try {
1844
- raw = await readFile(this.filePath, "utf8");
1845
- } catch {
1846
- return [];
1847
- }
1848
- try {
1849
- const parsed = JSON.parse(raw);
1850
- if (Array.isArray(parsed)) return parsed;
1851
- if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.items)) {
1852
- return parsed.items;
1853
- }
1854
- throw new Error("persisted file has an unrecognized shape");
1855
- } catch (error) {
1856
- this.log.warn(`Treating ${this.filePath} as empty \u2014 it could not be parsed.`);
1857
- this.onCorrupt?.(this.filePath, error);
1858
- return [];
1859
- }
1967
+ return readDocument(this.filePath, this.onCorrupt, this.quarantine);
1860
1968
  }
1861
1969
  async writeAll(items) {
1862
1970
  const document = { schemaVersion: this.schemaVersion, items };
1863
- const tmp = `${this.filePath}.tmp`;
1864
- await mkdir(dirname(this.filePath), { recursive: true });
1865
- await writeFile(tmp, `${JSON.stringify(document, null, 2)}
1866
- `, "utf8");
1867
- await rename(tmp, this.filePath);
1971
+ await writeFileAtomic(this.filePath, `${JSON.stringify(document, null, 2)}
1972
+ `);
1868
1973
  }
1869
1974
  /** Serializes an async operation against this file. */
1870
1975
  synchronized(operation) {
@@ -2167,7 +2272,7 @@ function sleep(ms) {
2167
2272
  }
2168
2273
 
2169
2274
  // src/audit/index.ts
2170
- import { appendFile, mkdir as mkdir2 } from "fs/promises";
2275
+ import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
2171
2276
  import { dirname as dirname2 } from "path";
2172
2277
  var FileAuditLog = class {
2173
2278
  filePath;
@@ -2179,8 +2284,15 @@ var FileAuditLog = class {
2179
2284
  async record(event) {
2180
2285
  try {
2181
2286
  await mkdir2(dirname2(this.filePath), { recursive: true });
2182
- await appendFile(this.filePath, `${JSON.stringify(event)}
2287
+ const { open: open2 } = await import("fs/promises");
2288
+ const handle = await open2(this.filePath, "a");
2289
+ try {
2290
+ await handle.appendFile(`${JSON.stringify(event)}
2183
2291
  `, "utf8");
2292
+ await handle.sync();
2293
+ } finally {
2294
+ await handle.close();
2295
+ }
2184
2296
  } catch (error) {
2185
2297
  this.log.warn(
2186
2298
  `Audit write failed: ${error instanceof Error ? error.message : String(error)}`
@@ -2189,7 +2301,6 @@ var FileAuditLog = class {
2189
2301
  }
2190
2302
  /** Recent events, oldest first; used by the CLI. */
2191
2303
  async recent(limit = 20) {
2192
- const { readFile: readFile2 } = await import("fs/promises");
2193
2304
  let raw;
2194
2305
  try {
2195
2306
  raw = await readFile2(this.filePath, "utf8");
@@ -3822,6 +3933,7 @@ function truncate3(text, max) {
3822
3933
  }
3823
3934
 
3824
3935
  // src/reports/service.ts
3936
+ var MAX_REPORT_BYTES = 256 * 1024;
3825
3937
  var ReportService = class {
3826
3938
  constructor(store, log = logger) {
3827
3939
  this.store = store;
@@ -3856,6 +3968,16 @@ var ReportService = class {
3856
3968
  const reports = [];
3857
3969
  const rejected = [];
3858
3970
  for (const candidate of candidates) {
3971
+ const size = Buffer.byteLength(JSON.stringify(candidate), "utf8");
3972
+ if (size > MAX_REPORT_BYTES) {
3973
+ rejected.push({
3974
+ key: candidate?.id ?? "(unknown)",
3975
+ reason: `report exceeds the ${MAX_REPORT_BYTES} byte limit (${size} bytes)`,
3976
+ issues: []
3977
+ });
3978
+ this.log.warn(`Rejected oversized report (${size} bytes).`);
3979
+ continue;
3980
+ }
3859
3981
  const result = validateReport(candidate);
3860
3982
  if (result.valid) {
3861
3983
  reports.push(result.report);
@@ -3976,9 +4098,59 @@ var ProjectStores = class {
3976
4098
  this.audit = new FileAuditLog(directory);
3977
4099
  }
3978
4100
  };
4101
+ var STORE_FILES = [
4102
+ "tasks.json",
4103
+ "pipelines.json",
4104
+ "reports.json",
4105
+ "plans.json",
4106
+ "memory.json"
4107
+ ];
4108
+ async function migrateProjectStores(directory = `${process.cwd()}/.omninode`, options = {}) {
4109
+ const reports = [];
4110
+ for (const file of STORE_FILES) {
4111
+ const path = `${directory}/${file}`;
4112
+ const state = await inspectDocument(path);
4113
+ if (state.status === "missing") {
4114
+ reports.push({ file, state: state.status, migrated: false });
4115
+ continue;
4116
+ }
4117
+ if (state.status === "current") {
4118
+ reports.push({ file, state: state.status, before: state.count, after: state.count, migrated: false });
4119
+ continue;
4120
+ }
4121
+ if (state.status === "future") {
4122
+ reports.push({
4123
+ file,
4124
+ state: state.status,
4125
+ migrated: false,
4126
+ note: `written by a newer OmniNode (schema v${state.schemaVersion}); not touched`
4127
+ });
4128
+ continue;
4129
+ }
4130
+ if (state.status === "corrupt") {
4131
+ reports.push({ file, state: state.status, migrated: false, note: state.detail });
4132
+ continue;
4133
+ }
4134
+ const items = await readDocument(path, void 0, false);
4135
+ if (!options.checkOnly) {
4136
+ await writeFileAtomic(path, `${JSON.stringify({ schemaVersion: SCHEMA_VERSION, items }, null, 2)}
4137
+ `);
4138
+ }
4139
+ const beforeCount = items.length;
4140
+ reports.push({
4141
+ file,
4142
+ state: state.status,
4143
+ before: beforeCount,
4144
+ after: items.length,
4145
+ migrated: !options.checkOnly,
4146
+ ...options.checkOnly ? { note: "would migrate" } : {}
4147
+ });
4148
+ }
4149
+ return reports;
4150
+ }
3979
4151
 
3980
4152
  // src/version.ts
3981
- var OMNINODE_VERSION = "2.0.0-alpha.11";
4153
+ var OMNINODE_VERSION = "2.0.0-alpha.13";
3982
4154
  export {
3983
4155
  AgentAdapterRegistry,
3984
4156
  AgentError,
@@ -3997,6 +4169,7 @@ export {
3997
4169
  Logger,
3998
4170
  MAX_CONTEXT_CHARS,
3999
4171
  MAX_MESSAGE_BYTES,
4172
+ MAX_REPORT_BYTES,
4000
4173
  MemoryError,
4001
4174
  MemoryService,
4002
4175
  ModelPlanner,
@@ -4020,6 +4193,8 @@ export {
4020
4193
  RememberaMemoryProvider,
4021
4194
  ReportService,
4022
4195
  RoleRegistry,
4196
+ SCHEMA_VERSION,
4197
+ STORE_FILES,
4023
4198
  SUPPORTED_PROTOCOL_VERSIONS,
4024
4199
  TERMINAL_TASK_STATUSES,
4025
4200
  TaskEngine,
@@ -4048,6 +4223,7 @@ export {
4048
4223
  defaultProjectConfigYaml,
4049
4224
  defaultSearch,
4050
4225
  describeExit,
4226
+ describeSecretFindings,
4051
4227
  detectConflicts,
4052
4228
  encodeMessage,
4053
4229
  envelope,
@@ -4055,6 +4231,7 @@ export {
4055
4231
  extractMessage,
4056
4232
  extractReportFromText,
4057
4233
  findConfigFile,
4234
+ inspectDocument,
4058
4235
  isNetworkError,
4059
4236
  isOmniNodeError,
4060
4237
  isRetryable,
@@ -4065,6 +4242,7 @@ export {
4065
4242
  kindFromStatus,
4066
4243
  loadConfig,
4067
4244
  logger,
4245
+ migrateProjectStores,
4068
4246
  newMessageId,
4069
4247
  normalizeEvidence,
4070
4248
  normalizeFinding,
@@ -4079,6 +4257,7 @@ export {
4079
4257
  providerConfigSchema,
4080
4258
  providerHttpError,
4081
4259
  providerTransportError,
4260
+ readDocument,
4082
4261
  registryKey,
4083
4262
  requestJson,
4084
4263
  requireCompletion,
@@ -4086,10 +4265,12 @@ export {
4086
4265
  resolveEnvPolicy,
4087
4266
  roleConfigSchema,
4088
4267
  sanitizeChunk,
4268
+ scanForInlineSecrets,
4089
4269
  similar,
4090
4270
  validateEnvelope,
4091
4271
  validatePlan,
4092
4272
  validateReport,
4093
- validateWorkingDirectory
4273
+ validateWorkingDirectory,
4274
+ writeFileAtomic
4094
4275
  };
4095
4276
  //# sourceMappingURL=index.js.map