hunt commited on
Commit
e24f164
·
1 Parent(s): 7eaf400

Fix duckdb list-column binding and neo4j retry test; 164/164 tests passing

Browse files
HANDOFF.md CHANGED
@@ -350,3 +350,27 @@ These are minimal compiling stubs, not final implementations. Once `cargo build
350
  3. Begin replacing the 14 stub files' placeholder logic with real implementations per `wo-conversational-intelligence` and `wo-model-forge` design docs — starting with the two non-negotiable invariant modules: `autonomy_calibration.rs` and `partnership_tracker.rs`.
351
  4. User's manual step still outstanding (needs interactive sudo, cannot be done via agent): CUPS purge commands provided earlier in session.
352
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  3. Begin replacing the 14 stub files' placeholder logic with real implementations per `wo-conversational-intelligence` and `wo-model-forge` design docs — starting with the two non-negotiable invariant modules: `autonomy_calibration.rs` and `partnership_tracker.rs`.
351
  4. User's manual step still outstanding (needs interactive sudo, cannot be done via agent): CUPS purge commands provided earlier in session.
352
 
353
+
354
+ ---
355
+
356
+ ## Session Status Update — Sep 4, 2026, 10:15 CDT — ALL TESTS PASSING
357
+
358
+ ### Verified via real test run: 164 passed, 0 failed, 2 ignored (`cargo test -p miranda-nodes`)
359
+
360
+ Two real bugs found and fixed after the build cleared:
361
+
362
+ 1. **`duckdb_writer.rs`** — `duckdb-rs` 1.10505.0 does not support binding native `LIST` parameters (confirmed by tracing the panic to `ToSqlConversionFailure("binding List parameters is not yet supported")` in the driver source). Fixed by storing the `entities` and `mood_contexts` columns as JSON-encoded `VARCHAR` instead of DuckDB `VARCHAR[]`, serialized/deserialized with `serde_json`. Schema updated in both the test's inline schema and the real `scripts/duckdb-init.sh`.
363
+ 2. **`neo4j_writer.rs`** — the `exhausts_retries_against_unreachable_host` test asserted `connect().await.is_err()`, but `neo4rs::Graph::new` pools connections lazily and does not fail at connect time against a closed port. Rewrote the test to actually exercise `write_conversation`'s retry loop (3 retries, 5ms backoff) and assert `Neo4jError::RetriesExhausted { attempts: 3, .. }` — this is the real behavior the module needs to guarantee per design.md's error-handling contract (JSONL log is source of truth if graph writes are exhausted).
364
+
365
+ Both fixes verified with real `cargo test` output, not code-review confidence.
366
+
367
+ ### Verified test coverage includes
368
+ - Memory: mood classifier (latency + accuracy), neo4j writer (schema shape + real retry exhaustion), duckdb writer (real temp-DB round trip)
369
+ - Conversation: solver (SIMD blendshape/viseme DSP — 20+ tests covering additivity, attack/release timing, band selectivity, determinism, autonomic-channel isolation), viseme mapping, 60Hz dispatcher cadence
370
+ - Real-time verification: `tests/rt_verification.rs` — 60fps acceptance criteria against a real shared-memory bus, velocity clamping on frames read back from shm
371
+
372
+ ### Next Steps
373
+ 1. `cargo build --workspace` to confirm no other crate broke.
374
+ 2. Begin real implementations for the 14 stub files (currently placeholders), starting with `autonomy_calibration.rs` and `partnership_tracker.rs` (the two non-negotiable invariant modules).
375
+ 3. User's outstanding manual step: CUPS purge (needs interactive sudo, commands provided earlier).
376
+
miranda-nodes/src/memory/duckdb_writer.rs CHANGED
@@ -12,7 +12,7 @@
12
  //! rather than held open across awaits here.
13
 
14
  use chrono::{DateTime, Utc};
15
- use duckdb::{params, Connection, types::Value};
16
 
17
  use super::entity_extractor::Entity;
18
  use super::mood_classifier::MoodState;
@@ -56,7 +56,12 @@ impl DuckDbWriter {
56
  let conn = self.connect()?;
57
 
58
  let entity_names: Vec<String> = entities.iter().map(|e| e.entity_name.clone()).collect();
59
- let entity_names_value = Value::List(entity_names.into_iter().map(Value::Text).collect());
 
 
 
 
 
60
 
61
  conn.execute(
62
  "INSERT INTO events (event_id, timestamp, event_type, user_message, \
@@ -69,7 +74,7 @@ impl DuckDbWriter {
69
  event_type,
70
  user_message,
71
  miranda_response,
72
- entity_names_value,
73
  mood_state.as_str(),
74
  mood_state.color_hex(),
75
  Option::<String>::None,
@@ -89,7 +94,8 @@ impl DuckDbWriter {
89
  entity.entity_type.as_str(),
90
  timestamp.to_rfc3339(),
91
  timestamp.to_rfc3339(),
92
- Value::List(vec![Value::Text(mood_state.as_str().to_string())]),
 
93
  ],
94
  )?;
95
  }
@@ -139,7 +145,7 @@ mod tests {
139
  event_type VARCHAR NOT NULL,
140
  user_message VARCHAR NOT NULL,
141
  miranda_response VARCHAR NOT NULL,
142
- entities VARCHAR[],
143
  mood_state VARCHAR NOT NULL,
144
  mood_rgb VARCHAR,
145
  mood_hsl VARCHAR
@@ -150,7 +156,7 @@ mod tests {
150
  first_mention TIMESTAMP NOT NULL,
151
  last_mention TIMESTAMP NOT NULL,
152
  mention_count INTEGER NOT NULL DEFAULT 1,
153
- mood_contexts VARCHAR[],
154
  PRIMARY KEY (entity_name, entity_type)
155
  );",
156
  )
 
12
  //! rather than held open across awaits here.
13
 
14
  use chrono::{DateTime, Utc};
15
+ use duckdb::{params, Connection};
16
 
17
  use super::entity_extractor::Entity;
18
  use super::mood_classifier::MoodState;
 
56
  let conn = self.connect()?;
57
 
58
  let entity_names: Vec<String> = entities.iter().map(|e| e.entity_name.clone()).collect();
59
+ // duckdb-rs does not yet support binding native LIST parameters
60
+ // (`ToSqlConversionFailure("binding List parameters is not yet
61
+ // supported")`), so list columns are stored as JSON-encoded
62
+ // VARCHAR instead of DuckDB VARCHAR[] and decoded on read.
63
+ let entity_names_json =
64
+ serde_json::to_string(&entity_names).unwrap_or_else(|_| "[]".to_string());
65
 
66
  conn.execute(
67
  "INSERT INTO events (event_id, timestamp, event_type, user_message, \
 
74
  event_type,
75
  user_message,
76
  miranda_response,
77
+ entity_names_json,
78
  mood_state.as_str(),
79
  mood_state.color_hex(),
80
  Option::<String>::None,
 
94
  entity.entity_type.as_str(),
95
  timestamp.to_rfc3339(),
96
  timestamp.to_rfc3339(),
97
+ serde_json::to_string(&vec![mood_state.as_str().to_string()])
98
+ .unwrap_or_else(|_| "[]".to_string()),
99
  ],
100
  )?;
