[attestations] Add attestations to mvr frontend#243
Conversation
Records the design decisions and build plan for displaying package attestations (from a curated set of trusted attestor packages) on the MVR web app, read over JSON-RPC from a localnet-backed simulated network. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- crates/mvr-api/examples/demo_server.rs: ephemeral-Postgres mvr-api that seeds name_records/packages/package_infos from the attestation demo's demo-ids.json (lifting the test-cluster setup_dummy_data pattern) and runs the real run_server, so @demo/subject resolves to the localnet package address. - app: env-driven mainnet RPC + MVR endpoint overrides (NEXT_PUBLIC_MAINNET_RPC_URL / NEXT_PUBLIC_MAINNET_MVR_ENDPOINT), unset in production; documented in .env.example. - ATTESTATION-INTEGRATION.md: local run recipe; M0/M1 marked done. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the override env vars to NEXT_PUBLIC_LOCAL_RPC_URL / NEXT_PUBLIC_LOCAL_MVR_ENDPOINT and apply them to both networks plus the experimental/analytics endpoints, so the demo never touches live Sui infra. Fixes a crash where the analytics call still hit real QA mainnet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- lib/attestations.ts: env-driven config, boxAddress (deriveObjectID), Attestation<T> JSON-RPC mapper, trusted-lineage helpers, and the ported conventions effectiveness interpreter (active/expires/requires with cycle guard). - useGetAttestations: getOwnedObjects on the per-subject Box, client-side trusted-lineage filter + Display-gate, per-attestation effectiveness. - SinglePackageAttestations tab: grouped by attester, exact inner type, effectiveness badge, https report link; count label of effective attestations. Registered in the SinglePackage Tabs. - scripts/write-demo-env.sh: generate app/.env (endpoints + attestation config incl. attester lineage) from demo-ids.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename the package tab to Trust Signals with Attestations as a section; split into Vulnerabilities and Audits sections (by polarity), each grouped by attester with an initials avatar. - Tab badge: two pills — effective positive count (check) and effective negative count (warning) — as real SVG icons (no more orange-on-orange). - lib/attestations.ts: isNegative() polarity helper, optional iconUrl on TrustedAttestor. - Doc: pass plan + web-of-trust whitelist and summary-field follow-ups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- demo_server seeds the subject -> dependency edge (powers the Dependencies tab and propagation). - useGetAttestations: extract fetchTrustedAttestations(); add useInheritedVulns() that surfaces a dependency's effective vulns on its dependents (negative-polarity dual of `requires`). - Security tab: Vulnerabilities (one severity-sorted list of own + inherited, CVSS band chips, max-severity-colored warning) and Audits sections; ineffective collapsed to a compact 'Inactive' line; friendly attester names (write-demo-env.sh) + attester avatars; count-pill numbers default-colored, icons colored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- demo_server seeds an MVR name for each trusted attester package (@demo/audit, @demo/vuln) so they browse to their own page. - write-demo-env.sh adds mvrName to the trust config (in sync). - Trust config gains optional mvrName; the Security tab links each attester name (on audit cards and disclosure rows) to its MVR page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Audit cards show the attester's MVR name (from the trust config, no extra reverse-resolution roundtrip) rather than the truncated original id. - Doc: web-of-trust whitelist deferred pending a team discussion on the RPC-roundtrip cost of per-attester on-chain lookups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Disclosure rows show the attester's MVR name in parens (e.g. Example Security Scanner (@example-scanner/disclosures)). - Auditors get distinct MVR namespaces: @example-auditor/audits and @example-scanner/disclosures (underscores aren't valid MVR/SuiNS names, so hyphenated orgs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seed a git_infos row for each trusted attester pointing at the sui-attestation-registry repo's mvr-demo tag, so the auditor MVR pages render their sample README via MVR's git-backed README fetch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Audit cards: friendly name as a plain label, MVR name as the link. - Disclosure rows: drop the display name; show the attester avatar + the MVR name as a link. - (Dependency provenance names were already linked.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace the package address in displayed types with the attester's (linked) MVR name; vuln rows consolidate to avatar + linked @org/name::module::Type · in dependency <link>. - Audit rows show module::Type (no raw address; attester is in the card). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plorer links - Cards only show effective attestations, so the 'Active' badge is removed. - Vulnerability rows' left border is colored by severity band. - Attestation types show the object id truncated + linked to an explorer, e.g. audit::Audit (0x123…567). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Severity left-border uses an arbitrary-value class bound to the CSS var (the content palette is text-only, so border-content-* didn't exist). - The 'N high, M medium' header summary colors each band segment by its own severity (not all by the max). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Color severity chips, the per-band header breakdown, and the warning icons via inline style bound to the band's CSS var. Tailwind only scans .tsx, so classes built in attestations.ts (.ts) were never generated — which silently dropped Medium (text-content-warning). Inline styles are generation-independent. - Severity left-border likewise via inline style. - Move the report/advisory link into the row header next to the headline (the description), e.g. 'Heap overflow in the dependency (scanner.example.com ↗)'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the client-side lineage filter with a server-side MatchAny: the trusted type set is the Attestation<T> for every store type T defined by a trusted attester's lineage (resolveTrustedTypes via getNormalizedMoveModulesByPackage, cached). Untrusted attestations are never returned by the node; the read-time Display-gate still drops trusted-but-undisplayed types. JSON-RPC only — no GraphQL/indexer. Verified against localnet (Untrusted excluded server-side). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pass 4: an 'Issued' tab on trusted-attester package pages, listing the attestations a package has issued (GraphQL objects() reverse query over the attester's Attestation<T> types, grouped by subject, Display-gated, with revoked/expired entries shown inactive). The tab is gated on trusted-attestor whitelist membership — a free in-memory check (isConfiguredAttestor) — so the reverse query never runs on non-attester package pages. Repoints the GraphQL endpoint to the local stack alongside RPC/MVR, and the env generator emits NEXT_PUBLIC_LOCAL_GRAPHQL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The local run recipe and the demo_server example hardcoded a personal path for demo-ids.json. It's a cross-repo artifact (written by the attestation-registry repo's run-demo.sh) whose location this repo can't know, so use a <attestation-registry>/demo-ids.json placeholder instead. Also note WITH_GRAPHQL=1 in the recipe — the frontend reads need localnet GraphQL on :9125 (gRPC is the part not needed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New /attestors page listing the configured trusted attestors, each linking to its MVR package page; reachable via a "View all trusted attestors" link on the Security tab. - "MVR-trusted attestor" badge in the package header, shown when the package is a configured attestor (same isConfiguredAttestor gate as the tab). - Rename the attester-only "Issued" tab to "Attestations". - Give the demo attestors real brand icons (public SVGs + iconUrl in the generated config) so AttesterAvatar stops falling back to initials; avatar is now rounded-md to read as a logo. AttesterAvatar exported + gains an lg size so the page and Security tab share one implementation. - write-demo-env.sh now requires the demo-ids.json path explicitly (first arg or ATTESTATION_DEMO_IDS) rather than defaulting to a machine-specific path. All attestation UI stays demo-only: attestationConfig() is null in production, so the page is empty, the badge never shows, and the Security-tab link hides. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The attestation-registry repo reorganized ts/lib into ts/src (SDK) / examples / demo. Update the ported-from references (boxes/conventions/queries) in the attestations read layer and ATTESTATION-INTEGRATION.md accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the on-chain box-membership model:
- attestations.ts: `boxAddress(registryPkg, registryId, subject)` keys on
`BoxKey{subject, revoked:false}`; add `revokedBoxAddress` (revoked:true).
`isEffective` shrinks to expiry-only; `readActive`/`readRequires`/
`ConventionsContext` removed.
- useGetAttestations: Trust Signals reads the active box, so revoked
attestations are absent by construction. The Issued tab reads by type
across all boxes, so it recovers revocation from ownership — an issued
attestation is revoked iff its owner is the subject's revoked sink. Drops
the now-dead `fetchByIdFor`.
- SinglePackageIssued: greys out revoked/expired rows, labelling "(revoked)"
vs "(expired)".
Verified on localnet: the auditor's Issued tab flags the revoked dependency
audit; the subject's audits stay live.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revoked attestations live in the per-subject revoked sink, so the
active-box reads never see them. Surface them explicitly instead of a
subtle inline grey:
- Issued tab: revoked entries move to their own "Revoked" section at the
bottom (factored out groupBySubject + SubjectGroup).
- Security page: read the revoked sink (fetchRevokedAttestations /
useGetRevokedAttestations), sharing a new fetchBoxAttestations core with
the active-box read, and render a concise "Revoked: ..." list.
- InactiveList gains a `label` ("Inactive" for expired, "Revoked" for the
sink); DisplayedAttestation now extends a base AttributedAttestation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the attestation frontend positive-only for the MVP and finish the
revocation UX:
- Carve out the negative surface: drop useInheritedVulns/InheritedVuln, the
VulnerabilitiesSection/VulnRow/severity-chip UI, and isNegative/
readSeverity/severityBand. Shared MVR features (useMvrDependencies, the
Dependencies/Dependents/Versions tabs) untouched.
- Security page: warn when a package has no live attestations ("…no active
attestations published on MVR, it may not have been audited") — covers
unattested and only-revoked alike — and drop the "View all trusted
attestors" link (reachable via the attester icon).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the attestation-repo restructure that renamed the demo's trusted auditor package to `auditor_a` (a published copy of `examples/auditor`). write-demo-env keys the friendly name/icon/mvr-name maps on the demo-ids trustedAttestor name, which is now `auditor_a`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| /** Every package-version id in the attester's lineage. Matching an | ||
| * attestation's inner-type package against this set is equivalent to | ||
| * resolving that package's original id (the on-chain `attester_of` rule). | ||
| * M2 seeds this directly; M3 derives it via GraphQL `packageVersions`. */ |
There was a problem hiding this comment.
This comment is pretty confusing
There was a problem hiding this comment.
[CLAUDE] Reworded the comment.
| registryId: string, | ||
| subject: string, | ||
| revoked: boolean, | ||
| ): string { |
There was a problem hiding this comment.
not familiar with ts, but could we introduce some type aliases so that everything isn't just string?
There was a problem hiding this comment.
[CLAUDE] Done — added SuiAddress/ObjectId aliases and applied them to the config/box/id fields.
| export interface AttestationInfo { | ||
| id: string; | ||
| /** Full object type, `…::attestation_registry::Attestation<T>`. */ | ||
| type: string; |
There was a problem hiding this comment.
do we need to keep type since it's always {registryPkg}::attestation_registry::Attestation<{innerType}>?
There was a problem hiding this comment.
[CLAUDE] Done — dropped the type field; it's derived from innerType (+ the registry pkg) where needed, and no other consumer used it.
|
|
||
| /** The defining (origin) package id of an inner type string. */ | ||
| export function innerTypePackage(innerType: string): string { | ||
| return normalizeSuiAddress(innerType.split("::")[0]!); |
There was a problem hiding this comment.
is there a sui SDK that will do some of this stuff for us?
There was a problem hiding this comment.
[CLAUDE] @mysten/sui already gives us the primitives in use — deriveObjectID + bcs for the box-address derivation, and SuiClient/GraphQL for the reads. The only custom piece is the BoxKey-derivation wrapper mirroring the on-chain derive_address; there's no higher-level SDK for the registry's box layout specifically.
| * An attestation is effective iff its `expires_at` Display field (if present) | ||
| * is still in the future. Revocation is handled upstream by box membership. | ||
| */ | ||
| export function isEffective( |
There was a problem hiding this comment.
we should consider removing expiresAt from the MVP, which would remove this logic entirely (take it out of CONVENTIONS.md or leave it in a future extensions section).
There was a problem hiding this comment.
IIRC our demo doesn't exercise it, and I don't have any concrete examples in mind of who would want to use it. Feel free to propose some
There was a problem hiding this comment.
[CLAUDE] Done — removed the expiration feature entirely (isEffective/readExpiresAt + the effective field across lib/hooks/components), matching expires_at → planned-future on the registry side.
| * attesters, with effectiveness. Reads the per-subject Box directly over | ||
| * JSON-RPC (no MVR backend). | ||
| * | ||
| * Spam-resistant (M3): rather than fetch every `Attestation<*>` on the box and |
There was a problem hiding this comment.
We should remove things like "M3" from the comments - nobody knows what they mean
There was a problem hiding this comment.
[CLAUDE] Done — stripped the M1/M2/M3 milestone labels (kept the substantive comment text).
| * `revoke` moved out of the active box into the subject's revoked sink. Same | ||
| * trusted/Display filter, just against the sink address. | ||
| */ | ||
| export async function fetchRevokedAttestations( |
There was a problem hiding this comment.
if an attestation is unrevoked but expired then it will not show up at all. If we decide to keep the expiration feature, we should show expired attestations (probably add fetchExpiredAttestations). But as I mentioned above, I think we should consider removing expiration.
There was a problem hiding this comment.
[CLAUDE] Resolved by removing expiration — there's no expired-but-unrevoked state anymore; a live attestation (in the active box) is simply live.
| const key = `${cfg.registryPkg}|${[...new Set(lineage.map((id) => normalizeSuiAddress(id)))].join(",")}`; | ||
| const cached = trustedTypesCache.get(key); | ||
| if (cached) return cached; | ||
| const promise = enumerateAttestationTypes(client, lineage, cfg.registryPkg); |
There was a problem hiding this comment.
We could pre-enumerate the attestation types in the config instead of refetching them every time. It might be sensible for the config to list the trusted types anyway so that we can include some attestations and not others. wdyt?
There was a problem hiding this comment.
OTOH we lose the automatic upgradability property (an attestor can just add new types and have them appear).
There was a problem hiding this comment.
[CLAUDE] Noted — I lean toward this (pre-enumerate the trusted types in the config: a perf win, and it enables whitelisting specific types, not just attesters). Holding it with the config-sourcing decision, since the config format would change together.
There was a problem hiding this comment.
I'm a little uncomfortable having the demo stuff co-mingled with the actual mvr application.
There was a problem hiding this comment.
[CLAUDE] Removed — app/public/demo-attestors/*.svg is gone, so no demo asset ships in the app. Each demo auditor's brand icon now lives with its package in the attestation-registry repo (beside the README the demo already renders: demo/auditor_a/icon.svg, demo/auditor_b/icon.svg), and write-demo-env points iconUrl at the GitHub raw URL (4d7c852). Bonus: iconUrl is now a real external URL, which matches the production shape — a consumer's trust config points at an attester's hosted icon rather than a local path. The other demo-only bits were already segregated: demo_server is a cargo example (not in the shipped binary), write-demo-env is a script, and the integration md is a doc.
| // The real `subject -> dependency` edge, so the dependencies endpoint | ||
| // returns it (powering both the Dependencies tab and vuln propagation). | ||
| { | ||
| let mut conn = db.connect().await?; |
There was a problem hiding this comment.
the approach here is that we're directly pushing the mvr names into the database. This is probably ok, but I wonder what it would look like to go through the mvr registration process for them (i.e. publish the on-chain registrations and then let the indexer pick them up).
There was a problem hiding this comment.
[CLAUDE] Decided (per your discussion) to keep the direct DB seeding — going through real on-chain mvr registration + indexer pickup isn't worth the extra demo machinery here. Leaving demo_server's approach as-is.
08382c8 to
9faacf8
Compare
Same swallow as the Issued tab: SinglePackageTrustSignals only checked isLoading, so a failed forward read would render as "This package version has no published audits" — a failure disguised as a clean negative. Surface the query error explicitly, matching the Issued tab. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ions
- Reverse read returns { items, failures }: the per-object getObject re-read
now counts rejections instead of silently dropping them, and the Issued tab
shows "N attestations couldn't be loaded" while still rendering the rest.
- Revoked moved out of the inline list into their own de-emphasized section
(each section independently paginated), so endorsements read distinctly from
withdrawn ones.
- Each row now leads with the attestation's description (then module::Type +
date as secondary metadata).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d error The Security tab's version selector now navigates to /package/<name>/<version> (preserving the tab) instead of swapping a local list, so the whole page — and the tab's attestation-count pill, which keys off the resolved version — reflects the chosen version. Drops the selectedAddress state + re-sync effect. Also fold the revoked sub-query's error into the surfaced error, so a revoked-read failure no longer hides behind "no published audits". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The badge was shown for `version === name.version` — the version the page is resolved to — which only coincided with the newest version because you'd normally view a package at its latest. Now that the attestation version selector can navigate to an older version, that conflation mislabeled the viewed (older) version as "Latest". Tag the actual max version as "Latest", add a separate "Current" marker for the viewed version when it isn't the latest, and use the max version for the Versions count heading (same conflation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ector Replace the version dropdown (which navigated to /package/<name>/<version>) with all versions listed on the page at once, newest first — each version shows its own audits / "no published audits" / errors via a VersionAudits sub-component (one per version, so the per-version reads stay rules-of-hooks-safe). Answers "which versions are audited?" at a glance and drops the dependency on per-version page routing. Single-version packages render with no version header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"✓ 0" on the latest version is a real signal — this package has no published audits — so show the pill at zero too, instead of hiding it. (No config gate; the feature only ships once attesters are configured for prod anyway.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0f2e69a to
4ad5e16
Compare
The in-page heading reads "Audits" for now, since audits are the only trust signal. Once others are integrated, the "Security" tab becomes an h1 with an "Audits" h2 beneath it. The tab title is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The card special-cased `display.score` (an AuditV2-specific field) as a pill, coupling the platform UI to one schema. Drop it: the card now surfaces only the standard presentation conventions (name/description/image_url/link). A schema-specific field like `score` should be promoted to a documented convention if it's broadly useful, not inferred by the general UI. Removes the now-unused ScorePill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seed an mvr name (+ README) for each untrustedAttestors entry in demo-ids.json, so @demo/auditor-b resolves and its page is browsable. It's not added to the trust config, so the UI hides its Issued tab — the case we want to demo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Issued tab now works for any package, not just configured trusted attesters: - useIssuedAttestations enumerates the viewed package's own types from its mvr version lineage (useGetMvrVersionAddresses) instead of the trust config, so it loads for any package. The trust config governs only presentation now. - The tab stays in the nav for configured attesters only, but is reachable by URL (?tab=issued) for any package — SinglePackage splits nav visibility (navTabs) from URL-selectability (the full Tabs list); the issued label gets `name` so its count can resolve the lineage too. - SinglePackageIssued shows a prominent "not on your trusted list" warning when the package isn't a configured attester. Safe to show: Attestation<T> is mintable only by T's defining package, so a package's issued list is genuinely its own claims; the warning is about trust, not authenticity. Verified: tsc clean; demo end-to-end — auditor-b (untrusted) issued read returns its 1 attestation and /package/@demo/auditor-b?tab=issued serves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give the attestation registry package itself an mvr name (@demo/attestations), so <registry-pkg> resolves by name in the demo — e.g. as a move-call target like the auditor packages, matching what the onboarding doc describes. No README (the package has none; like @demo/subject). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give the demo packages more realistic mvr names: the registry is @mysten/attestations, and each auditor is its own org (@auditor-a/audit, @auditor-b/audit) rather than @demo/auditor-x — matching the existing @example-scanner/disclosures shape. Subject/dependency stay @demo/*. Kept in sync: auditor_mvr_name (demo_server.rs) and the trusted attester's mvrName (write-demo-env.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the registry name's git_path to packages/attestations so its mvr page shows the package README (mirrored into the public prototype repo that the demo fetches READMEs from). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No description provided.