@ouro.bot/cli 0.1.0-alpha.580 → 0.1.0-alpha.581

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/changelog.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.581",
6
+ "changes": [
7
+ "Trip ledger storage now uses git-controlled bundle-root `trips/` as canonical local storage, with copy-only import from legacy ignored `state/trips/` and divergence warnings instead of silent merges.",
8
+ "`ouro doctor` now checks durable trip ledgers and reports legacy-only or divergent `state/trips/` data clearly so bundle sync and backup gaps surface before travel facts are lost."
9
+ ]
10
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.580",
6
13
  "changes": [
@@ -373,15 +373,27 @@ function checkTrips(deps) {
373
373
  return { name: "Trips", checks };
374
374
  }
375
375
  for (const agentDir of agents) {
376
- const tripsRootPath = `${deps.bundlesRoot}/${agentDir}/state/trips`;
377
- if (!deps.existsSync(tripsRootPath)) {
378
- // Trip ledger is optional; absence is fine. Pass with a hint.
379
- checks.push({ label: `${agentDir} trip ledger`, status: "pass", detail: "no ledger directory (no trips ensured yet)" });
376
+ const durableTripsRoot = `${deps.bundlesRoot}/${agentDir}/trips`;
377
+ const legacyTripsRoot = `${deps.bundlesRoot}/${agentDir}/state/trips`;
378
+ const hasDurableTrips = deps.existsSync(durableTripsRoot);
379
+ const hasLegacyTrips = deps.existsSync(legacyTripsRoot);
380
+ if (!hasDurableTrips) {
381
+ if (hasLegacyTrips) {
382
+ checks.push({
383
+ label: `${agentDir} trip ledger`,
384
+ status: "warn",
385
+ detail: "legacy state/trips exists but durable trips/ missing — run any trip tool to copy legacy storage into trips/",
386
+ });
387
+ }
388
+ else {
389
+ // Trip ledger is optional; absence is fine. Pass with a hint.
390
+ checks.push({ label: `${agentDir} trip ledger`, status: "pass", detail: "no ledger directory (no trips ensured yet)" });
391
+ }
380
392
  continue;
381
393
  }
382
- const ledgerPath = `${tripsRootPath}/ledger.json`;
394
+ const ledgerPath = `${durableTripsRoot}/ledger.json`;
383
395
  if (!deps.existsSync(ledgerPath)) {
384
- checks.push({ label: `${agentDir} trip ledger`, status: "warn", detail: "state/trips/ exists but ledger.json missing — run trip_ensure_ledger" });
396
+ checks.push({ label: `${agentDir} trip ledger`, status: "warn", detail: "trips/ exists but ledger.json missing — run trip_ensure_ledger" });
385
397
  continue;
386
398
  }
387
399
  let raw;
@@ -420,7 +432,7 @@ function checkTrips(deps) {
420
432
  continue;
421
433
  }
422
434
  let recordCount = 0;
423
- const recordsDir = `${tripsRootPath}/records`;
435
+ const recordsDir = `${durableTripsRoot}/records`;
424
436
  /* v8 ignore start -- defensive: records dir presence and readdir error are filesystem-state branches not all exercised by tests; pluralization branch likewise depends on record count fixtures @preserve */
425
437
  if (deps.existsSync(recordsDir)) {
426
438
  try {
@@ -430,15 +442,50 @@ function checkTrips(deps) {
430
442
  // ignore — the warn detail will still report 0 records
431
443
  }
432
444
  }
445
+ const legacyDiverges = hasLegacyTrips && tripStoresDiffer(deps, durableTripsRoot, legacyTripsRoot);
433
446
  checks.push({
434
447
  label: `${agentDir} trip ledger`,
435
- status: "pass",
436
- detail: `${ledgerId} (${recordCount} record${recordCount === 1 ? "" : "s"})`,
448
+ status: legacyDiverges ? "warn" : "pass",
449
+ detail: `${ledgerId} (${recordCount} record${recordCount === 1 ? "" : "s"})${legacyDiverges ? "; legacy state/trips differs from durable trips/ — durable trips/ is authoritative" : ""}`,
437
450
  });
438
451
  /* v8 ignore stop */
439
452
  }
440
453
  return { name: "Trips", checks };
441
454
  }
455
+ function listTripStoreFiles(deps, root) {
456
+ const files = [];
457
+ if (deps.existsSync(`${root}/ledger.json`)) {
458
+ files.push("ledger.json");
459
+ }
460
+ const recordsDir = `${root}/records`;
461
+ if (deps.existsSync(recordsDir)) {
462
+ for (const name of deps.readdirSync(recordsDir)) {
463
+ if (name.endsWith(".json")) {
464
+ files.push(`records/${name}`);
465
+ }
466
+ }
467
+ }
468
+ return files.sort();
469
+ }
470
+ function tripStoresDiffer(deps, durableRoot, legacyRoot) {
471
+ const relativePaths = new Set([
472
+ ...listTripStoreFiles(deps, durableRoot),
473
+ ...listTripStoreFiles(deps, legacyRoot),
474
+ ]);
475
+ for (const relativePath of relativePaths) {
476
+ const durablePath = `${durableRoot}/${relativePath}`;
477
+ const legacyPath = `${legacyRoot}/${relativePath}`;
478
+ const durableExists = deps.existsSync(durablePath);
479
+ const legacyExists = deps.existsSync(legacyPath);
480
+ if (durableExists !== legacyExists) {
481
+ return true;
482
+ }
483
+ if (durableExists && deps.readFileSync(durablePath) !== deps.readFileSync(legacyPath)) {
484
+ return true;
485
+ }
486
+ }
487
+ return false;
488
+ }
442
489
  function checkMailroom(deps) {
443
490
  const checks = [];
444
491
  const agents = discoverAgents(deps);
@@ -45,6 +45,9 @@ const identity_1 = require("../heart/identity");
45
45
  const runtime_1 = require("../nerves/runtime");
46
46
  const core_1 = require("./core");
47
47
  function tripsRoot(agentName) {
48
+ return path.join((0, identity_1.getAgentRoot)(agentName), "trips");
49
+ }
50
+ function legacyTripsRoot(agentName) {
48
51
  return path.join((0, identity_1.getAgentRoot)(agentName), "state", "trips");
49
52
  }
50
53
  function ledgerPath(agentName) {
@@ -56,6 +59,118 @@ function recordsDir(agentName) {
56
59
  function recordPath(agentName, tripId) {
57
60
  return path.join(recordsDir(agentName), `${tripId}.json`);
58
61
  }
62
+ function ledgerPathFor(root) {
63
+ return path.join(root, "ledger.json");
64
+ }
65
+ function recordsDirFor(root) {
66
+ return path.join(root, "records");
67
+ }
68
+ function removeMigrationTempRoot(tmpRoot) {
69
+ const records = recordsDirFor(tmpRoot);
70
+ if (fs.existsSync(records)) {
71
+ for (const entry of fs.readdirSync(records)) {
72
+ fs.unlinkSync(path.join(records, entry));
73
+ }
74
+ fs.rmdirSync(records);
75
+ }
76
+ const ledger = ledgerPathFor(tmpRoot);
77
+ if (fs.existsSync(ledger)) {
78
+ fs.unlinkSync(ledger);
79
+ }
80
+ if (fs.existsSync(tmpRoot)) {
81
+ fs.rmdirSync(tmpRoot);
82
+ }
83
+ }
84
+ function copyLegacyTripsIfNeeded(agentName) {
85
+ const durableRoot = tripsRoot(agentName);
86
+ const legacyRoot = legacyTripsRoot(agentName);
87
+ if (fs.existsSync(durableRoot) || !fs.existsSync(ledgerPathFor(legacyRoot))) {
88
+ return;
89
+ }
90
+ const tmpRoot = `${durableRoot}.tmp-${process.pid}-${Date.now()}`;
91
+ removeMigrationTempRoot(tmpRoot);
92
+ try {
93
+ fs.mkdirSync(recordsDirFor(tmpRoot), { recursive: true });
94
+ fs.copyFileSync(ledgerPathFor(legacyRoot), ledgerPathFor(tmpRoot));
95
+ const legacyRecordsDir = recordsDirFor(legacyRoot);
96
+ if (fs.existsSync(legacyRecordsDir)) {
97
+ for (const entry of fs.readdirSync(legacyRecordsDir)) {
98
+ if (entry.endsWith(".json")) {
99
+ fs.copyFileSync(path.join(legacyRecordsDir, entry), path.join(recordsDirFor(tmpRoot), entry));
100
+ }
101
+ }
102
+ }
103
+ fs.renameSync(tmpRoot, durableRoot);
104
+ }
105
+ catch (error) {
106
+ removeMigrationTempRoot(tmpRoot);
107
+ throw error;
108
+ }
109
+ }
110
+ function collectStoreFiles(root) {
111
+ const files = [];
112
+ if (fs.existsSync(ledgerPathFor(root))) {
113
+ files.push("ledger.json");
114
+ }
115
+ const records = recordsDirFor(root);
116
+ if (fs.existsSync(records)) {
117
+ for (const entry of fs.readdirSync(records)) {
118
+ if (entry.endsWith(".json")) {
119
+ files.push(`records/${entry}`);
120
+ }
121
+ }
122
+ }
123
+ return files.sort();
124
+ }
125
+ function fileAt(root, relativePath) {
126
+ return path.join(root, ...relativePath.split("/"));
127
+ }
128
+ function findLegacyDifferences(durableRoot, legacyRoot) {
129
+ const paths = new Set([...collectStoreFiles(durableRoot), ...collectStoreFiles(legacyRoot)]);
130
+ const differences = [];
131
+ for (const relativePath of [...paths].sort()) {
132
+ const durablePath = fileAt(durableRoot, relativePath);
133
+ const legacyPath = fileAt(legacyRoot, relativePath);
134
+ const durableExists = fs.existsSync(durablePath);
135
+ const legacyExists = fs.existsSync(legacyPath);
136
+ if (durableExists !== legacyExists) {
137
+ differences.push(relativePath);
138
+ continue;
139
+ }
140
+ if (durableExists && !fs.readFileSync(durablePath).equals(fs.readFileSync(legacyPath))) {
141
+ differences.push(relativePath);
142
+ }
143
+ }
144
+ return differences;
145
+ }
146
+ function reportLegacyDivergence(agentName) {
147
+ const durableRoot = tripsRoot(agentName);
148
+ const legacyRoot = legacyTripsRoot(agentName);
149
+ if (!fs.existsSync(durableRoot) || !fs.existsSync(legacyRoot)) {
150
+ return;
151
+ }
152
+ const differences = findLegacyDifferences(durableRoot, legacyRoot);
153
+ if (differences.length === 0) {
154
+ return;
155
+ }
156
+ (0, runtime_1.emitNervesEvent)({
157
+ level: "warn",
158
+ component: "trips",
159
+ event: "trips.legacy_diverged",
160
+ message: "legacy trip store differs from durable trip store",
161
+ meta: {
162
+ agentId: agentName,
163
+ durablePath: "trips",
164
+ legacyPath: "state/trips",
165
+ differenceCount: differences.length,
166
+ differences: differences.slice(0, 10),
167
+ },
168
+ });
169
+ }
170
+ function prepareTripStorage(agentName) {
171
+ copyLegacyTripsIfNeeded(agentName);
172
+ reportLegacyDivergence(agentName);
173
+ }
59
174
  function readJsonFile(filePath) {
60
175
  if (!fs.existsSync(filePath))
61
176
  return null;
@@ -79,6 +194,7 @@ exports.TripNotFoundError = TripNotFoundError;
79
194
  * generate a fresh keypair and persist both halves.
80
195
  */
81
196
  function ensureAgentTripLedger(input) {
197
+ prepareTripStorage(input.agentName);
82
198
  const existing = readJsonFile(ledgerPath(input.agentName));
83
199
  if (existing) {
84
200
  return { ledger: existing.ledger, added: false };
@@ -103,6 +219,7 @@ function ensureAgentTripLedger(input) {
103
219
  return { ledger: created.ledger, added: true };
104
220
  }
105
221
  function readLedgerOrThrow(agentName) {
222
+ prepareTripStorage(agentName);
106
223
  const stored = readJsonFile(ledgerPath(agentName));
107
224
  if (!stored) {
108
225
  throw new Error(`no trip ledger for agent ${agentName} — call ensureAgentTripLedger first`);
@@ -129,6 +246,7 @@ function upsertTripRecord(agentName, trip) {
129
246
  });
130
247
  }
131
248
  function readTripRecord(agentName, tripId) {
249
+ prepareTripStorage(agentName);
132
250
  const payload = readJsonFile(recordPath(agentName, tripId));
133
251
  if (!payload)
134
252
  throw new TripNotFoundError({ agentName, tripId });
@@ -136,6 +254,7 @@ function readTripRecord(agentName, tripId) {
136
254
  return (0, core_1.decryptTripRecord)(payload, stored.privateKeyPem);
137
255
  }
138
256
  function listTripIds(agentName) {
257
+ prepareTripStorage(agentName);
139
258
  const dir = recordsDir(agentName);
140
259
  if (!fs.existsSync(dir))
141
260
  return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.580",
3
+ "version": "0.1.0-alpha.581",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",