101
  }
 
145
  event_type VARCHAR NOT NULL,
146
  user_message VARCHAR NOT NULL,
147
  miranda_response VARCHAR NOT NULL,
148
+ entities VARCHAR,
149
  mood_state VARCHAR NOT NULL,
150
  mood_rgb VARCHAR,
151
  mood_hsl VARCHAR
 
156
  first_mention TIMESTAMP NOT NULL,
157
  last_mention TIMESTAMP NOT NULL,
158
  mention_count INTEGER NOT NULL DEFAULT 1,
159
+ mood_contexts VARCHAR,
160
  PRIMARY KEY (entity_name, entity_type)
161
  );",
162
  )
miranda-nodes/src/memory/neo4j_writer.rs CHANGED
@@ -264,15 +264,34 @@ mod tests {
264
  }
265
 
266
  /// Real integration test exercising retry-with-backoff against a
267
- /// deliberately unreachable Bolt address, verifying the writer
268
- /// exhausts retries and returns an error instead of hanging or
 
 
 
269
  /// silently succeeding.
270
  #[tokio::test]
271
  async fn exhausts_retries_against_unreachable_host() {
272
- let connect = Neo4jWriter::connect("bolt://127.0.0.1:1", "neo4j", "mirandamemory").await;
273
- // neo4rs may fail fast at connect time against a closed port,
274
- // which itself proves the error path works without needing the
275
- // write-level retry loop.
276
- assert!(connect.is_err());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  }
278
  }
 
264
  }
265
 
266
  /// Real integration test exercising retry-with-backoff against a
267
+ /// deliberately unreachable Bolt address. `neo4rs::Graph::new` pools
268
+ /// connections lazily, so `connect()` itself succeeds even against a
269
+ /// closed port; the actual failure only surfaces once a query is
270
+ /// attempted. This verifies `write_conversation`'s retry loop runs to
271
+ /// `max_retries` and returns `RetriesExhausted` instead of hanging or
272
  /// silently succeeding.
273
  #[tokio::test]
274
  async fn exhausts_retries_against_unreachable_host() {
275
+ let writer = Neo4jWriter::connect("bolt://127.0.0.1:1", "neo4j", "mirandamemory")
276
+ .await
277
+ .expect("Graph::new pools lazily and should not fail before a query is run")
278
+ .with_retry_policy(3, Duration::from_millis(5));
279
+
280
+ let entities: Vec<Entity> = Vec::new();
281
+ let record = ConversationRecord {
282
+ conversation_id: uuid::Uuid::new_v4(),
283
+ timestamp: Utc::now(),
284
+ user_message: "unreachable host test",
285
+ miranda_response: "n/a",
286
+ mood_state: MoodState::Curiosity,
287
+ intimacy_level: None,
288
+ entities: &entities,
289
+ };
290
+
291
+ let result = writer.write_conversation(&record).await;
292
+ match result {
293
+ Err(Neo4jError::RetriesExhausted { attempts, .. }) => assert_eq!(attempts, 3),
294
+ other => panic!("expected RetriesExhausted after 3 attempts, got {other:?}"),
295
+ }
296
  }
297
  }
scripts/duckdb-init.sh CHANGED
@@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS events (
27
  event_type VARCHAR NOT NULL,
28
  user_message VARCHAR NOT NULL,
29
  miranda_response VARCHAR NOT NULL,
30
- entities VARCHAR[],
31
  mood_state VARCHAR NOT NULL,
32
  mood_rgb VARCHAR,
33
  mood_hsl VARCHAR
@@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS entities (
39
  first_mention TIMESTAMP NOT NULL,
40
  last_mention TIMESTAMP NOT NULL,
41
  mention_count INTEGER NOT NULL DEFAULT 1,
42
- mood_contexts VARCHAR[],
43
  PRIMARY KEY (entity_name, entity_type)
44
  );
45
 
 
27
  event_type VARCHAR NOT NULL,
28
  user_message VARCHAR NOT NULL,
29
  miranda_response VARCHAR NOT NULL,
30
+ entities VARCHAR,
31
  mood_state VARCHAR NOT NULL,
32
  mood_rgb VARCHAR,
33
  mood_hsl VARCHAR
 
39
  first_mention TIMESTAMP NOT NULL,
40
  last_mention TIMESTAMP NOT NULL,
41
  mention_count INTEGER NOT NULL DEFAULT 1,
42
+ mood_contexts VARCHAR,
43
  PRIMARY KEY (entity_name, entity_type)
44
  );
45