Skip to content

feat: local case history and comparison - #35

Merged
EauDoon merged 2 commits into
mainfrom
feat/case-history-compare
Sep 6, 2026
Merged

feat: local case history and comparison#35
EauDoon merged 2 commits into
mainfrom
feat/case-history-compare

Conversation

@EauDoon

@EauDoon EauDoon commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Capability (approved milestone 3)

Revisit a previous case and understand what changed between two cases
without browsing output directories or diffing large JSON.

  • aas cases: bounded, summary-only history (outcome, policy reference,
    review verdict, evidence digest, component revisions).
  • aas compare <a> <b>: classifies identical / different / not comparable
    and lists the fields that differ.
  • GUI: Load history + Compare selected cases with loading, error, and
    not-comparable states; selection tokens prevent cross-pair results.

Semantics are explicit: no causation from differences, matching metadata is
not proof of matching evidence, and only the listed compared fields are
checked. Raw evidence never enters summary views; nothing mutates,
overwrites, or deletes a case, and cleanup stays the explicit
aas prune --keep.

Fixes found during review

  • . and .. satisfied the run-id pattern and resolved outside the runs
    directory (confirmed by a canary read over HTTP and CLI); run ids must now
    look like real run ids, validated identically on both entry points.
  • An unreadable stage artifact silently became null, so a corrupted
    prove artifact made two different cases read as identical. Now tracked and
    forced to not-comparable.
  • stages was not compared, so prove skipped vs prove failed read as
    identical; now compared, and the CLI prints both caveats (previously it
    printed only the causation one, contradicting the README).
  • Compare always exited 0 and echoed absolute paths; now exits 1 when not
    comparable and reports filesystem errors without paths.

Verification

  • npm test: 101/101, including traversal rejection (./../...),
    unreadable-artifact fail-closed, distinct-run stage differences, bounded
    history that skips unreadable cases, CLI exit codes, and endpoint
    validation with escaping checks.
  • npm run test:browser: 9/9 real browser journeys, including history load
    and UI comparison.
  • Integration proof covers cases/compare against pinned components.

Devin Review

An operator can revisit a previous case and see what changed between two
cases without browsing output directories or diffing large JSON.

- `aas cases` lists bounded summaries (outcome, policy reference, review
  verdict, evidence digest, component revisions) and `aas compare <a> <b>`
  classifies a pair as identical, different, or not comparable, listing the
  fields that differ. Summaries never include raw evidence, and comparison
  states that differences do not establish causation and that matching
  metadata does not prove matching evidence.
- GUI mirrors this with `Load history` and `Compare selected cases`,
  including loading, error, and not-comparable states, and per-selection
  tokens so switching cases mid-load cannot show another pair's result.
- Run ids are validated as real run ids: "." and ".." matched the old
  pattern and resolved outside the runs directory. Filesystem errors are
  reported without absolute paths. An unreadable stage artifact now fails
  closed as not-comparable instead of silently reading as absent, and
  `stages` is compared. Comparison exits nonzero when not comparable.
- History scans newest-first before reading manifests, so a large store
  cannot make the scan or the summaries unbounded.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread bin/aas.mjs
Comment on lines +1695 to +1698
function printComparison(result, asJson) {
if (asJson) {
process.stdout.write(`${JSON.stringify({ ok: true, ...result }, null, 2)}\n`);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 JSON comparisons hide unavailable cases

With --json, printComparison returns before assigning the failure status for a not-comparable pair. Automation therefore accepts missing, unreadable, or unsupported cases as successful comparisons.

Suggested change
function printComparison(result, asJson) {
if (asJson) {
process.stdout.write(`${JSON.stringify({ ok: true, ...result }, null, 2)}\n`);
return;
function printComparison(result, asJson) {
if (asJson) {
process.stdout.write(`${JSON.stringify({ ok: true, ...result }, null, 2)}\n`);
process.exitCode = result.classification === "not-comparable" ? 1 : 0;
return;
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread bin/aas-gui.mjs
const response=await fetch('/api/compare?a='+encodeURIComponent(leftCase.value)+'&b='+encodeURIComponent(rightCase.value));
body=await response.json();
} catch(error){ if(token!==compareToken) return; compareStatus.textContent='Comparison failed.'; compareButton.disabled=false; return; }
if(token!==compareToken) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 History refresh freezes comparison button

When history refresh starts during compareButton's request, the stale-response guard returns without re-enabling the button. The interface remains stuck on “Comparing...” and blocks every later comparison.

Prompt for agents
The GUI uses one compareToken for both history refreshes and comparison requests. Starting refreshHistory while a comparison fetch is pending invalidates the comparison token. Its stale-response return at bin/aas-gui.mjs leaves compareButton disabled and compareStatus at “Comparing...”. The reverse overlap can also leave historyList at “Loading history...”. Separate operation tokens or ensure each stale path restores only the UI state owned by that operation without allowing stale results to overwrite newer output.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@EauDoon
EauDoon merged commit 6c15110 into main Sep 6, 2026
10 checks passed
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