@wrongstack/core 0.309.1 → 0.310.1

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.
Files changed (120) hide show
  1. package/dist/chronicle/index.js +113 -41
  2. package/dist/chronicle/metrics-ingest.d.ts +7 -0
  3. package/dist/chronicle/metrics-schema.d.ts +4 -1
  4. package/dist/chronicle/project-server.js +94 -38
  5. package/dist/chronicle/query.d.ts +1 -0
  6. package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
  7. package/dist/chronicle/types.d.ts +2 -0
  8. package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
  9. package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
  10. package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
  11. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
  12. package/dist/coordination/fleet-status-tool.d.ts +1 -1
  13. package/dist/coordination/index.d.ts +5 -1
  14. package/dist/coordination/index.js +2061 -346
  15. package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
  16. package/dist/coordination/kanban-ops-port.d.ts +21 -0
  17. package/dist/coordination/mailbox-hooks.d.ts +1 -1
  18. package/dist/coordination/mailbox-project-server.js +14 -9
  19. package/dist/core/context.d.ts +35 -44
  20. package/dist/core/conversation-state.d.ts +16 -52
  21. package/dist/core/index.js +53 -16
  22. package/dist/core/provider-runner.d.ts +2 -2
  23. package/dist/core/run-env.d.ts +7 -28
  24. package/dist/core/streaming-response-builder.d.ts +2 -2
  25. package/dist/execution/index.js +111 -153
  26. package/dist/goal/index.js +100 -22
  27. package/dist/hq/auth-store.d.ts +9 -0
  28. package/dist/hq/cost-bridge.d.ts +1 -1
  29. package/dist/hq/index.js +24 -2
  30. package/dist/index.d.ts +12 -5
  31. package/dist/index.js +27808 -33937
  32. package/dist/infrastructure/index.js +210 -132
  33. package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
  34. package/dist/infrastructure/token-counter.d.ts +5 -1
  35. package/dist/kernel/events/file-events.d.ts +6 -0
  36. package/dist/kernel/events/provider-events.d.ts +10 -7
  37. package/dist/kernel/events/session-events.d.ts +4 -4
  38. package/dist/kernel/events/tool-events.d.ts +12 -2
  39. package/dist/plugin/index.js +105 -35
  40. package/dist/security/index.d.ts +1 -0
  41. package/dist/security/index.js +47 -18
  42. package/dist/security/kanban-boundary.d.ts +1 -1
  43. package/dist/security/kanban-governance-port.d.ts +28 -0
  44. package/dist/session-catalog/index.js +2 -3
  45. package/dist/session-catalog/project-server.js +2 -3
  46. package/dist/session-catalog/protocol.d.ts +1 -1
  47. package/dist/session-catalog/registry.d.ts +1 -1
  48. package/dist/session-catalog/store-schema.d.ts +1 -1
  49. package/dist/session-catalog/store.d.ts +1 -1
  50. package/dist/storage/annotations-store.d.ts +1 -1
  51. package/dist/storage/board-store-port.d.ts +45 -0
  52. package/dist/storage/completed-work-checkpoint.d.ts +1 -1
  53. package/dist/storage/config-loader/types.d.ts +1 -1
  54. package/dist/storage/event-bus-port.d.ts +27 -0
  55. package/dist/storage/file-session-writer.d.ts +47 -5
  56. package/dist/storage/goal-coordination.d.ts +1 -1
  57. package/dist/storage/goal-store.d.ts +1 -1
  58. package/dist/storage/index.d.ts +3 -4
  59. package/dist/storage/index.js +1063 -1459
  60. package/dist/storage/plan-store.d.ts +1 -1
  61. package/dist/storage/queue-store.d.ts +1 -1
  62. package/dist/storage/replay-log-store.d.ts +1 -1
  63. package/dist/storage/session-recovery.d.ts +10 -0
  64. package/dist/storage/session-resume-validation.d.ts +13 -1
  65. package/dist/storage/session-store/events.d.ts +1 -1
  66. package/dist/storage/session-store/load-session-data.d.ts +1 -1
  67. package/dist/storage/session-store/rename-session.d.ts +1 -1
  68. package/dist/storage/session-store/resume-session.d.ts +3 -1
  69. package/dist/storage/session-store/session-store-index.d.ts +2 -1
  70. package/dist/storage/session-store/types.d.ts +1 -1
  71. package/dist/storage/session-store.d.ts +70 -0
  72. package/dist/storage/session-summary-tracker.d.ts +8 -0
  73. package/dist/storage/session-write-buffer.d.ts +31 -2
  74. package/dist/storage/task-store.d.ts +1 -1
  75. package/dist/storage/todos-checkpoint.d.ts +1 -1
  76. package/dist/storage/tool-audit-log.d.ts +1 -1
  77. package/dist/tasking/index.js +100 -22
  78. package/dist/tasking/task-tracker.d.ts +24 -1
  79. package/dist/types/compactor.d.ts +2 -2
  80. package/dist/types/config/runtime.d.ts +7 -0
  81. package/dist/types/config/tools.d.ts +30 -0
  82. package/dist/types/context.d.ts +212 -0
  83. package/dist/types/conversation-state.d.ts +109 -0
  84. package/dist/types/error-handler.d.ts +2 -2
  85. package/dist/types/file-event-record.d.ts +6 -0
  86. package/dist/types/index.d.ts +4 -4
  87. package/dist/types/index.js +19 -1
  88. package/dist/types/permission.d.ts +3 -3
  89. package/dist/types/plugin.d.ts +3 -3
  90. package/dist/types/provider-runner.d.ts +2 -2
  91. package/dist/types/provider.d.ts +10 -0
  92. package/dist/types/run-env.d.ts +32 -0
  93. package/dist/types/session.d.ts +3 -0
  94. package/dist/types/slash-command.d.ts +2 -2
  95. package/dist/types/token-counter.d.ts +30 -1
  96. package/dist/types/tool-executor.d.ts +2 -2
  97. package/dist/types/tool.d.ts +5 -5
  98. package/dist/utils/context-breakdown.d.ts +2 -2
  99. package/dist/utils/context-evidence.d.ts +12 -12
  100. package/dist/utils/crash-shield.d.ts +9 -0
  101. package/dist/utils/heap-watchdog.js +11 -3
  102. package/dist/utils/index.d.ts +1 -1
  103. package/dist/utils/index.js +72 -156
  104. package/dist/utils/regex-guard.d.ts +7 -30
  105. package/dist/utils/todos-format.d.ts +1 -1
  106. package/dist/utils/tree-kill.d.ts +2 -0
  107. package/dist/utils/tree-kill.js +1 -0
  108. package/dist/wiring/proxy-rewrite.d.ts +76 -0
  109. package/dist/wiring/proxy-rewrite.js +70 -0
  110. package/instructions/coordination/subagent-baseline.md +10 -0
  111. package/instructions/system-lite.md +2 -0
  112. package/instructions/system-pro.md +40 -0
  113. package/instructions/system.md +15 -0
  114. package/package.json +10 -9
  115. package/dist/defaults/index.d.ts +0 -69
  116. package/dist/defaults/index.js +0 -38133
  117. /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
  118. /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
  119. /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
  120. /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
@@ -5159,7 +5159,11 @@ function relationKeys(event) {
5159
5159
  add("attempt", event.correlation.attemptId, "explicit");
5160
5160
  add("decision", event.attributes?.decisionId, "explicit");
5161
5161
  add("network_request", event.attributes?.requestId, "explicit");
5162
- add("prompt_manifest", event.attributes?.promptManifest?.manifestId, "explicit");
5162
+ add(
5163
+ "prompt_manifest",
5164
+ event.correlation.promptManifestId ?? event.attributes?.promptManifest?.manifestId,
5165
+ "explicit"
5166
+ );
5163
5167
  add("resource_lineage", event.resource?.id, "inferred");
5164
5168
  if (event.eventType === "metrics.rollup" && event.attributes?.signal === "tool.resource.observed") {
5165
5169
  const resources = event.attributes.resources;
@@ -5181,6 +5185,7 @@ function matches(event, query) {
5181
5185
  if (!equal(query.agentId, event.scope.agentId) || !equal(query.taskId, event.scope.taskId)) return false;
5182
5186
  if (!equal(query.providerId, event.runtime?.providerId) || !equal(query.modelId, event.runtime?.modelId)) return false;
5183
5187
  if (!equal(query.traceId, event.correlation.traceId) || !equal(query.logicalRequestId, event.correlation.logicalRequestId)) return false;
5188
+ if (!equal(query.promptManifestId, event.correlation.promptManifestId)) return false;
5184
5189
  if (!equal(query.attemptId, event.correlation.attemptId) || !equal(query.toolCallId, event.correlation.toolCallId)) return false;
5185
5190
  if (!equal(query.resourceKind, event.resource?.kind) || !equal(query.resourceId, event.resource?.id)) return false;
5186
5191
  if (query.path && normalize(event.resource?.path) !== normalize(query.path)) return false;
@@ -5321,6 +5326,7 @@ function isChronicleEvent(value) {
5321
5326
  ]) && typeof value.correlation.traceId === "string" && typeof value.correlation.spanId === "string" && optionalStrings(value.correlation, [
5322
5327
  "parentSpanId",
5323
5328
  "logicalRequestId",
5329
+ "promptManifestId",
5324
5330
  "attemptId",
5325
5331
  "toolCallId"
5326
5332
  ]) && isRuntime(value.runtime) && isResource(value.resource) && (value.attributes === void 0 || isRecord2(value.attributes)) && (value.tags === void 0 || isStringRecord(value.tags));
@@ -5510,7 +5516,7 @@ var ChronicleQuotaManager = class {
5510
5516
  };
5511
5517
 
5512
5518
  // src/chronicle/sqlite-journal-schema.ts
5513
- import { createRequire } from "node:module";
5519
+ import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
5514
5520
 
5515
5521
  // src/utils/index.ts
5516
5522
  init_atomic_write();
@@ -5928,20 +5934,18 @@ var CHRONICLE_SQLITE_FILE = "chronicle.sqlite";
5928
5934
  var LEGACY_JSONL_MIGRATION_KEY = "legacy-jsonl-v1";
5929
5935
  var LEGACY_JSONL_QUARANTINE_KEY = "legacy-jsonl-v1:quarantine";
5930
5936
  var LEGACY_JSONL_BOUNDARY_KEY = "legacy-jsonl-v1:boundary";
5931
- var SCHEMA_VERSION = 2;
5937
+ var SCHEMA_VERSION = 3;
5932
5938
  var Ctor;
5933
5939
  function loadDatabaseSync() {
5934
5940
  if (Ctor) return Ctor;
5935
- if (Ctor === null) throw new Error("node:sqlite is unavailable in this runtime");
5941
+ if (Ctor === null) throw new Error("SQLite is unavailable in this runtime");
5936
5942
  try {
5937
- Ctor = withSqliteExperimentalWarningSuppressed(
5938
- () => createRequire(import.meta.url)("node:sqlite").DatabaseSync
5939
- );
5943
+ Ctor = withSqliteExperimentalWarningSuppressed(loadRuntimeDatabaseSync);
5940
5944
  return Ctor;
5941
5945
  } catch (error) {
5942
5946
  Ctor = null;
5943
5947
  throw new Error(
5944
- "The Chronicle journal needs Node's built-in SQLite (node:sqlite, Node >= 22.5): " + (error instanceof Error ? error.message : String(error))
5948
+ "The Chronicle journal needs node:sqlite (Node >= 22.5) or bun:sqlite: " + (error instanceof Error ? error.message : String(error))
5945
5949
  );
5946
5950
  }
5947
5951
  }
@@ -5956,6 +5960,7 @@ function projectEvent(event) {
5956
5960
  taskId: event.scope.taskId ?? null,
5957
5961
  traceId: event.correlation?.traceId ?? null,
5958
5962
  logicalRequestId: event.correlation?.logicalRequestId ?? null,
5963
+ promptManifestId: event.correlation?.promptManifestId ?? null,
5959
5964
  resourceKind: event.resource?.kind ?? null,
5960
5965
  resourceId: event.resource?.id ?? null,
5961
5966
  resourcePath: event.resource?.path ?? null,
@@ -5980,6 +5985,7 @@ function ensureChronicleSchema(db) {
5980
5985
  task_id TEXT,
5981
5986
  trace_id TEXT,
5982
5987
  logical_request_id TEXT,
5988
+ prompt_manifest_id TEXT,
5983
5989
  resource_kind TEXT,
5984
5990
  resource_id TEXT,
5985
5991
  resource_path TEXT,
@@ -6007,6 +6013,10 @@ function ensureChronicleSchema(db) {
6007
6013
  if (version < 2) {
6008
6014
  db.exec("DROP INDEX IF EXISTS events_resource_path");
6009
6015
  }
6016
+ if (version > 0 && version < 3) {
6017
+ db.exec("ALTER TABLE events ADD COLUMN prompt_manifest_id TEXT");
6018
+ }
6019
+ db.exec("CREATE INDEX IF NOT EXISTS events_prompt_manifest ON events(prompt_manifest_id)");
6010
6020
  if (version !== SCHEMA_VERSION) {
6011
6021
  db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
6012
6022
  }
@@ -6043,6 +6053,7 @@ function pushDown(query) {
6043
6053
  eq("task_id", query.taskId);
6044
6054
  eq("trace_id", query.traceId);
6045
6055
  eq("logical_request_id", query.logicalRequestId);
6056
+ eq("prompt_manifest_id", query.promptManifestId);
6046
6057
  eq("resource_kind", query.resourceKind);
6047
6058
  eq("resource_id", query.resourceId);
6048
6059
  if (query.eventTypes?.length) {
@@ -6399,6 +6410,7 @@ var ChronicleSqliteJournal = class {
6399
6410
  row.taskId,
6400
6411
  row.traceId,
6401
6412
  row.logicalRequestId,
6413
+ row.promptManifestId,
6402
6414
  row.resourceKind,
6403
6415
  row.resourceId,
6404
6416
  row.resourcePath,
@@ -6558,9 +6570,9 @@ var ChronicleSqliteJournal = class {
6558
6570
  const insert = this.db.prepare(
6559
6571
  `INSERT INTO events (
6560
6572
  day, sequence, event_id, hash, previous_hash, occurred_at, event_type, outcome,
6561
- project_id, session_id, agent_id, task_id, trace_id, logical_request_id,
6573
+ project_id, session_id, agent_id, task_id, trace_id, logical_request_id, prompt_manifest_id,
6562
6574
  resource_kind, resource_id, resource_path, duration_ns, payload
6563
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
6575
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
6564
6576
  );
6565
6577
  const checkpoint = this.db.prepare(
6566
6578
  `INSERT INTO chain_checkpoint (day, sequence, hash) VALUES (?, ?, ?)
@@ -6589,6 +6601,7 @@ var ChronicleSqliteJournal = class {
6589
6601
  row.taskId,
6590
6602
  row.traceId,
6591
6603
  row.logicalRequestId,
6604
+ row.promptManifestId,
6592
6605
  row.resourceKind,
6593
6606
  row.resourceId,
6594
6607
  row.resourcePath,
@@ -6675,9 +6688,9 @@ var ChronicleSqliteJournal = class {
6675
6688
  insert: this.db.prepare(
6676
6689
  `INSERT INTO events (
6677
6690
  day, sequence, event_id, hash, previous_hash, occurred_at, event_type, outcome,
6678
- project_id, session_id, agent_id, task_id, trace_id, logical_request_id,
6691
+ project_id, session_id, agent_id, task_id, trace_id, logical_request_id, prompt_manifest_id,
6679
6692
  resource_kind, resource_id, resource_path, duration_ns, payload
6680
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
6693
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
6681
6694
  ),
6682
6695
  trimBoundary: this.db.prepare(
6683
6696
  "SELECT day, sequence, hash FROM events ORDER BY day, sequence LIMIT 1 OFFSET ?"
@@ -6718,8 +6731,8 @@ var ChronicleSqliteJournal = class {
6718
6731
  };
6719
6732
 
6720
6733
  // src/chronicle/metrics-schema.ts
6721
- import { createRequire as createRequire2 } from "node:module";
6722
- var SCHEMA_VERSION2 = 3;
6734
+ import { loadRuntimeDatabaseSync as loadRuntimeDatabaseSync2 } from "@wrongstack/persistence";
6735
+ var SCHEMA_VERSION2 = 4;
6723
6736
  var READ_CHUNK_BYTES = 1024 * 1024;
6724
6737
  var SQLITE_SOURCE_PREFIX = "sqlite:";
6725
6738
  var SQLITE_INGEST_BATCH = 2e3;
@@ -6737,16 +6750,14 @@ var EMPTY_FAMILIES = {
6737
6750
  var Ctor2;
6738
6751
  function loadDatabaseSync2() {
6739
6752
  if (Ctor2) return Ctor2;
6740
- if (Ctor2 === null) throw new Error("node:sqlite is unavailable in this runtime");
6753
+ if (Ctor2 === null) throw new Error("SQLite is unavailable in this runtime");
6741
6754
  try {
6742
- Ctor2 = withSqliteExperimentalWarningSuppressed(
6743
- () => createRequire2(import.meta.url)("node:sqlite").DatabaseSync
6744
- );
6755
+ Ctor2 = withSqliteExperimentalWarningSuppressed(loadRuntimeDatabaseSync2);
6745
6756
  return Ctor2;
6746
6757
  } catch (error) {
6747
6758
  Ctor2 = null;
6748
6759
  throw new Error(
6749
- "Chronicle metrics need Node's built-in SQLite (node:sqlite, Node >= 22.5): " + (error instanceof Error ? error.message : String(error))
6760
+ "Chronicle metrics need node:sqlite (Node >= 22.5) or bun:sqlite: " + (error instanceof Error ? error.message : String(error))
6750
6761
  );
6751
6762
  }
6752
6763
  }
@@ -6807,6 +6818,9 @@ function ensureMetricsSchema(db) {
6807
6818
  tool_name TEXT NOT NULL DEFAULT '',
6808
6819
  provider_id TEXT NOT NULL DEFAULT '',
6809
6820
  model_id TEXT NOT NULL DEFAULT '',
6821
+ logical_request_id TEXT NOT NULL DEFAULT '',
6822
+ prompt_manifest_id TEXT NOT NULL DEFAULT '',
6823
+ provenance_confidence TEXT NOT NULL DEFAULT 'unknown',
6810
6824
  source TEXT NOT NULL DEFAULT ''
6811
6825
  );
6812
6826
  CREATE INDEX IF NOT EXISTS idx_file_lineage_path ON file_lineage(path_key, occurred_at);
@@ -6899,6 +6913,20 @@ var ChronicleMetricsIngester = class {
6899
6913
  db;
6900
6914
  directory;
6901
6915
  dbPath;
6916
+ /**
6917
+ * Prepared-statement cache for the per-event ingest hot paths. Preparing
6918
+ * the same SQL on every event was the dominant ingest cost; node:sqlite
6919
+ * statements are reusable, so each distinct SQL string is prepared once.
6920
+ */
6921
+ stmtCache = /* @__PURE__ */ new Map();
6922
+ stmt(sql) {
6923
+ let s = this.stmtCache.get(sql);
6924
+ if (!s) {
6925
+ s = this.db.prepare(sql);
6926
+ this.stmtCache.set(sql, s);
6927
+ }
6928
+ return s;
6929
+ }
6902
6930
  async refresh() {
6903
6931
  const result = {
6904
6932
  ingestedEvents: 0,
@@ -6928,7 +6956,7 @@ var ChronicleMetricsIngester = class {
6928
6956
  let base;
6929
6957
  if (!migrated) {
6930
6958
  base = offsets.get(key) ?? 0;
6931
- } else if (boundary !== null && boundary.has(key)) {
6959
+ } else if (boundary?.has(key)) {
6932
6960
  base = Math.max(offsets.get(key) ?? 0, boundary.get(key));
6933
6961
  } else {
6934
6962
  base = rebuilt ? 0 : offsets.get(key) ?? 0;
@@ -7151,11 +7179,11 @@ var ChronicleMetricsIngester = class {
7151
7179
  }
7152
7180
  ingestDailyCounters(event) {
7153
7181
  const day = eventDay(event);
7154
- this.db.prepare("INSERT OR IGNORE INTO daily_counters (day) VALUES (?)").run(day);
7155
- const bump = (sql, ...params) => this.db.prepare(`UPDATE daily_counters SET ${sql} WHERE day = ?`).run(...params, day);
7182
+ this.stmt("INSERT OR IGNORE INTO daily_counters (day) VALUES (?)").run(day);
7183
+ const bump = (sql, ...params) => this.stmt(`UPDATE daily_counters SET ${sql} WHERE day = ?`).run(...params, day);
7156
7184
  const family = signalFamily(event);
7157
7185
  const failed = isTerminalFailure(event) ? 1 : 0;
7158
- this.db.prepare(
7186
+ this.stmt(
7159
7187
  `INSERT INTO family_daily (day, family, count, failure_count) VALUES (?, ?, 1, ?)
7160
7188
  ON CONFLICT(day, family) DO UPDATE SET count = count + 1, failure_count = failure_count + excluded.failure_count`
7161
7189
  ).run(day, family, failed);
@@ -7164,12 +7192,15 @@ var ChronicleMetricsIngester = class {
7164
7192
  bump("cancellations = cancellations + 1");
7165
7193
  if (family === "agent") bump("agent_events = agent_events + 1");
7166
7194
  if (event.correlation.logicalRequestId) {
7167
- this.db.prepare(
7195
+ this.stmt(
7168
7196
  "INSERT OR IGNORE INTO logical_request_daily (day, logical_request_id) VALUES (?, ?)"
7169
7197
  ).run(day, event.correlation.logicalRequestId);
7170
7198
  }
7171
7199
  if (event.scope.agentId) {
7172
- this.db.prepare("INSERT OR IGNORE INTO agent_daily (day, agent_id) VALUES (?, ?)").run(day, event.scope.agentId);
7200
+ this.stmt("INSERT OR IGNORE INTO agent_daily (day, agent_id) VALUES (?, ?)").run(
7201
+ day,
7202
+ event.scope.agentId
7203
+ );
7173
7204
  }
7174
7205
  const type = event.eventType;
7175
7206
  if (type === "decision.requested") bump("decisions = decisions + 1");
@@ -7191,7 +7222,10 @@ var ChronicleMetricsIngester = class {
7191
7222
  if (event.resource?.kind === "file" || type.startsWith("file.")) {
7192
7223
  bump("file_events_all = file_events_all + 1");
7193
7224
  if (event.resource?.path) {
7194
- this.db.prepare("INSERT OR IGNORE INTO file_seen_daily (day, path_key) VALUES (?, ?)").run(day, normalizePathKey(event.resource.path));
7225
+ this.stmt("INSERT OR IGNORE INTO file_seen_daily (day, path_key) VALUES (?, ?)").run(
7226
+ day,
7227
+ normalizePathKey(event.resource.path)
7228
+ );
7195
7229
  }
7196
7230
  }
7197
7231
  }
@@ -7200,8 +7234,10 @@ var ChronicleMetricsIngester = class {
7200
7234
  const providerId = event.runtime?.providerId ?? asString(readPath2(event.attributes ?? {}, "from.providerId")) ?? "";
7201
7235
  const modelId = event.runtime?.modelId ?? asString(readPath2(event.attributes ?? {}, "from.model")) ?? "";
7202
7236
  if (!providerId && !modelId) return;
7203
- this.db.prepare("INSERT OR IGNORE INTO provider_daily (day, provider_id, model_id) VALUES (?, ?, ?)").run(day, providerId, modelId);
7204
- const update = (sql, ...params) => this.db.prepare(
7237
+ this.stmt(
7238
+ "INSERT OR IGNORE INTO provider_daily (day, provider_id, model_id) VALUES (?, ?, ?)"
7239
+ ).run(day, providerId, modelId);
7240
+ const update = (sql, ...params) => this.stmt(
7205
7241
  `UPDATE provider_daily SET ${sql} WHERE day = ? AND provider_id = ? AND model_id = ?`
7206
7242
  ).run(...params, day, providerId, modelId);
7207
7243
  const duration = durationMs2(event);
@@ -7242,7 +7278,7 @@ var ChronicleMetricsIngester = class {
7242
7278
  if (typeof cost !== "number" || !Number.isFinite(cost)) return;
7243
7279
  const scopeKey2 = `${event.scope.projectId ?? ""}\0${event.scope.sessionId ?? ""}\0${event.scope.agentId ?? ""}`;
7244
7280
  const occurredAt = event.occurredAt ?? event.observedAt;
7245
- this.db.prepare(
7281
+ this.stmt(
7246
7282
  `INSERT INTO token_cost (scope_key, day, occurred_at, sequence, cost) VALUES (?, ?, ?, ?, ?)
7247
7283
  ON CONFLICT(scope_key) DO UPDATE SET
7248
7284
  day = excluded.day, occurred_at = excluded.occurred_at,
@@ -7256,8 +7292,8 @@ var ChronicleMetricsIngester = class {
7256
7292
  const taskId = event.scope.taskId ?? stringAt(attributes, "taskId");
7257
7293
  if (!taskId) return;
7258
7294
  const occurredAt = event.occurredAt ?? event.observedAt;
7259
- this.db.prepare("INSERT OR IGNORE INTO task_outcomes (task_id) VALUES (?)").run(taskId);
7260
- const set = (sql, ...params) => this.db.prepare(`UPDATE task_outcomes SET ${sql} WHERE task_id = ?`).run(...params, taskId);
7295
+ this.stmt("INSERT OR IGNORE INTO task_outcomes (task_id) VALUES (?)").run(taskId);
7296
+ const set = (sql, ...params) => this.stmt(`UPDATE task_outcomes SET ${sql} WHERE task_id = ?`).run(...params, taskId);
7261
7297
  const lineage = [
7262
7298
  ["run_id", stringAt(attributes, "runId")],
7263
7299
  ["board_id", event.scope.kanbanBoardId ?? stringAt(attributes, "boardId")],
@@ -7304,11 +7340,12 @@ var ChronicleMetricsIngester = class {
7304
7340
  if (!operation || operation === "read") return;
7305
7341
  const filePath = event.resource?.path ?? stringAt(attributes, "filePath");
7306
7342
  if (!filePath) return;
7307
- this.db.prepare(
7343
+ this.stmt(
7308
7344
  `INSERT OR IGNORE INTO file_lineage
7309
7345
  (event_id, path, path_key, operation, occurred_at, session_id, agent_id, task_id, board_id, run_id,
7310
- tool_name, provider_id, model_id, source)
7311
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
7346
+ tool_name, provider_id, model_id, logical_request_id, prompt_manifest_id,
7347
+ provenance_confidence, source)
7348
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
7312
7349
  ).run(
7313
7350
  event.eventId,
7314
7351
  normalizeKey2(filePath),
@@ -7323,10 +7360,17 @@ var ChronicleMetricsIngester = class {
7323
7360
  stringAt(attributes, "toolName") ?? "",
7324
7361
  event.runtime?.providerId ?? stringAt(attributes, "provider") ?? "",
7325
7362
  event.runtime?.modelId ?? stringAt(attributes, "model") ?? "",
7363
+ event.correlation.logicalRequestId ?? stringAt(attributes, "logicalRequestId") ?? "",
7364
+ event.correlation.promptManifestId ?? stringAt(attributes, "promptManifestId") ?? "",
7365
+ provenanceConfidence(attributes),
7326
7366
  stringAt(attributes, "source") ?? (event.eventType === "file.event" ? "tool" : "external")
7327
7367
  );
7328
7368
  }
7329
7369
  };
7370
+ function provenanceConfidence(attributes) {
7371
+ const value = attributes["provenanceConfidence"];
7372
+ return value === "explicit" || value === "correlated" || value === "inferred" ? value : "unknown";
7373
+ }
7330
7374
 
7331
7375
  // src/chronicle/metrics-store.ts
7332
7376
  var ChronicleMetricsStore = class _ChronicleMetricsStore {
@@ -7458,7 +7502,8 @@ var ChronicleMetricsStore = class _ChronicleMetricsStore {
7458
7502
  const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
7459
7503
  params.push(clampLimit(options.limit, 200));
7460
7504
  const projection = `path, operation, occurred_at, session_id, agent_id, task_id, board_id, run_id,
7461
- tool_name, provider_id, model_id, source`;
7505
+ tool_name, provider_id, model_id, logical_request_id, prompt_manifest_id,
7506
+ provenance_confidence, source`;
7462
7507
  const sql = options.latestPerPath ? `SELECT ${projection} FROM (
7463
7508
  SELECT ${projection}, ROW_NUMBER() OVER (
7464
7509
  PARTITION BY path_key ORDER BY occurred_at DESC, event_id DESC
@@ -7479,6 +7524,9 @@ var ChronicleMetricsStore = class _ChronicleMetricsStore {
7479
7524
  toolName: row.tool_name,
7480
7525
  providerId: row.provider_id,
7481
7526
  modelId: row.model_id,
7527
+ logicalRequestId: row.logical_request_id,
7528
+ promptManifestId: row.prompt_manifest_id,
7529
+ provenanceConfidence: row.provenance_confidence,
7482
7530
  source: row.source
7483
7531
  }));
7484
7532
  }
@@ -8128,6 +8176,7 @@ function legacyPartitionPath(location) {
8128
8176
  function useSqliteStore() {
8129
8177
  return process.env["WRONGSTACK_CHRONICLE_STORE"] !== "jsonl";
8130
8178
  }
8179
+ var MAX_OPEN_JOURNAL_DAYS = 2;
8131
8180
  var InlineChronicleProjectAccess = class {
8132
8181
  constructor(options) {
8133
8182
  this.options = options;
@@ -8236,6 +8285,26 @@ var InlineChronicleProjectAccess = class {
8236
8285
  get chronicleDirectory() {
8237
8286
  return path16.join(this.options.projectDir, "chronicle");
8238
8287
  }
8288
+ /**
8289
+ * Drop journals for days we will not write to again.
8290
+ *
8291
+ * Mirrors the daemon's `pruneJournals` in `project-server.ts`: without a cap
8292
+ * an inline access object that lives across midnight kept one open
8293
+ * `ChronicleJournal` (with its write buffer and file handle) per day, forever.
8294
+ */
8295
+ pruneJournals(currentDay) {
8296
+ if (this.journals.size <= MAX_OPEN_JOURNAL_DAYS) return;
8297
+ const keep = new Set(
8298
+ [...this.journals.keys()].sort().reverse().slice(0, MAX_OPEN_JOURNAL_DAYS)
8299
+ );
8300
+ keep.add(currentDay);
8301
+ for (const [day, journal] of this.journals) {
8302
+ if (keep.has(day)) continue;
8303
+ this.journals.delete(day);
8304
+ void journal.flush().catch(() => {
8305
+ });
8306
+ }
8307
+ }
8239
8308
  journalForToday() {
8240
8309
  const now = /* @__PURE__ */ new Date();
8241
8310
  const day = now.toISOString().slice(0, 10);
@@ -8252,6 +8321,7 @@ var InlineChronicleProjectAccess = class {
8252
8321
  retentionDays: this.options.retentionDays
8253
8322
  });
8254
8323
  this.journals.set(day, journal);
8324
+ this.pruneJournals(day);
8255
8325
  }
8256
8326
  return journal;
8257
8327
  }
@@ -1,6 +1,7 @@
1
1
  export { noOpVault } from '../types/secret-vault.js';
2
2
  export { clampSubagentCapabilities, DANGEROUS_FOR_SUBAGENTS, getDangerousCapabilities, hasCapability, hasDangerousCapabilityForSubagents, ToolCapabilities, type ToolCapability, WIDE_SUBAGENT_CAPABILITIES, } from './capabilities.js';
3
3
  export { decryptConfigSecrets, encryptConfigSecrets, isSecretField } from './config-secrets.js';
4
+ export { kanbanGovernance, type KanbanGovernancePort, setKanbanGovernance, } from './kanban-governance-port.js';
4
5
  export { DirectoryPermissionPolicy, type DirectoryPermissionPolicyOptions, matchRule, resolveTargetPath, } from './directory-permission-policy.js';
5
6
  export { DIRECTORY_POLICY_LIMITS, DIRECTORY_POLICY_SCHEMA_VERSION, type DirectoryPolicyDiagnostic, type DirectoryPolicyDiagnosticCode, type DirectoryPolicyValidationResult, validateDirectoryPolicy, } from './directory-policy-schema.js';
6
7
  export { ERROR_DETAIL_MAX, sanitizeApiError, scrubErrorDetail, scrubErrorText, } from './error-sanitize.js';
@@ -183,6 +183,20 @@ function isSecretField(name) {
183
183
  return SECRET_KEY_PATTERN.test(lc);
184
184
  }
185
185
 
186
+ // src/security/kanban-governance-port.ts
187
+ var notWired = () => {
188
+ throw new Error(
189
+ "KanbanGovernancePort is not wired \u2014 register the implementation at the CLI composition root (see setKanbanGovernance)."
190
+ );
191
+ };
192
+ var port = void 0;
193
+ function setKanbanGovernance(impl) {
194
+ port = impl;
195
+ }
196
+ function kanbanGovernance() {
197
+ return port ?? notWired();
198
+ }
199
+
186
200
  // src/security/directory-permission-policy.ts
187
201
  import * as path from "node:path";
188
202
 
@@ -1318,14 +1332,7 @@ import {
1318
1332
  // src/security/kanban-boundary.ts
1319
1333
  import { realpath } from "node:fs/promises";
1320
1334
  import * as path2 from "node:path";
1321
- import {
1322
- evaluateContractGraphReadiness,
1323
- evaluateKanbanBoundaryOpaque,
1324
- evaluateKanbanBoundaryPath,
1325
- readBoard,
1326
- resolveKanbanBoundaryLayers
1327
- } from "@wrongstack/kanban";
1328
- var GOVERNANCE_CONTROL_TOOLS = /* @__PURE__ */ new Set(["kanban", "plan", "task", "todo"]);
1335
+ var GOVERNANCE_CONTROL_TOOLS = /* @__PURE__ */ new Set(["kanban", "plan", "task", "todo", "nextsteps"]);
1329
1336
  var PATH_KEYS2 = /* @__PURE__ */ new Set([
1330
1337
  "path",
1331
1338
  "paths",
@@ -1360,7 +1367,10 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
1360
1367
  reason: "Kanban governance is mandatory before product mutation. Create a managed card, add its required details and executable acceptance criteria, then call kanban start_task to bind it to this run."
1361
1368
  } : { decision: "allow" };
1362
1369
  }
1363
- const board = await readBoard(identity.policyRoot ?? ctx.projectRoot, identity.boardId);
1370
+ const board = await kanbanGovernance().readBoard(
1371
+ identity.policyRoot ?? ctx.projectRoot,
1372
+ identity.boardId
1373
+ );
1364
1374
  if (!board) {
1365
1375
  return governanceRequired ? {
1366
1376
  decision: "block",
@@ -1386,22 +1396,39 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
1386
1396
  taskId: identity.taskId
1387
1397
  };
1388
1398
  }
1389
- const readiness = evaluateContractGraphReadiness(board, task.id);
1399
+ const readiness = kanbanGovernance().evaluateContractGraphReadiness(board, task.id);
1390
1400
  if (!readiness.ready) {
1401
+ const issueLines = readiness.issues.map((issue, index) => ` ${index + 1}. ${issue.message}`).join("\n");
1391
1402
  return {
1392
1403
  decision: "block",
1393
- reason: "Active card is not implementation-ready: " + readiness.issues.map((issue) => issue.message).join(" | "),
1404
+ reason: `Active card failed contract-readiness check (${readiness.issues.length} issue(s)):
1405
+ ` + issueLines + `
1406
+ Resolve the readiness issues, then call kanban start_task to retry.`,
1394
1407
  boardId: board.id,
1395
1408
  taskId: task.id,
1396
1409
  readinessIssues: readiness.issues
1397
1410
  };
1398
1411
  }
1399
- if (task.lifecycle?.currentStage !== "running" || task.assignment?.status !== "running") {
1400
- const lifecycleStage = task.lifecycle?.currentStage ?? "missing";
1401
- const assignmentStatus = task.assignment?.status ?? "missing";
1412
+ const lifecycleStage = task.lifecycle?.currentStage;
1413
+ const assignmentStatus = task.assignment?.status;
1414
+ const lifecycleOk = lifecycleStage === "running";
1415
+ const assignmentOk = assignmentStatus === "running";
1416
+ if (!lifecycleOk || !assignmentOk) {
1417
+ const failed = [];
1418
+ let reassessmentNote = "";
1419
+ if (!lifecycleOk) {
1420
+ failed.push(`lifecycle.currentStage (got: ${lifecycleStage ?? "missing"}; want: 'running')`);
1421
+ }
1422
+ if (!assignmentOk) {
1423
+ failed.push(`assignment.status (got: ${assignmentStatus ?? "missing"}; want: 'running')`);
1424
+ if (lifecycleOk && (assignmentStatus === "queued" || assignmentStatus === void 0 || assignmentStatus === null)) {
1425
+ reassessmentNote = '\nNote: lifecycle is still "running" but the assignment was cleared \u2014 the automated kanban reassessment agent likely reset the binding. Re-issue kanban start_task to re-bind the leader to this card.';
1426
+ }
1427
+ }
1402
1428
  return {
1403
1429
  decision: "block",
1404
- reason: `Active card must be in Running with a live assignment before product mutation (lifecycle: ${lifecycleStage}; assignment: ${assignmentStatus}). Call kanban start_task after completing the required card details.`,
1430
+ reason: `Active card failed ${failed.length} governance check(s) before product mutation:
1431
+ ` + failed.map((line, index) => ` ${index + 1}. ${line}`).join("\n") + reassessmentNote,
1405
1432
  boardId: board.id,
1406
1433
  taskId: task.id
1407
1434
  };
@@ -1419,7 +1446,7 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
1419
1446
  };
1420
1447
  }
1421
1448
  }
1422
- const layers = resolveKanbanBoundaryLayers(board, task);
1449
+ const layers = kanbanGovernance().resolveKanbanBoundaryLayers(board, task);
1423
1450
  if (layers.length === 0) return { decision: "allow" };
1424
1451
  const caps = tool.capabilities ?? [];
1425
1452
  const access = caps.includes("fs.write") || caps.includes("fs.write.outside-project") ? "write" : caps.includes("fs.read") ? "read" : void 0;
@@ -1427,10 +1454,10 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
1427
1454
  const candidates = access ? await extractCandidatePaths(tool.name, input, ctx) : [];
1428
1455
  let evaluation;
1429
1456
  if (shellLike || access && candidates.length === 0) {
1430
- evaluation = evaluateKanbanBoundaryOpaque(layers, tool.name);
1457
+ evaluation = kanbanGovernance().evaluateKanbanBoundaryOpaque(layers, tool.name);
1431
1458
  } else if (access) {
1432
1459
  const results = candidates.map(
1433
- (candidate) => evaluateKanbanBoundaryPath(layers, candidate, access)
1460
+ (candidate) => kanbanGovernance().evaluateKanbanBoundaryPath(layers, candidate, access)
1434
1461
  );
1435
1462
  evaluation = results.find((result) => result.decision === "block") ?? results.find((result) => result.decision === "confirm") ?? { decision: "allow" };
1436
1463
  } else {
@@ -4387,6 +4414,7 @@ export {
4387
4414
  hashRecoveryCode,
4388
4415
  isSecretField,
4389
4416
  isTrustDecisionAllowed,
4417
+ kanbanGovernance,
4390
4418
  matchRule,
4391
4419
  migratePlaintextSecrets,
4392
4420
  noOpVault,
@@ -4398,6 +4426,7 @@ export {
4398
4426
  sanitizeApiError,
4399
4427
  scrubErrorDetail,
4400
4428
  scrubErrorText,
4429
+ setKanbanGovernance,
4401
4430
  toolMutates,
4402
4431
  validateDirectoryPolicy,
4403
4432
  validateTrustPolicy,
@@ -1,4 +1,4 @@
1
- import { type KanbanBoundaryEvaluation, type KanbanContractReadinessIssue } from '@wrongstack/kanban';
1
+ import type { KanbanBoundaryEvaluation, KanbanContractReadinessIssue } from '@wrongstack/kanban';
2
2
  import type { Context } from '../core/context.js';
3
3
  import type { Tool } from '../types/tool.js';
4
4
  export interface ToolKanbanBoundaryEvaluation extends KanbanBoundaryEvaluation {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Kanban governance-evaluation port for the security area.
3
+ *
4
+ * `kanban-boundary.ts` previously imported the boundary evaluation and
5
+ * board-read functions directly from `@wrongstack/kanban`, keeping a
6
+ * core → kanban runtime edge (roadmap #11 / report §2.1). This port is
7
+ * the leaf contract; the CLI composition root registers the real
8
+ * implementation at boot alongside the storage and coordination ports.
9
+ *
10
+ * Type-only imports of kanban's data shapes remain permitted — they erase
11
+ * at compile time and do not create a runtime edge.
12
+ */
13
+ import type { KanbanBoundaryAccess, KanbanBoundaryEvaluation, KanbanBoundaryLayer, KanbanBoard, KanbanContractReadinessIssue, KanbanTask } from '@wrongstack/kanban';
14
+ export interface KanbanGovernancePort {
15
+ readBoard(projectRoot: string, boardId: string): Promise<KanbanBoard | null>;
16
+ resolveKanbanBoundaryLayers(board: KanbanBoard, task: Pick<KanbanTask, 'boundary'> | null | undefined): KanbanBoundaryLayer[];
17
+ evaluateKanbanBoundaryOpaque(layers: KanbanBoundaryLayer[], toolName: string): KanbanBoundaryEvaluation;
18
+ evaluateKanbanBoundaryPath(layers: KanbanBoundaryLayer[], candidate: unknown, access: KanbanBoundaryAccess): KanbanBoundaryEvaluation;
19
+ evaluateContractGraphReadiness(board: KanbanBoard, taskId: string): {
20
+ ready: boolean;
21
+ issues: KanbanContractReadinessIssue[];
22
+ };
23
+ }
24
+ /** Composition-root hook: register the real kanban-backed implementation. */
25
+ export declare function setKanbanGovernance(impl: KanbanGovernancePort): void;
26
+ /** Resolve the governance port (throws when not wired, mirroring BoardStorePort). */
27
+ export declare function kanbanGovernance(): KanbanGovernancePort;
28
+ //# sourceMappingURL=kanban-governance-port.d.ts.map
@@ -740,7 +740,7 @@ import * as fs5 from "node:fs";
740
740
  import * as path5 from "node:path";
741
741
 
742
742
  // src/coordination/sqlite-mailbox-schema.ts
743
- import { createRequire } from "node:module";
743
+ import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
744
744
 
745
745
  // src/utils/sqlite-warning.ts
746
746
  var SQLITE_EXPERIMENTAL_WARNING_RE = /sqlite is an experimental feature/i;
@@ -770,8 +770,7 @@ var DatabaseSyncCtor;
770
770
  function loadDatabaseSync() {
771
771
  if (DatabaseSyncCtor) return DatabaseSyncCtor;
772
772
  return withSqliteExperimentalWarningSuppressed(() => {
773
- const require2 = createRequire(import.meta.url);
774
- DatabaseSyncCtor = require2("node:sqlite").DatabaseSync;
773
+ DatabaseSyncCtor = loadRuntimeDatabaseSync();
775
774
  return DatabaseSyncCtor;
776
775
  });
777
776
  }
@@ -184,7 +184,7 @@ import * as fs2 from "node:fs";
184
184
  import * as path3 from "node:path";
185
185
 
186
186
  // src/coordination/sqlite-mailbox-schema.ts
187
- import { createRequire } from "node:module";
187
+ import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
188
188
 
189
189
  // src/utils/sqlite-warning.ts
190
190
  var SQLITE_EXPERIMENTAL_WARNING_RE = /sqlite is an experimental feature/i;
@@ -214,8 +214,7 @@ var DatabaseSyncCtor;
214
214
  function loadDatabaseSync() {
215
215
  if (DatabaseSyncCtor) return DatabaseSyncCtor;
216
216
  return withSqliteExperimentalWarningSuppressed(() => {
217
- const require2 = createRequire(import.meta.url);
218
- DatabaseSyncCtor = require2("node:sqlite").DatabaseSync;
217
+ DatabaseSyncCtor = loadRuntimeDatabaseSync();
219
218
  return DatabaseSyncCtor;
220
219
  });
221
220
  }
@@ -1,4 +1,4 @@
1
- import type { SessionRegistryEntry } from '../session-registry-types.js';
1
+ import type { SessionRegistryEntry } from './session-registry-types.js';
2
2
  import type { SessionSummary } from '../types/session.js';
3
3
  export declare const SESSION_CATALOG_PROTOCOL_VERSION = 1;
4
4
  export declare const SESSION_CATALOG_MAX_FRAME_CHARS: number;
@@ -1,4 +1,4 @@
1
- import type { AgentEntry, SessionRegistryEntry } from '../session-registry-types.js';
1
+ import type { AgentEntry, SessionRegistryEntry } from './session-registry-types.js';
2
2
  import type { ResumeReservation, SessionCatalogEvent } from './protocol.js';
3
3
  export interface SessionResumeClaim {
4
4
  reservation: ResumeReservation;
@@ -1,5 +1,5 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
- import type { SessionRegistryEntry } from '../session-registry-types.js';
2
+ import type { SessionRegistryEntry } from './session-registry-types.js';
3
3
  export declare const SCHEMA_VERSION = 1;
4
4
  export declare const MAX_LEASE_MS = 120000;
5
5
  export declare const MAX_RESERVATION_MS = 60000;
@@ -1,4 +1,4 @@
1
- import type { SessionRegistryEntry } from '../session-registry-types.js';
1
+ import type { SessionRegistryEntry } from './session-registry-types.js';
2
2
  import type { SessionSummary } from '../types/session.js';
3
3
  import type { CatalogSessionRecord, MaintenanceLease, ResumeReservation, SessionCatalogHealth, SessionCatalogListArgs, SessionLeaseCredential } from './protocol.js';
4
4
  export declare class SessionCatalogStore {
@@ -1,4 +1,4 @@
1
- import type { EventBus } from '../kernel/events.js';
1
+ import type { EventBus } from './event-bus-port.js';
2
2
  /**
3
3
  * L2-B: AnnotationsStore — sidecar storage for collaboration annotations
4
4
  * (Phase 2 of idea #13 from IDEAS.md).