diff --git a/README.md b/README.md index 98128cc..4535f14 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,22 @@ More detail is available in [`docs/HARNESS.md`](docs/HARNESS.md), [`docs/API.md` The default demo is replay based. It verifies the UI, API, decision path, dry run execution, streamed results, and ledger hash verification without making provider calls. The TrueForge driver has been exercised against the local harness and the Keyring MCP endpoints, including the five system fan out, reconciliation, and card persistence. Live GitHub and Google Workspace provider operations remain opt in and require credentials, a configured MCP server, and a throwaway test organization. The repository does not claim that every live provider response or live mutation path has been verified. +## Qodo Code Review Evidence + +Qodo was installed before the first feature commit and reviewed all nine pull requests. Nothing merged to `main` without a review. + +**Representative merged PR:** [#2, audit chain fork regression](https://github.com/GautamTalksDev/keyring/pull/2) + +Qodo found that a regression test permanently altered the `recorded_at` column default and never restored it, leaking a fixed timestamp into every subsequent test on the shared Postgres backend. We wrapped the mutation in `try/finally` so restoration happens even when an assertion fails. The PR history records the completed review, our decision to fix the isolation leak rather than add a retry, and Qodo's follow up review against the final code before merge. + +The deepest fix Qodo prompted was in the audit ledger. A test failed once and passed on retry, but the underlying issue was that two records written in the same millisecond could claim the same parent and fork the hash chain. That meant the tamper-evident ledger was not tamper-evident under concurrent writes. We fixed it with a monotonic sequence column, a unique constraint on the parent hash, and an advisory lock. + +Other merged PRs with substantive Qodo findings, all fixed before merge: + +- [#8, demo and security hardening](https://github.com/GautamTalksDev/keyring/pull/8) fixed five bugs, including a demo card limit applied to production scan paths that silently dropped grants from real audits, and a secret scanner that reported success without scanning anything. +- [#7, guided demo safeguards](https://github.com/GautamTalksDev/keyring/pull/7) fixed four bugs, including guided demo decisions committing server-side after a stop. +- [#9, queue legibility](https://github.com/GautamTalksDev/keyring/pull/9) fixed the UI labelling declared agents as unregistered because it inferred registration from missing attribution instead of reading the authoritative declaration status. + ## AI assistance disclosure This project was developed with assistance from Cursor, an AI coding agent. Humans directed the product decisions, safety defaults, tests, review, and final verification. Cursor assisted with implementation, refactoring, testing, and documentation drafting, as permitted by the hackathon rules. diff --git a/apps/web/src/components/ApprovalCardView.tsx b/apps/web/src/components/ApprovalCardView.tsx index c3606ea..7a22030 100644 --- a/apps/web/src/components/ApprovalCardView.tsx +++ b/apps/web/src/components/ApprovalCardView.tsx @@ -86,10 +86,9 @@ export function ApprovalCardView({ ) : null} {card.grant.principal.kind === "ai_agent" && - (card.grant.principal.agentName === "Keyring" || - card.grant.evidence.some( - (evidence) => evidence.source === "keyring:self-inventory", - )) ? ( + card.grant.evidence.some( + (evidence) => evidence.source === "keyring:self-inventory", + ) ? ( Self-inventory @@ -188,7 +187,9 @@ export function ApprovalCardView({
{inferenceConclusion(card, who)} - ▸ show inference chain + + ▸ show {hasInferenceChain(card) ? "inference chain" : "attribution details"} +

{card.attribution.reasoning} @@ -231,7 +232,12 @@ export function ApprovalCardView({ } function inferenceConclusion(card: ApiCard, who: string): string { - const chain = card.attribution.reasoning.split("Inference chain:")[1]?.trim(); + const chain = hasInferenceChain(card) + ? card.attribution.reasoning.split("Inference chain:")[1]?.trim() + : undefined; + if (!chain && !card.attribution.resolvedTo) { + return `Unattributed · ${card.proposedAction.description}`; + } const firstSignal = chain ?.split(" → ")[0] ?.replace(/^\([^)]+\)\s*/, "") @@ -242,6 +248,10 @@ function inferenceConclusion(card: ApiCard, who: string): string { } · ${firstSignal || "no matching inference"}`; } +function hasInferenceChain(card: ApiCard): boolean { + return card.attribution.reasoning.includes("Inference chain:"); +} + function topRiskReason(reasons: string[]): string { return ( reasons.reduce((top, reason) => { diff --git a/apps/web/src/hooks/useGuidedDemo.ts b/apps/web/src/hooks/useGuidedDemo.ts index 0633f77..0f733ee 100644 --- a/apps/web/src/hooks/useGuidedDemo.ts +++ b/apps/web/src/hooks/useGuidedDemo.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { executeScanStream, fetchAudit, postDecision } from "../api/client.js"; import type { ApiCard, AuditRecord, AuditVerification, ExecuteResult } from "../api/types.js"; import type { AgentActivityState } from "../api/types.js"; -import { sortCards } from "../lib/format.js"; +import { queueSections } from "../lib/format.js"; export type GuidedDemoPhase = | "idle" @@ -259,7 +259,7 @@ export function useGuidedDemo({ }); await wait(HEADLINE_HOLD_MS, controller.signal); - const ordered = sortCards(cardsRef.current); + const ordered = queueSections(cardsRef.current).visualOrder; const safeCards = ordered.filter( (card) => card.status === "pending" &&