Skip to content

fix(dashboard): tolerate string decisions, derive active projects, log passive activations#23

Open
juanparisma wants to merge 1 commit into
Luispitik:mainfrom
juanparisma:fix/dashboard-decisions-and-passive-telemetry
Open

fix(dashboard): tolerate string decisions, derive active projects, log passive activations#23
juanparisma wants to merge 1 commit into
Luispitik:mainfrom
juanparisma:fix/dashboard-decisions-and-passive-telemetry

Conversation

@juanparisma

Copy link
Copy Markdown

Three fixes found while auditing a live Sinapsis install.

1. collect_decisions crashes on string-form decisions

_operator-state.json can store strategicDecisions as a list of plain strings. The current code calls .get('date') on each item, raising AttributeError: 'str' object has no attribute 'get' and aborting the whole dashboard generation. Now tolerates both shapes (objects {id,date,decision} and plain strings).

2. collect_projects always reports 0 active projects

active was computed as [p for p in projects if p.get('active')], but the registry never sets an active flag, so the dashboard always showed 0 projects even with thousands of observations. Now derives 'active' from observation recency (≤14 days) and resolves friendly project names from the registry / last observation instead of showing raw dir hashes.

3. Passive rules fire silently — no telemetry

_passive-activator.sh injected matched rules but never wrote a log, so collect_passive_log() always returned empty and the dashboard reported 0 passive activations. Added a one-line-per-fired-rule append to _passive.log (TIMESTAMP | rule_id | tool), matching the format collect_passive_log() already parses. Mirrors how the instinct activator logs to _instinct.log.

All three verified against a real install (Windows, py 3.12 / node 24): dashboard went from 'Projects: 0' to the real count, and _passive.log now populates on tool use. py_compile and a functional smoke test pass for both files.

…g passive activations

- collect_decisions: handle strategicDecisions as plain strings (not just
  objects). Previously crashed with AttributeError: 'str' has no 'get' when
  operator-state stored decisions as strings.
- collect_projects: 'active' count was always 0 because the registry never
  sets an 'active' flag. Now derives activity from observation recency
  (<=14d) and resolves friendly project names from the registry/observations
  instead of showing raw hashes.
- _passive-activator.sh: write fired rule activations to _passive.log
  ('TS | rule_id | tool') so the dashboard and /passive-status can report
  passive-rule telemetry. Previously passive rules fired silently with no
  log, so activation counts were always 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Luispitik Luispitik left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top-quality PR — I reproduced all three bugs on main before reading your fixes, and they match exactly:

  1. A single string in strategicDecisions crashes collect_decisions() with AttributeError and kills the whole dashboard (main() re-raises).
  2. Nothing ever writes an active flag (_session-learner.sh writes {id,name,root,remote,created,last_seen}), so the dashboard always showed 0 active projects. Your mtime<=14d derivation + friendly-name resolution check out: the registry id matches the homunculus sha256[:12] hash, and every observation line carries project_name.
  3. _passive-activator.sh wrote no telemetry, so collect_passive_log() always returned empty. Your ISO | rule_id | tool format is parsed correctly by the dashboard, and I verified functionally on Windows Git Bash that the activator still injects and never corrupts the rules JSON.

The patch applies clean on v4.6.1 and the full suite is green with it (I ran all 11 suites: 147/147).

Four asks before merge:

  1. Guard the log-path derivation so a future rename can't corrupt the rules file — if replace() doesn't match, logPath IS _passive-rules.json and the append would corrupt it:
    const logPath = process.argv[1].replace(/_passive-rules\.json$/, "_passive.log");
    if (logPath !== process.argv[1]) {
      const ts = new Date().toISOString();
      fs.appendFileSync(logPath, topIds.map(id => ts + " | " + id + " | " + tool + "\n").join(""));
    }
  2. Empty string instead of None for the synthetic decision id — the template renders ${d.id} directly (lines 408/424), so string-decisions currently show a literal null in the timeline: decs.append({'id': '', 'date': '', 'decision': d}).
  3. Regression tests in tests/test-dashboard.sh (repo convention — every fix ships with one): T13 mixed strings+objects in strategicDecisions → dashboard generates without crash; T14 one recent observations file + one with old mtime (touch -d '30 days ago') → active==1 and friendly name resolved from registry/last observation, not the hash; T15 invoke _passive-activator.sh with a sandboxed HOME → _passive.log exists with the ts | rule_id | tool shape and _passive-rules.json is still valid JSON.
  4. Trim _passive.log in the dream cycle (e.g. keep the last 5000 lines) — none of our logs rotate, and this one grows with every matching tool use.

Nit (optional): in collect_projects(), capture the last line during the same counting loop instead of re-reading the file when the name isn't in the registry.

With those in, this merges. Thanks — the repro quality made the review fast.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants