Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3a61d61
feat(opencode): persist session lineage roots
jeonghun-jj-lee Sep 13, 2026
054d282
Merge pull request #344 from harmoniqs/amico/issue-1075-session-lineage
jeonghun-jj-lee Sep 13, 2026
196cd1a
feat(session): persist receipt storage (#346)
jeonghun-jj-lee Sep 13, 2026
7155b43
feat(session): reserve receipt root budgets
jeonghun-jj-lee Sep 13, 2026
d893551
Merge pull request #347 from harmoniqs/amico/issue-1103-root-budget
jeonghun-jj-lee Sep 13, 2026
a885be3
feat(session): add bounded evidence sidecars
jeonghun-jj-lee Sep 13, 2026
9c7d37d
Merge pull request #348 from harmoniqs/amico/issue-1104-evidence-sidecar
jeonghun-jj-lee Sep 13, 2026
b5bcc51
feat(session): page legacy external receipts
jeonghun-jj-lee Sep 13, 2026
4c0f2c5
Merge pull request #349 from harmoniqs/amico/issue-1090-legacy-pagina…
jeonghun-jj-lee Sep 13, 2026
49e6bf9
feat(session): add mutation gate
jeonghun-jj-lee Sep 13, 2026
42fcaa1
Merge pull request #350 from harmoniqs/amico/issue-1077-mutation-gate
jeonghun-jj-lee Sep 14, 2026
995f164
feat(session): protect receipt serializers
jeonghun-jj-lee Sep 14, 2026
bf68585
Merge pull request #351 from harmoniqs/amico/issue-1078-privacy-seria…
jeonghun-jj-lee Sep 14, 2026
91e78ac
feat(session): adopt engine mutation groups
jeonghun-jj-lee Sep 14, 2026
8448100
Merge pull request #352 from harmoniqs/amico/issue-1079-engine-mutators
jeonghun-jj-lee Sep 14, 2026
99fe920
feat(session): adopt plugin and runner business records
jeonghun-jj-lee Sep 14, 2026
9b3e12c
Merge pull request #353 from harmoniqs/amico/issue-1081-plugin-runner
jeonghun-jj-lee Sep 14, 2026
eb6c7fa
feat(session): adopt app and extension user mutation routes
jeonghun-jj-lee Sep 14, 2026
faecf99
Merge pull request #354 from harmoniqs/amico/issue-1080-app-mutators
jeonghun-jj-lee Sep 14, 2026
db2820e
feat(app): render the unified lineage ledger
jeonghun-jj-lee Sep 14, 2026
fd19c11
Merge pull request #355 from harmoniqs/amico/issue-1082-unified-ui
jeonghun-jj-lee Sep 14, 2026
757f6fa
feat(session): gate-migration + release-readiness surface
jeonghun-jj-lee Sep 14, 2026
dddaf90
Merge pull request #356 from harmoniqs/amico/issue-1083-rollout-release
jeonghun-jj-lee Sep 14, 2026
dc86009
test(session): versioned provenance matrix manifest + all_required_pa…
jeonghun-jj-lee Sep 14, 2026
62cb05c
test(session): engine harness of the adversarial provenance matrix (#…
jeonghun-jj-lee Sep 14, 2026
aa56257
test(session): ui harness of the adversarial provenance matrix (#1084)
jeonghun-jj-lee Sep 14, 2026
c051cfa
Merge pull request #357 from harmoniqs/amico/issue-1084-adversarial-e2e
jeonghun-jj-lee Sep 14, 2026
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
135 changes: 135 additions & 0 deletions packages/app/src/pages/session/v2/adversarial-ui-e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { describe, expect, test } from "bun:test"
import { ProvenanceMatrix } from "@opencode-ai/schema/provenance-matrix"
import {
applyLedgerKeyDown,
buildLedgerView,
describeLedgerStatus,
LEDGER_CAPABILITY_LABELS,
ledgerCapabilityLabel,
type LedgerStatusInput,
type LedgerStatusKind,
} from "./lineage-ledger-view"

/**
* The ui harness of the adversarial full-provenance matrix (amicode#1084).
*
* This cross-harness E2E fixture RUNS the ui-owned matrix rows against the REAL
* unified lineage-ledger view-model (#1082) — the same pure reducer + status
* descriptors the Files Changed surface renders — and records each row that
* produced its declared observable result as green. It proves the unified surface
* is operable by keyboard and legible in BOTH supported themes (its distinctions
* are text + icon, never color), then feeds the green set to the shared gate
* (`ProvenanceMatrix.gate`) exactly as the engine harness does.
*/

const green = new Set<string>()
const ui = ProvenanceMatrix.subset("ui")

// ── AC7a: keyboard operation ─────────────────────────────────────────────────

function keyEvent(key: string) {
let prevented = false
return { event: { key, preventDefault: () => (prevented = true) }, prevented: () => prevented }
}

function runKeyboard(): void {
// Enter and Space on a focused row toggle it and prevent default (no page scroll).
for (const key of ["Enter", " "]) {
const e = keyEvent(key)
expect(applyLedgerKeyDown(e.event, { focusKind: "row", id: "res-1", expanded: false })).toEqual({
type: "toggle",
id: "res-1",
})
expect(e.prevented()).toBe(true)
}
// Escape inside expanded content collapses and returns focus to the row.
expect(applyLedgerKeyDown(keyEvent("Escape").event, { focusKind: "content", id: "res-1", expanded: true })).toEqual({
type: "collapse",
id: "res-1",
refocus: "res-1",
})
// Escape on an expanded row collapses it.
expect(applyLedgerKeyDown(keyEvent("Escape").event, { focusKind: "row", id: "res-1", expanded: true })).toEqual({
type: "collapse",
id: "res-1",
refocus: "res-1",
})
// Unrelated keys and collapsed-row escape are no-ops — the surface never traps the keyboard.
expect(applyLedgerKeyDown(keyEvent("a").event, { focusKind: "row", id: "res-1", expanded: false })).toEqual({
type: "none",
})
expect(applyLedgerKeyDown(keyEvent("Escape").event, { focusKind: "row", id: "res-1", expanded: false })).toEqual({
type: "none",
})
}

// ── AC7b: legibility across both supported themes (text + icon, never color) ──

const STATUS_CASES: Array<{ input: LedgerStatusInput; kind: LedgerStatusKind }> = [
{ input: { netState: "added", outcome: "succeeded" }, kind: "added" },
{ input: { netState: "modified", outcome: "succeeded" }, kind: "modified" },
{ input: { netState: "deleted", outcome: "succeeded" }, kind: "deleted" },
{ input: { netState: "reverted", outcome: "succeeded" }, kind: "reverted" },
{ input: { netState: "conflicted", outcome: "succeeded" }, kind: "conflicted" },
{ input: { netState: "unavailable", outcome: "succeeded", evidenceState: "unavailable" }, kind: "unavailable" },
{ input: { outcome: "opaque", netState: "opaque" }, kind: "opaque" },
{ input: { outcome: "partial", netState: "added" }, kind: "partial" },
]

function runThemes(): void {
const descriptors = STATUS_CASES.map((c) => describeLedgerStatus(c.input))
// Each named state resolves to its kind.
for (const [index, c] of STATUS_CASES.entries()) expect(descriptors[index].kind).toBe(c.kind)
// Distinctions are TEXT + ICON — unique per state — so the surface is legible without relying on color.
expect(new Set(descriptors.map((d) => d.label)).size).toBe(STATUS_CASES.length)
expect(new Set(descriptors.map((d) => d.icon)).size).toBe(STATUS_CASES.length)
for (const d of descriptors) {
expect(d.label.length).toBeGreaterThan(0)
expect(d.icon.length).toBeGreaterThan(0)
// Tone is a semantic keyword, never a raw color literal — so no theme owns the meaning.
expect(["success", "danger", "warning", "neutral"]).toContain(d.tone)
expect(d.tone).not.toMatch(/#|rgb|hsl/i)
}
// Theme-independent by construction: the descriptor carries no theme branch or inline color.
expect(describeLedgerStatus({ netState: "deleted", outcome: "succeeded" })).toEqual(
describeLedgerStatus({ netState: "deleted", outcome: "succeeded" }),
)
// Screen-reader legibility (theme-agnostic): the accessible name carries execution + assessment + evidence.
const accessible = describeLedgerStatus({ outcome: "succeeded", netState: "added", evidenceState: "available" }).accessibleName.toLowerCase()
for (const token of ["succeeded", "added", "available"]) expect(accessible).toContain(token)
// The capability label is fixed explanatory text selected only by capability — surfaced on the built view.
for (const mode of ["full", "partial", "legacy"] as const)
expect(ledgerCapabilityLabel(mode)).toEqual(LEDGER_CAPABILITY_LABELS[mode])
expect(buildLedgerView({ capability: { mode: "legacy" }, records: [] }).capability).toEqual(
LEDGER_CAPABILITY_LABELS.legacy,
)
}

// ── Register the ui per-row tests ────────────────────────────────────────────

describe("adversarial matrix — ui harness (unified Files Changed surface)", () => {
for (const row of ui.cases) {
test(row.id, () => {
if (row.id === "a11y:keyboard") runKeyboard()
else if (row.id === "a11y:themes") runThemes()
else throw new Error(`no ui executor for row ${row.id}`)
green.add(row.id)
})
}
})

describe("adversarial matrix — the ui-subset release gate (AC8)", () => {
test("every required ui case ran green — all_required_passed, enablement STILL off", () => {
const result = ProvenanceMatrix.gate(ui, green)
expect(result.missingRequired).toEqual([])
expect(result.all_required_passed).toBe(true)
expect(result.defaultEnablement).toBe("off")
})

test("dropping any one required ui case blocks the gate", () => {
const dropped = ProvenanceMatrix.requiredCaseIDs(ui)[0]
const minusOne = new Set(green)
minusOne.delete(dropped)
expect(ProvenanceMatrix.gate(ui, minusOne).all_required_passed).toBe(false)
})
})
125 changes: 125 additions & 0 deletions packages/app/src/pages/session/v2/lineage-ledger-panel.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* Unified lineage-ledger panel (amicode#1082). Geometry + color come only from
* design-system tokens, so both themes are equal citizens and the status color
* is redundant to the icon + text label (color is never the only signal). */

[data-component="lineage-ledger"] {
display: flex;
flex-direction: column;
gap: var(--space-2, 8px);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-capability"] {
display: flex;
flex-direction: column;
gap: var(--space-1, 4px);
padding: var(--space-3, 12px);
border: 1px solid var(--v2-border-border-base);
border-radius: var(--radius-lg, 12px);
background: var(--v2-background-bg-layer-01);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-capability"][data-mode="legacy"],
[data-component="lineage-ledger"] [data-slot="lineage-ledger-capability"][data-mode="partial"] {
border-color: var(--v2-border-border-warning);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-filter"] {
padding: var(--space-1, 4px) var(--space-2, 8px);
border: 1px solid var(--v2-border-border-base);
border-radius: var(--radius-md, 8px);
background: var(--v2-background-bg-layer-02);
color: var(--v2-text-text-base);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-filter"]:focus-visible {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 1px;
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-resources"] {
display: flex;
flex-direction: column;
gap: var(--space-1, 4px);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-resource-row"] {
display: flex;
align-items: center;
gap: var(--space-2, 8px);
width: 100%;
padding: var(--space-1, 4px) var(--space-2, 8px);
border-radius: var(--radius-md, 8px);
background: transparent;
text-align: left;
cursor: pointer;
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-resource-row"]:hover {
background: var(--v2-overlay-simple-overlay-hover);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-resource-row"]:focus-visible {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: -1px;
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-path"] {
flex: 1 1 auto;
min-width: 0;
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-status"] {
display: inline-flex;
align-items: center;
gap: var(--space-1, 4px);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-status"][data-tone="success"] {
color: var(--v2-state-fg-success);
}
[data-component="lineage-ledger"] [data-slot="lineage-ledger-status"][data-tone="danger"] {
color: var(--v2-state-fg-danger);
}
[data-component="lineage-ledger"] [data-slot="lineage-ledger-status"][data-tone="warning"] {
color: var(--v2-state-fg-warning);
}
[data-component="lineage-ledger"] [data-slot="lineage-ledger-status"][data-tone="neutral"] {
color: var(--v2-text-text-muted);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-history"] {
display: flex;
flex-direction: column;
gap: var(--space-1, 4px);
padding: var(--space-1, 4px) var(--space-2, 8px) var(--space-2, 8px) var(--space-5, 20px);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-unknown"] {
display: flex;
flex-direction: column;
gap: var(--space-1, 4px);
padding: var(--space-2, 8px);
border: 1px solid var(--v2-border-border-warning);
border-radius: var(--radius-lg, 12px);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-unknown-item"] {
display: flex;
align-items: center;
gap: var(--space-2, 8px);
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-more"] {
align-self: flex-start;
padding: var(--space-1, 4px) var(--space-2, 8px);
border: 1px solid var(--v2-border-border-base);
border-radius: var(--radius-md, 8px);
background: var(--v2-background-bg-layer-01);
color: var(--v2-text-text-base);
cursor: pointer;
}

[data-component="lineage-ledger"] [data-slot="lineage-ledger-more"]:focus-visible {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 1px;
}
71 changes: 71 additions & 0 deletions packages/app/src/pages/session/v2/lineage-ledger-panel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, expect, test } from "bun:test"
import { readFileSync } from "node:fs"
import { resolve } from "node:path"

// The lineage-ledger panel is authored in Solid JSX; bun's test transpiler does
// not run Solid's dom-expressions transform, so (as elsewhere in this repo —
// see vscode-explorer-file-icon.test.tsx) the component's behavioral substance
// lives in pure, directly-tested functions (lineage-ledger-view.test.ts) and
// this test proves the component wires the accessibility contract those
// functions require. The Work Column design contract (amicode#1082 AC8) is:
// Tab reaches each filter/row/continuation/unknown item, Enter/Space toggles a
// resource, Escape returns focus to its row, and each status has an accessible
// name plus a non-color signal.
const source = readFileSync(resolve(__dirname, "lineage-ledger-panel.tsx"), "utf8")

describe("LineageLedgerPanel — render + accessibility contract", () => {
test("renders from the projected view-model only (no tool/watcher inference — AC1)", () => {
expect(source).toContain("./lineage-ledger-view")
expect(source).toContain("props.view()")
expect(source).not.toMatch(/accumulate-diffs|toolDiffs|externalFileStatus|watcher/i)
})

test("surfaces the capability label as accessible status text (AC7)", () => {
expect(source).toContain('data-slot="lineage-ledger-capability"')
expect(source).toContain('role="status"')
expect(source).toContain("capability().label")
expect(source).toContain("capability().description")
})

test("each canonical resource is one focusable, toggleable row (AC2/AC8)", () => {
expect(source).toContain('data-slot="lineage-ledger-resource"')
expect(source).toContain("aria-expanded")
expect(source).toContain("aria-controls")
// a real <button> is natively Tab-reachable and Enter/Space-activatable
expect(source).toMatch(/<button[\s\S]*?onKeyDown/)
})

test("status carries an accessible name and a non-color signal (AC5)", () => {
expect(source).toContain('data-slot="lineage-ledger-status"')
expect(source).toContain("status.accessibleName")
expect(source).toContain("data-status={")
expect(source).toContain("data-tone={")
// icon + text label accompany the tone — color is never the only signal
expect(source).toContain("status.icon")
expect(source).toContain("status.label")
})

test("expanded history is a chronological region keyed to its row (AC3)", () => {
expect(source).toContain('data-slot="lineage-ledger-history"')
expect(source).toContain("history")
expect(source).toContain("assessments")
})

test("unknown mutation receipts render in a dedicated uncertainty group (AC4)", () => {
expect(source).toContain('data-slot="lineage-ledger-unknown"')
expect(source).toContain("Unknown Mutation Receipts")
expect(source).toContain("view().unknown")
})

test("keyboard: applyLedgerKeyDown drives toggle/collapse and Escape refocuses the row (AC8)", () => {
expect(source).toContain("applyLedgerKeyDown")
expect(source).toContain('"toggle"')
expect(source).toContain('"collapse"')
expect(source).toContain("focus()")
})

test("paging exposes a Tab-reachable continuation control (AC6)", () => {
expect(source).toContain('data-slot="lineage-ledger-more"')
expect(source).toContain("nextCursor")
})
})
Loading
Loading