Skip to content

refactor(dashboard): simplify goal prioritization + O(facts) goals-API dedup (#2695)#2730

Merged
rysweet merged 2 commits into
mainfrom
refactor/issue-2695-prioritize-simplify
Jul 6, 2026
Merged

refactor(dashboard): simplify goal prioritization + O(facts) goals-API dedup (#2695)#2730
rysweet merged 2 commits into
mainfrom
refactor/issue-2695-prioritize-simplify

Conversation

@rysweet

@rysweet rysweet commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up refactor/perf on the #2695 Goals-tab work (hierarchy + differentiated, priority-ordered goals, merged in #2714). This PR is additive and behavior-preserving — it simplifies and speeds up the already-shipped feature, it does not change what the operator sees.

Two changes:

  1. refactor(goal-curation): drop the never-populated user_facing prioritization signal.
    PrioritizationSignals.user_facing was a structured, caller-supplied HashSet that no production caller ever populated (the decompose path only ever built depends_on). Carrying a dead signal through the pure prioritize() scorer added an always-false branch and an empty-set plumb on every call site. Removing it shrinks PrioritizationSignals to the one signal that is actually fed (depends_on) — no observable change to output because the field was always empty. Prefer structured goal-graph data over dead configuration (G3).

  2. perf(dashboard): make the /api/goals fact-dedup O(facts) instead of O(facts×board).
    goals_at merges meeting/action/decision facts from cognitive memory into the backlog, skipping any fact whose id is already listed. The old check re-scanned the entire active + backlog list (with a serde-map lookup per element) for every fact — quadratic on an endpoint that is polled on every dashboard refresh. Build an O(1) HashSet id index once and insert newly-listed fact ids as we go, so later facts still dedup against earlier ones. Identical output, no quadratic scan.

Reconciled with the just-merged Goals tab (#2695/#2714): one coherent tab that shows hierarchy (structured parent_goal_id nesting), priority-ordered goals (ascending, stable id tiebreak, visible tiers + priority_explicit provenance), and real per-goal status (status_progress).

Rebased onto latest origin/main. Additive; no "Bridge" names; no new stray prints; no silent fallbacks; structured goal-graph data preferred over parsing (G3).

Step 13: Local Testing Results

Detected toolchains:

  • Rust / Cargo, single crate simard (edition 2024). Evidence: Cargo.toml ([package] name = "simard", default-run = "simard") + Cargo.lock at repo root; changed files are all .rs under src/goal_curation/ and src/operator_commands_dashboard/. Per the qa-team skill, a Cargo.toml-at-root repo uses cargo test as the outside-in harness (no gadugi framework required).

Chosen validation strategy:

  • The user-visible boundary is the dashboard /api/goals JSON contract (served by goals_at) plus the goal prioritization pass and decompose path. I exercised both the native hermetic consumer-boundary tests (which call goals_at(state_root) — the exact fn the HTTP handler calls — and assert on the JSON a browser receives) and the real compiled simard binary end-to-end: the goal CLI against a durable store and the live dashboard serve HTTP server via curl.

Scenario 1 — Simple: prioritization pass + real CLI shows differentiated, visible priorities

Command: cargo test --lib goal_curation::tests_prioritize and simard goal add <p> <desc> + simard goal set-priority + simard goal list (real binary, temp SIMARD_STATE_ROOT)
Result: PASS
Output:

test result: ok. 10 passed; 0 failed  (goal_curation::tests_prioritize)
  - prioritize_differentiates_a_flat_undifferentiated_set ... ok
  - prioritize_leaves_explicitly_set_priorities_intact ... ok
  - prioritize_bands_every_rescored_priority_into_1_through_5 ... ok
  - prioritize_ranks_a_blocker_above_a_non_blocker ... ok

$ simard goal list
ID                              PRIORITY  STATUS       DESCRIPTION
fix-auth-outage-blocking-users  p1        not-started  Fix auth outage blocking users
tidy-up-doc-comments-someday    p5        not-started  Tidy up doc comments someday
refactor-config-loader          p3        not-started  Refactor config loader

Priorities are visible and differentiated (p1/p3/p5), not a flat default.

Scenario 2 — Complex: dashboard /api/goals HTTP boundary — hierarchy field + priority-ordered + real status + provenance + dedup

Command: cargo test --lib operator_commands_dashboard::tests_goals_crud goal_curation::tests_decompose and a live simard dashboard serve + curl -H "Authorization: Bearer …" /api/goals
Result: PASS
Output (hermetic — the exact JSON contract a browser consumes):

test result: ok. 45 passed; 0 failed  (tests_goals_crud)
  - goals_active_ordered_by_priority_ascending_with_stable_id_tiebreak ... ok
  - goals_expose_parent_goal_id_for_hierarchy ... ok        # child → parent_goal_id="umbrella"; parent → null
  - goals_expose_priority_explicit_provenance ... ok
  - goals_exposes_distinct_status_progress_per_lifecycle_state ... ok
test result: ok. 21 passed; 0 failed  (tests_decompose)     # differentiates decomposed siblings via depends_on

Output (live HTTP server against a real durable store, 5 goals; note priority-ascending order, real status, and priority_explicit provenance):

$ curl -s -H "Authorization: Bearer …" http://localhost:8100/api/goals
p1  hotfix-startup-crash     parent=null  status=NotStarted  explicit=false
p2  add-rate-limiting        parent=null  status=NotStarted  explicit=false
p3  overhaul-auth-subsystem  parent=null  status=NotStarted  explicit=false   # p3 tie → stable id order
p3  refactor-config-loader   parent=null  status=NotStarted  explicit=false
p4  tidy-doc-comments        parent=null  status=NotStarted  explicit=true    # operator set-priority honored
active_count=5   (no goals dropped/duplicated — dedup path exercised)

Ordered by priority (highest first) with a stable id tiebreak; each goal's priority is visible; priority_explicit provenance and parent_goal_id hierarchy edge are exposed; status_progress carries real per-goal lifecycle status. The non-null parent_goal_id nesting case (goal decompose needs an LLM/recipe-runner, unavailable offline) is proven by the goals_expose_parent_goal_id_for_hierarchy hermetic test, which drives the same goals_at code with a properly-seeded store.

Regression + CI gates (local)

cargo fmt --all -- --check ......................... clean
cargo clippy --all-targets --all-features --locked . 0 warnings
cargo test --lib goal_curation .................... 289 passed; 0 failed
cargo test --lib operator_commands_dashboard ...... 534 passed; 0 failed
pre-push (fmt + release test subset + clippy -D warnings) ... all Passed

Related: #2695, #2714

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

rysweet and others added 2 commits July 6, 2026 21:04
…ion signal (#2695)

Step 9 (refactor & simplify) of the #2695 follow-up. The prioritization pass
carried a `PrioritizationSignals.user_facing` signal that NO caller ever
populated — `decompose.rs::sibling_dependency_signals` and every test build the
signals with only `depends_on` — so its `+1` scoring branch could never fire in
production or under test. It was speculative generality (also absent from the
task's A7 signal set), not live behavior.

Remove it entirely, behavior-preserving:
- prioritize.rs: drop the `user_facing` field, the `HashSet` import, the
  `signal_score` parameter, and its dead scoring branch (5 -> 4 params).
- decompose.rs / tests_prioritize.rs: `PrioritizationSignals` now has a single
  field, so the `..Default::default()` struct-update is needless
  (clippy::needless_update) — construct it directly as `{ depends_on }`.
- docs: drop the user-facing signal row and correct the `skip_serializing_if`
  attribute to the real `is_false` helper (the doc showed `std::ops::Not::not`,
  which the code does not use), so the reference matches reality.

No behavior change: `user_facing` was always empty, so every score and banded
priority is identical. fmt + clippy clean; full lib suite 7528 passed, 0 failed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s×board) (#2695)

Step 9b (performance optimization) of the #2695 follow-up. `goals_at` is
polled on every dashboard refresh. Its cognitive-memory backlog merge
deduped each candidate fact by re-scanning the entire active+backlog list
with a serde_json `.get("id")` map lookup per element — and since matched
facts are pushed onto `backlog`, the scanned list grew as the loop ran,
making the dedup effectively O(facts²) plus a JSON lookup per comparison.

Build a `HashSet<String>` id index once and check membership in O(1),
inserting each newly-listed fact so later facts still dedup against
earlier ones. Same result, no quadratic scan and no per-comparison serde
lookups. Bounded, hot-path win with no behavior change.

Verification: fmt + clippy (all-targets) clean; full lib suite 7528
passed, 0 failed — identical to the pre-change baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

📊 Coverage Summary

Generated by cargo llvm-cov --workspace --summary-only (nightly, excluding test files)

Module Lines Covered Coverage
Total 146810 122468 83.4%

Coverage data from CI run. Test files matching tests?/ are excluded from line counts.

@rysweet rysweet merged commit d59e08a into main Jul 6, 2026
17 checks passed
@rysweet rysweet deleted the refactor/issue-2695-prioritize-simplify branch July 6, 2026 21:50
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.

1 participant