@ouro.bot/cli 0.1.0-alpha.275 → 0.1.0-alpha.276

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,12 @@
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.276",
6
+ "changes": [
7
+ "fix(nerves): root-cause the `start_end_pairing` audit flake on `daemon.server_start`, `daemon.update_checker_start`, and `daemon.apply_pending_updates_start`. Three fixes: (1) `applyPendingUpdates` in `update-hooks.ts` now wraps its body in try/finally so `_end` always fires, including on the early returns for `!fs.existsSync(bundlesRoot)` and `readdirSync` throws that previously orphaned the `_start`. (2) `daemon.start()` now wraps the ~380-line startup body in a try/catch that emits `daemon.server_error` with the error message and rethrows — the audit's pairing rule accepts `_end` OR `_error` as a valid closure for a `_start`, so startup throws no longer orphan `server_start`. The body was extracted to a private `startInner()` method to keep the try/catch small and readable. (3) `startUpdateChecker` callers in the update-checker test suite were already paired via an `afterEach(stopUpdateChecker)`; audited and confirmed no new unpaired callers. Also adds a vitest `afterEach` pairing guard in `src/__tests__/nerves/global-capture.ts` that fails loudly on any orphaned lifecycle `_start` in a test's per-test event stream — scoped to the three lifecycle events above so it catches regressions without false-positiving on legitimate narrow-slice operational events like `repertoire.task_scan_start`. New `src/__tests__/nerves/pairing-regression.test.ts` locks in the contract with 5 tests (nonexistent-dir, readdirSync-throws, happy-path, startUpdateChecker-pair, daemon-start-throw). Three consecutive `npm run test:coverage` runs confirm zero flakes on the target events."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.275",
6
12
  "changes": [
@@ -397,6 +397,26 @@ class OuroDaemon {
397
397
  message: "starting daemon server",
398
398
  meta: { socketPath: this.socketPath },
399
399
  });
400
+ try {
401
+ await this.startInner();
402
+ }
403
+ catch (err) {
404
+ // Emit a paired terminating event (`_error`) so the nerves audit's
405
+ // start_end_pairing rule is satisfied when startup throws mid-sequence
406
+ // and `stop()` (which emits `server_end`) is never called.
407
+ (0, runtime_1.emitNervesEvent)({
408
+ level: "error",
409
+ component: "daemon",
410
+ event: "daemon.server_error",
411
+ message: "daemon start failed",
412
+ meta: {
413
+ error: err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err),
414
+ },
415
+ });
416
+ throw err;
417
+ }
418
+ }
419
+ async startInner() {
400
420
  // Register update hooks and apply pending updates before starting agents
401
421
  (0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
402
422
  (0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
@@ -65,74 +65,78 @@ async function applyPendingUpdates(bundlesRoot, currentVersion) {
65
65
  message: "applying pending updates",
66
66
  meta: { bundlesRoot, currentVersion },
67
67
  });
68
- if (!fs.existsSync(bundlesRoot)) {
69
- return summary;
70
- }
71
- let entries;
72
68
  try {
73
- entries = fs.readdirSync(bundlesRoot, { withFileTypes: true });
74
- }
75
- catch {
76
- return summary;
77
- }
78
- for (const entry of entries) {
79
- if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
80
- continue;
81
- const agentRoot = path.join(bundlesRoot, entry.name);
82
- let previousVersion;
83
- const metaPath = path.join(agentRoot, "bundle-meta.json");
69
+ if (!fs.existsSync(bundlesRoot)) {
70
+ return summary;
71
+ }
72
+ let entries;
84
73
  try {
85
- if (fs.existsSync(metaPath)) {
86
- const raw = fs.readFileSync(metaPath, "utf-8");
87
- const meta = JSON.parse(raw);
88
- previousVersion = meta.runtimeVersion;
89
- if (previousVersion === currentVersion) {
90
- continue;
91
- }
92
- // Skip downgrades — only update forward
93
- if (semver.valid(previousVersion) && semver.valid(currentVersion) && semver.gte(previousVersion, currentVersion)) {
94
- (0, runtime_1.emitNervesEvent)({
95
- component: "daemon",
96
- event: "daemon.update_hook_skip_downgrade",
97
- message: "skipping downgrade",
98
- meta: { agentRoot, previousVersion, currentVersion },
99
- });
100
- continue;
101
- }
102
- }
74
+ entries = fs.readdirSync(bundlesRoot, { withFileTypes: true });
103
75
  }
104
76
  catch {
105
- // Malformed or unreadable bundle-meta.json -- treat as needing update
106
- previousVersion = undefined;
77
+ return summary;
107
78
  }
108
- const ctx = { agentRoot, currentVersion, previousVersion };
109
- for (const hook of _hooks) {
79
+ for (const entry of entries) {
80
+ if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
81
+ continue;
82
+ const agentRoot = path.join(bundlesRoot, entry.name);
83
+ let previousVersion;
84
+ const metaPath = path.join(agentRoot, "bundle-meta.json");
110
85
  try {
111
- await hook(ctx);
86
+ if (fs.existsSync(metaPath)) {
87
+ const raw = fs.readFileSync(metaPath, "utf-8");
88
+ const meta = JSON.parse(raw);
89
+ previousVersion = meta.runtimeVersion;
90
+ if (previousVersion === currentVersion) {
91
+ continue;
92
+ }
93
+ // Skip downgrades — only update forward
94
+ if (semver.valid(previousVersion) && semver.valid(currentVersion) && semver.gte(previousVersion, currentVersion)) {
95
+ (0, runtime_1.emitNervesEvent)({
96
+ component: "daemon",
97
+ event: "daemon.update_hook_skip_downgrade",
98
+ message: "skipping downgrade",
99
+ meta: { agentRoot, previousVersion, currentVersion },
100
+ });
101
+ continue;
102
+ }
103
+ }
104
+ }
105
+ catch {
106
+ // Malformed or unreadable bundle-meta.json -- treat as needing update
107
+ previousVersion = undefined;
112
108
  }
113
- catch (err) {
114
- (0, runtime_1.emitNervesEvent)({
115
- component: "daemon",
116
- event: "daemon.update_hook_error",
117
- message: "update hook threw",
118
- meta: {
119
- agentRoot,
120
- error: err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err),
121
- },
122
- });
109
+ const ctx = { agentRoot, currentVersion, previousVersion };
110
+ for (const hook of _hooks) {
111
+ try {
112
+ await hook(ctx);
113
+ }
114
+ catch (err) {
115
+ (0, runtime_1.emitNervesEvent)({
116
+ component: "daemon",
117
+ event: "daemon.update_hook_error",
118
+ message: "update hook threw",
119
+ meta: {
120
+ agentRoot,
121
+ error: err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err),
122
+ },
123
+ });
124
+ }
123
125
  }
126
+ summary.updated.push({
127
+ agent: entry.name.replace(/\.ouro$/, ""),
128
+ from: previousVersion,
129
+ to: currentVersion,
130
+ });
124
131
  }
125
- summary.updated.push({
126
- agent: entry.name.replace(/\.ouro$/, ""),
127
- from: previousVersion,
128
- to: currentVersion,
132
+ return summary;
133
+ }
134
+ finally {
135
+ (0, runtime_1.emitNervesEvent)({
136
+ component: "daemon",
137
+ event: "daemon.apply_pending_updates_end",
138
+ message: "pending updates applied",
139
+ meta: { bundlesRoot },
129
140
  });
130
141
  }
131
- (0, runtime_1.emitNervesEvent)({
132
- component: "daemon",
133
- event: "daemon.apply_pending_updates_end",
134
- message: "pending updates applied",
135
- meta: { bundlesRoot },
136
- });
137
- return summary;
138
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.275",
3
+ "version": "0.1.0-alpha.276",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",