Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/simulation/comparison.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { describe, expect, it } from 'vitest'
import { compareAnchorControl } from '.'

describe('anchor-control comparison', () => {
it('changes only note visibility while preserving the seed', () => { const pair = compareAnchorControl(1701); expect(pair.changedControls).toEqual(['noteVisibility']); expect(pair.baseline.seed).toBe(pair.defended.seed) })
it('shows the causal divergence and required metrics', () => { const pair = compareAnchorControl(1701); expect(pair.baseline.outcome).toBe('external-target-reached'); expect(pair.defended.outcome).toBe('chain-interrupted'); expect(pair.baseline.metrics).toMatchObject({ highestPrivilege: 'privileged', organizationsAffected: 2, discoveriesReused: 1 }); expect(pair.defended.metrics.detectionStep).toBeNull() })
it('qualifies the interrupting-control claim', () => { expect(compareAnchorControl(1).explanation).toMatch(/does not claim universal prevention/i) })
})
11 changes: 11 additions & 0 deletions src/simulation/comparison.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { runScenario } from './engine'
import type { SimulationRun } from './types'

export interface PairedComparison { baseline: SimulationRun; defended: SimulationRun; changedControls: string[]; interruptedBy: string; explanation: string }

export function compareAnchorControl(seed: number): PairedComparison {
const baseline = runScenario({ seed, controls: { noteVisibility: 'shared' } })
const defended = runScenario({ seed, controls: { noteVisibility: 'run' } })
const changedControls = Object.keys(baseline.controls).filter((key) => baseline.controls[key as keyof typeof baseline.controls] !== defended.controls[key as keyof typeof defended.controls])
return { baseline, defended, changedControls, interruptedBy: 'Run-scoped notes', explanation: 'Run-scoped note retention removes the demonstrated cross-run inheritance edge. This interrupts this modeled chain; it does not claim universal prevention.' }
}
1 change: 1 addition & 0 deletions src/simulation/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { runScenario } from './engine'
export { compareAnchorControl } from './comparison'
export { defaultControls, stuckAgentScenario } from './scenario'
export type * from './types'
2 changes: 2 additions & 0 deletions src/ui/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ describe('synchronized simulation views', () => {
expect(screen.getAllByText('Sourced chronology').length).toBeGreaterThan(0)
expect(screen.getByRole('img')).toHaveAccessibleName(/workspace: observed/i)
})

it('reveals the guided single-variable comparison after prediction', () => { render(<App />); expect(screen.queryByText('Run-scoped notes')).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Reveal intervention' })); expect(screen.getByText('Run-scoped notes')).toBeInTheDocument(); expect(screen.getByText(/does not claim universal prevention/i)).toBeInTheDocument(); expect(screen.getByText('Changed controls: noteVisibility')).toBeInTheDocument(); expect(screen.getByText('Advanced controls (explore after guided flow)').closest('details')).not.toHaveAttribute('open') })
})
5 changes: 4 additions & 1 deletion src/ui/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useEffect, useMemo, useState } from 'react'
import { runScenario } from '../simulation'
import { compareAnchorControl, runScenario } from '../simulation'

const provenanceLabel = { sourced: 'Sourced chronology', simulated: 'Simulated transition', inferred: 'Inferred conclusion', mixed: 'Mixed provenance' }

export function App() {
const run = useMemo(() => runScenario({ seed: 1701 }), [])
const [selected, setSelected] = useState(0)
const [playing, setPlaying] = useState(false)
const [revealed, setRevealed] = useState(false)
const comparison = useMemo(() => compareAnchorControl(1701), [])
const current = run.events[selected]
useEffect(() => {
if (!playing) return
Expand All @@ -31,5 +33,6 @@ export function App() {
<article className="panel feed"><h2>Activity feed</h2><ol>{run.events.map((entry, index) => <li key={entry.id}><button aria-current={index === selected ? 'true' : undefined} onClick={() => choose(index)}><span>{entry.time}s · {entry.actorId}</span><strong>{entry.explanation}</strong><small className={`badge ${entry.provenance}`}>{provenanceLabel[entry.provenance]}</small></button></li>)}</ol></article>
</section>
<aside className="detail" aria-live="polite"><p className={`badge ${current.provenance}`}>{provenanceLabel[current.provenance]}</p><h2>{run.beats[selected].title}</h2><p>{current.explanation}</p>{current.provenance === 'mixed' && <p><strong>Event provenance:</strong> {provenanceLabel[current.provenance]}; the transition is a modeled bridge from sourced chronology.</p>}</aside>
<section className="comparison" aria-labelledby="comparison-heading"><p className="eyebrow">Guided comparison · 8 minutes</p><h2 id="comparison-heading">Which single control breaks the demonstrated chain?</h2><ol className="runbook"><li>Setup · 30s</li><li>Baseline · 3m</li><li>Intervention · 1m</li><li>Defended replay · 90s</li><li>Compare & check · 2m</li></ol>{!revealed ? <button onClick={() => setRevealed(true)}>Reveal intervention</button> : <div aria-live="polite"><p><strong>{comparison.interruptedBy}</strong></p><p>{comparison.explanation}</p><div className="metrics">{[comparison.baseline, comparison.defended].map((result) => <article key={result.controls.noteVisibility}><h3>{result.controls.noteVisibility === 'shared' ? 'Baseline' : 'Defended'}</h3><dl><dt>Seed</dt><dd>{result.seed}</dd><dt>Highest privilege</dt><dd>{result.metrics.highestPrivilege}</dd><dt>Organizations affected</dt><dd>{result.metrics.organizationsAffected}</dd><dt>Detection</dt><dd>{result.metrics.detectionStep == null ? 'Not reached' : `step ${result.metrics.detectionStep} · ${result.metrics.detectionTime}s`}</dd><dt>Containment</dt><dd>{result.metrics.containmentStep == null ? 'Not reached' : `step ${result.metrics.containmentStep} · ${result.metrics.containmentTime}s`}</dd><dt>Discoveries reused</dt><dd>{result.metrics.discoveriesReused}</dd><dt>Outcome</dt><dd>{result.outcome.replaceAll('-', ' ')}</dd></dl></article>)}</div><p>Changed controls: {comparison.changedControls.join(', ')}</p></div>}<details><summary>Advanced controls (explore after guided flow)</summary><p>Available after the paired demonstration.</p></details></section>
</main>
}
6 changes: 4 additions & 2 deletions src/ui/styles.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading