@aglyn/plugins-marketplace 1.0.0-beta.152 → 1.0.0-beta.153

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/listing-review-status.component.d.ts +1 -1
  3. package/src/lib/components/listing-review-status.component.js +1 -1
  4. package/src/lib/components/listing-review-status.component.js.map +1 -1
  5. package/src/lib/components/listing-verification-request.component.d.ts +1 -1
  6. package/src/lib/components/listing-verification-request.component.js +1 -1
  7. package/src/lib/components/listing-verification-request.component.js.map +1 -1
  8. package/src/lib/components/plugin-review-detail.component.js +1 -1
  9. package/src/lib/components/plugin-review-detail.component.js.map +1 -1
  10. package/src/lib/components/publish-plugin-form.component.js +1 -1
  11. package/src/lib/components/publish-plugin-form.component.js.map +1 -1
  12. package/src/lib/model/listing-verification.d.ts +95 -0
  13. package/src/lib/model/listing-verification.js +81 -0
  14. package/src/lib/model/listing-verification.js.map +1 -0
  15. package/src/lib/model/marketplace.d.ts +11 -7
  16. package/src/lib/model/marketplace.js +9 -5
  17. package/src/lib/model/marketplace.js.map +1 -1
  18. package/src/lib/model/publisher-attestation.d.ts +99 -0
  19. package/src/lib/model/publisher-attestation.js +158 -0
  20. package/src/lib/model/publisher-attestation.js.map +1 -0
  21. package/src/lib/server/admin-reviews.js +2 -2
  22. package/src/lib/server/admin-reviews.js.map +1 -1
  23. package/src/lib/server/artifact-inventory.d.ts +18 -0
  24. package/src/lib/server/artifact-inventory.js +171 -0
  25. package/src/lib/server/artifact-inventory.js.map +1 -0
  26. package/src/lib/server/listing-versions.js +1 -1
  27. package/src/lib/server/listing-versions.js.map +1 -1
  28. package/src/lib/server/publish-plugin.js +1 -1
  29. package/src/lib/server/publish-plugin.js.map +1 -1
  30. package/src/lib/server.js +13 -0
  31. package/src/lib/server.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-marketplace",
3
- "version": "1.0.0-beta.152",
3
+ "version": "1.0.0-beta.153",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,19 +25,19 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.152",
29
- "@aglyn/aglyn-markdown-editor": "1.0.0-beta.152",
30
- "@aglyn/shared-data-enums": "1.0.0-beta.152",
31
- "@aglyn/shared-data-mdi": "1.0.0-beta.152",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.152",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.152",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.152",
35
- "@aglyn/shared-util-email": "1.0.0-beta.152",
36
- "@aglyn/shared-util-http": "1.0.0-beta.152",
37
- "@aglyn/shared-util-timestamp": "1.0.0-beta.152",
38
- "@aglyn/tenant-data-admin": "1.0.0-beta.152",
39
- "@aglyn/tenant-feature-instance": "1.0.0-beta.152",
40
- "@aglyn/tenant-runtime": "1.0.0-beta.152",
28
+ "@aglyn/aglyn": "1.0.0-beta.153",
29
+ "@aglyn/aglyn-markdown-editor": "1.0.0-beta.153",
30
+ "@aglyn/shared-data-enums": "1.0.0-beta.153",
31
+ "@aglyn/shared-data-mdi": "1.0.0-beta.153",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.153",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.153",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.153",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.153",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.153",
37
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.153",
38
+ "@aglyn/tenant-data-admin": "1.0.0-beta.153",
39
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.153",
40
+ "@aglyn/tenant-runtime": "1.0.0-beta.153",
41
41
  "@swc/helpers": "0.5.23"
42
42
  },
43
43
  "peerDependencies": {
@@ -1,4 +1,4 @@
1
- import { PUBLISHER_ATTESTATION } from '@aglyn/aglyn/app-utils/publisher-attestation';
1
+ import { PUBLISHER_ATTESTATION } from '../model/publisher-attestation';
2
2
  export interface PublisherVersion {
3
3
  version: string;
4
4
  publishedAtMs: number | null;
@@ -19,7 +19,7 @@ import { CardDisplay } from "@aglyn/shared-ui-jsx";
19
19
  import { Alert, Chip, Divider, Stack, Typography } from "@mui/material";
20
20
  import { useEffect, useState } from "react";
21
21
  import { useUser } from "@aglyn/tenant-feature-instance";
22
- import { attestationLabels, PUBLISHER_ATTESTATION, requiredAttestationIds } from "@aglyn/aglyn/app-utils/publisher-attestation";
22
+ import { attestationLabels, PUBLISHER_ATTESTATION, requiredAttestationIds } from "../model/publisher-attestation.js";
23
23
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
24
24
  import { pluginDocsHelp } from "@aglyn/aglyn";
25
25
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-review-status.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Chip,\n Divider,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n attestationLabels,\n PUBLISHER_ATTESTATION,\n requiredAttestationIds,\n} from '@aglyn/aglyn/app-utils/publisher-attestation'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface PublisherVersion {\n version: string\n publishedAtMs: number | null\n sha256: string\n reviewState: string\n grandfathered: boolean\n signed: boolean\n changelog: string\n rejectionReason: string\n /**\n * The platform kill switch, per version (AGL-2328). Independent of\n * `reviewState` and NOT a substitute for it: a version is normally\n * approved first and revoked afterwards, so both are true at once and the\n * card has to say both.\n */\n revoked?: boolean\n /**\n * Reconciled against the listing totals server-side (AGL-1418), so this\n * card and the listing's own Version history print the same number for the\n * same version instead of two answers to one question.\n */\n activeInstalls: number\n installCount?: number\n attestation: string[]\n}\n\ninterface PublisherVersionsPayload {\n versions: PublisherVersion[]\n latestApprovedVersion: string\n latestVersion: string\n /** Listing-scoped — one reason covers every revoked version (AGL-2328). */\n revocationReason?: string\n revokedAtMs?: number | null\n}\n\n/**\n * A review state said in words (AGL-1079).\n *\n * `grandfathered` is checked FIRST and deliberately: those versions were\n * published before review was per-version (AGL-966), so they carry no\n * verdict at all. Calling them \"pending\" would promise a reviewer who is\n * never coming, and calling them \"approved\" would claim a judgement nobody\n * made.\n */\nfunction reviewLabel(entry: PublisherVersion): {\n label: string\n color: 'default' | 'success' | 'warning' | 'error' | 'info'\n detail: string\n} {\n if (entry.grandfathered) {\n return {\n label: 'Published before review',\n color: 'default',\n detail:\n 'Published before versions were reviewed individually. It installs, ' +\n 'but no reviewer ever signed off on these bytes.',\n }\n }\n switch (entry.reviewState) {\n case 'approved':\n return {\n label: 'Approved',\n color: 'success',\n detail: 'A reviewer approved these exact bytes.',\n }\n case 'rejected':\n return {\n label: 'Rejected',\n color: 'error',\n detail:\n 'Not installable. Fix what the reason says and publish a new ' +\n 'version — these bytes cannot be edited.',\n }\n case 'pending':\n return {\n label: 'In review',\n color: 'warning',\n detail:\n 'Waiting for a reviewer. Nobody else can install it — but you ' +\n 'can, on your own sites, which is how you test a version before ' +\n 'it is approved.',\n }\n default:\n return {\n label: 'Unknown',\n color: 'default',\n detail: 'No review state was recorded for this version.',\n }\n }\n}\n\nexport interface ListingReviewStatusProps {\n listingId: string\n /** Only a plugin listing has a review-per-version story to tell. */\n isPlugin: boolean\n}\n\n/**\n * What review is doing with a publisher's versions (AGL-1079).\n *\n * AGL-966's guarantee — a new version waits in the queue while the\n * previously approved one keeps installing — has been true and invisible\n * since the day it shipped. A publisher got an email on the outcome and,\n * in between, a listing row identical to the one they had before they\n * published. This is the page that answers what they submitted, what state\n * it is in, which version their customers are actually on, and why one was\n * sent back.\n *\n * Reads through the publisher-scoped versions endpoint rather than\n * Firestore: `pluginVersions` is server-only, because the docs carry\n * publish internals a client has no business holding.\n */\nexport function ListingReviewStatus(props: ListingReviewStatusProps) {\n const { listingId, isPlugin } = props\n const { data: user } = useUser()\n const [payload, setPayload] = useState<PublisherVersionsPayload | null>(null)\n const [failed, setFailed] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n if (!listingId || !isPlugin) return undefined\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/marketplace/listing-versions?scope=publisher&listingId=${encodeURIComponent(\n listingId,\n )}`,\n )\n if (!response.ok) {\n if (!cancelled) setFailed(true)\n return\n }\n const data = (await response.json()) as PublisherVersionsPayload\n if (!cancelled) setPayload(data)\n } catch {\n if (!cancelled) setFailed(true)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [listingId, isPlugin, user])\n\n if (!isPlugin) return null\n if (failed) return null\n // Nothing rather than an empty shell: this card is only worth space once\n // it has something to say, and a skeleton that never fills is worse than\n // a card that appears.\n if (!payload) return null\n\n const { versions, latestApprovedVersion, latestVersion } = payload\n const revocationReason = String(payload.revocationReason ?? '').trim()\n const revokedAtMs = payload.revokedAtMs ?? null\n const inReview = versions.filter(\n (entry) => entry.reviewState === 'pending' && !entry.grandfathered,\n )\n /**\n * The version the card names as installing today, revoked (AGL-2328).\n *\n * `latestApprovedVersion` is a REVIEW verdict and the kill switch is not\n * — nothing about revoking a version clears it — so the headline below\n * went on saying \"is approved and is what installs today\" about a version\n * the installer refuses with a 409. That is the sentence a publisher\n * reads before opening a support ticket about installs that stopped\n * working, so it is checked before any of the review arms.\n */\n const installsTodayRevoked = Boolean(\n latestApprovedVersion &&\n versions.some(\n (entry) => entry.version === latestApprovedVersion && entry.revoked,\n ),\n )\n const behind =\n latestApprovedVersion &&\n latestVersion &&\n latestApprovedVersion !== latestVersion\n\n return (\n <CardDisplay\n header={'Review status'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#review-what-happens-after-you-publish',\n excerpt:\n 'What state each version is in, which one installs today, and why ' +\n 'anything was sent back.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n {/* The AGL-966 guarantee, in a sentence. It has been true since the\n day it shipped and nobody has ever read it. */}\n {installsTodayRevoked ? (\n <Alert severity=\"error\">\n {`v${latestApprovedVersion} was disabled by the platform. It is ` +\n 'still your latest approved version, but nobody can install ' +\n 'it and sites already running it render a placeholder. ' +\n 'Publishing a new version is what clears this — these bytes ' +\n 'cannot be re-enabled by editing them.'}\n </Alert>\n ) : behind && inReview.length ? (\n <Alert severity=\"info\">\n {`v${latestVersion} is in review. New installs get ` +\n `v${latestApprovedVersion} until it is approved, and anyone ` +\n 'already running it stays where they are.'}\n </Alert>\n ) : behind ? (\n <Alert severity=\"warning\">\n {`v${latestVersion} is not approved, so new installs get ` +\n `v${latestApprovedVersion}.`}\n </Alert>\n ) : latestApprovedVersion ? (\n <Alert severity=\"success\">\n {`v${latestApprovedVersion} is approved and is what installs today.`}\n </Alert>\n ) : (\n <Alert severity=\"info\">\n {'Nothing is approved yet, so nobody else can install this. A ' +\n 'reviewer sees your submission in the queue. You can still ' +\n 'install it on your own sites to test it — that is the only ' +\n 'way these bytes run today.'}\n </Alert>\n )}\n\n {/* Stated rather than implied (AGL-1079). Both halves are things a\n publisher currently has to infer from behaviour. */}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'While a version is in review you can still edit this listing — ' +\n 'name, description, README, screenshots and links — and it will ' +\n 'not send anything back to the queue, because approval is about ' +\n 'the bundle’s bytes. The bytes themselves are never edited: ' +\n 'changing them means publishing a new version.'}\n </Typography>\n\n <Divider />\n\n <Stack spacing={2}>\n {versions.map((entry) => {\n const state = reviewLabel(entry)\n const claimed = entry.attestation.length\n // Which set applied to THIS version: the first one published is\n // not asked for a changelog, so measuring every version against\n // the update set would report the first as permanently short.\n const expected = requiredAttestationIds(\n entry.version !== versions[versions.length - 1]?.version,\n ).length\n return (\n <Stack key={entry.version} spacing={0.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"subtitle2\">{`v${entry.version}`}</Typography>\n <Chip size=\"small\" color={state.color} label={state.label} />\n {/* Beside the review chip, never instead of it: a version\n is normally approved and THEN revoked, and collapsing\n the two would erase the verdict the publisher earned. */}\n {entry.revoked ? (\n <Chip size=\"small\" color=\"error\" label=\"Disabled\" />\n ) : null}\n {/* Not on a revoked row — it does not install today, and\n the whole defect here was a card that said it did. */}\n {entry.version === latestApprovedVersion && !entry.revoked ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Installs today\"\n />\n ) : null}\n {entry.signed ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Realm-trusted\"\n />\n ) : null}\n {entry.activeInstalls ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={\n state.label === 'Approved' ? undefined : 'warning'\n }\n label={`${entry.activeInstalls} on this version`}\n />\n ) : null}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {state.detail}\n {entry.publishedAtMs\n ? ` Published ${new Date(\n entry.publishedAtMs,\n ).toLocaleDateString()}.`\n : ''}\n </Typography>\n {/* An unreviewed version that is nonetheless RUNNING\n somewhere. The publisher-testing carve-out in\n `install-plugin` is deliberate — you cannot test a\n version you cannot install — but it is invisible, and a\n card that said \"nothing installs it yet\" over a live\n install was worse than saying nothing. */}\n {entry.activeInstalls &&\n entry.reviewState !== 'approved' &&\n !entry.grandfathered ? (\n <Alert severity=\"warning\" sx={{ mt: 0.5 }}>\n {/* \"Installs\", not \"sites\" (AGL-1418). An org-wide\n install counts once and covers every site in that\n organization, so this used to report a five-site\n exposure as one site — understating the very risk the\n alert exists to raise. */}\n {`${entry.activeInstalls} of your installs ${\n entry.activeInstalls === 1 ? 'is' : 'are'\n } running this version, which no reviewer has approved. ` +\n 'Only your own organization can install it — but an ' +\n 'org-wide install covers every site in it, so if any ' +\n 'of them is live, unreviewed code is serving real ' +\n 'visitors.'}\n </Alert>\n ) : null}\n {/* In place, on the version it belongs to — it only ever\n reached an email before. */}\n {entry.rejectionReason ? (\n <Alert severity=\"error\" sx={{ mt: 0.5 }}>\n {entry.rejectionReason}\n </Alert>\n ) : null}\n {/* The kill switch, said to the publisher it happened to\n (AGL-2328). The consumer whose site broke was told the\n reason first; the publisher who caused it was told\n nothing at all, on the one page that exists to tell them\n what state their versions are in. */}\n {entry.revoked ? (\n <Alert severity=\"error\" sx={{ mt: 0.5 }}>\n {'The platform disabled this version. Nobody can install ' +\n 'it, and sites already running it render a placeholder.'}\n {/* Only when one was recorded. A revocation written\n before the reason was required says so, rather than\n showing an empty quotation that reads as \"no reason\n given\". */}\n {revocationReason ? (\n <Typography variant=\"body2\" sx={{ mt: 1 }}>\n {`Reason: ${revocationReason}`}\n </Typography>\n ) : null}\n {revokedAtMs ? (\n <Typography\n variant=\"caption\"\n sx={{ display: 'block', mt: 0.5 }}\n >\n {`Disabled ${new Date(\n revokedAtMs,\n ).toLocaleDateString()}`}\n </Typography>\n ) : null}\n </Alert>\n ) : null}\n {entry.changelog ? (\n <Typography variant=\"caption\">\n {entry.changelog}\n </Typography>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {claimed\n ? `You confirmed ${claimed}/${expected}: ` +\n attestationLabels(entry.attestation)\n .map((label) => label.replace(/\\s+/g, ' '))\n .join('; ')\n : `Nothing was confirmed for these bytes — published ` +\n 'before the pre-publish checklist existed, or ' +\n 'republished since.'}\n </Typography>\n {entry.sha256 ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ fontFamily: 'monospace' }}\n >\n {`sha256 ${entry.sha256.slice(0, 12)}…`}\n </Typography>\n ) : null}\n </Stack>\n )\n })}\n {versions.length ? null : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'No versions published yet.'}\n </Typography>\n )}\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\n\nListingReviewStatus.displayName = 'ListingReviewStatus'\n\nexport default ListingReviewStatus\n\n/** Exported for the spec: the words each state is said in. */\nexport { reviewLabel, PUBLISHER_ATTESTATION }\n"],"names":["CardDisplay","Alert","Chip","Divider","Stack","Typography","useEffect","useState","useUser","attestationLabels","PUBLISHER_ATTESTATION","requiredAttestationIds","authorizedFetch","pluginDocsHelp","reviewLabel","entry","grandfathered","label","color","detail","reviewState","ListingReviewStatus","props","payload","listingId","isPlugin","data","user","setPayload","failed","setFailed","cancelled","undefined","response","encodeURIComponent","ok","json","versions","latestApprovedVersion","latestVersion","revocationReason","String","trim","revokedAtMs","inReview","filter","installsTodayRevoked","Boolean","some","version","revoked","behind","header","help","anchor","excerpt","contentGutterX","contentGutterY","spacing","severity","length","variant","map","state","claimed","attestation","expected","direction","sx","alignItems","flexWrap","size","signed","activeInstalls","publishedAtMs","Date","toLocaleDateString","mt","rejectionReason","display","changelog","replace","join","sha256","fontFamily","slice","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-review-status.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Chip,\n Divider,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n attestationLabels,\n PUBLISHER_ATTESTATION,\n requiredAttestationIds,\n} from '../model/publisher-attestation'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface PublisherVersion {\n version: string\n publishedAtMs: number | null\n sha256: string\n reviewState: string\n grandfathered: boolean\n signed: boolean\n changelog: string\n rejectionReason: string\n /**\n * The platform kill switch, per version (AGL-2328). Independent of\n * `reviewState` and NOT a substitute for it: a version is normally\n * approved first and revoked afterwards, so both are true at once and the\n * card has to say both.\n */\n revoked?: boolean\n /**\n * Reconciled against the listing totals server-side (AGL-1418), so this\n * card and the listing's own Version history print the same number for the\n * same version instead of two answers to one question.\n */\n activeInstalls: number\n installCount?: number\n attestation: string[]\n}\n\ninterface PublisherVersionsPayload {\n versions: PublisherVersion[]\n latestApprovedVersion: string\n latestVersion: string\n /** Listing-scoped — one reason covers every revoked version (AGL-2328). */\n revocationReason?: string\n revokedAtMs?: number | null\n}\n\n/**\n * A review state said in words (AGL-1079).\n *\n * `grandfathered` is checked FIRST and deliberately: those versions were\n * published before review was per-version (AGL-966), so they carry no\n * verdict at all. Calling them \"pending\" would promise a reviewer who is\n * never coming, and calling them \"approved\" would claim a judgement nobody\n * made.\n */\nfunction reviewLabel(entry: PublisherVersion): {\n label: string\n color: 'default' | 'success' | 'warning' | 'error' | 'info'\n detail: string\n} {\n if (entry.grandfathered) {\n return {\n label: 'Published before review',\n color: 'default',\n detail:\n 'Published before versions were reviewed individually. It installs, ' +\n 'but no reviewer ever signed off on these bytes.',\n }\n }\n switch (entry.reviewState) {\n case 'approved':\n return {\n label: 'Approved',\n color: 'success',\n detail: 'A reviewer approved these exact bytes.',\n }\n case 'rejected':\n return {\n label: 'Rejected',\n color: 'error',\n detail:\n 'Not installable. Fix what the reason says and publish a new ' +\n 'version — these bytes cannot be edited.',\n }\n case 'pending':\n return {\n label: 'In review',\n color: 'warning',\n detail:\n 'Waiting for a reviewer. Nobody else can install it — but you ' +\n 'can, on your own sites, which is how you test a version before ' +\n 'it is approved.',\n }\n default:\n return {\n label: 'Unknown',\n color: 'default',\n detail: 'No review state was recorded for this version.',\n }\n }\n}\n\nexport interface ListingReviewStatusProps {\n listingId: string\n /** Only a plugin listing has a review-per-version story to tell. */\n isPlugin: boolean\n}\n\n/**\n * What review is doing with a publisher's versions (AGL-1079).\n *\n * AGL-966's guarantee — a new version waits in the queue while the\n * previously approved one keeps installing — has been true and invisible\n * since the day it shipped. A publisher got an email on the outcome and,\n * in between, a listing row identical to the one they had before they\n * published. This is the page that answers what they submitted, what state\n * it is in, which version their customers are actually on, and why one was\n * sent back.\n *\n * Reads through the publisher-scoped versions endpoint rather than\n * Firestore: `pluginVersions` is server-only, because the docs carry\n * publish internals a client has no business holding.\n */\nexport function ListingReviewStatus(props: ListingReviewStatusProps) {\n const { listingId, isPlugin } = props\n const { data: user } = useUser()\n const [payload, setPayload] = useState<PublisherVersionsPayload | null>(null)\n const [failed, setFailed] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n if (!listingId || !isPlugin) return undefined\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/marketplace/listing-versions?scope=publisher&listingId=${encodeURIComponent(\n listingId,\n )}`,\n )\n if (!response.ok) {\n if (!cancelled) setFailed(true)\n return\n }\n const data = (await response.json()) as PublisherVersionsPayload\n if (!cancelled) setPayload(data)\n } catch {\n if (!cancelled) setFailed(true)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [listingId, isPlugin, user])\n\n if (!isPlugin) return null\n if (failed) return null\n // Nothing rather than an empty shell: this card is only worth space once\n // it has something to say, and a skeleton that never fills is worse than\n // a card that appears.\n if (!payload) return null\n\n const { versions, latestApprovedVersion, latestVersion } = payload\n const revocationReason = String(payload.revocationReason ?? '').trim()\n const revokedAtMs = payload.revokedAtMs ?? null\n const inReview = versions.filter(\n (entry) => entry.reviewState === 'pending' && !entry.grandfathered,\n )\n /**\n * The version the card names as installing today, revoked (AGL-2328).\n *\n * `latestApprovedVersion` is a REVIEW verdict and the kill switch is not\n * — nothing about revoking a version clears it — so the headline below\n * went on saying \"is approved and is what installs today\" about a version\n * the installer refuses with a 409. That is the sentence a publisher\n * reads before opening a support ticket about installs that stopped\n * working, so it is checked before any of the review arms.\n */\n const installsTodayRevoked = Boolean(\n latestApprovedVersion &&\n versions.some(\n (entry) => entry.version === latestApprovedVersion && entry.revoked,\n ),\n )\n const behind =\n latestApprovedVersion &&\n latestVersion &&\n latestApprovedVersion !== latestVersion\n\n return (\n <CardDisplay\n header={'Review status'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#review-what-happens-after-you-publish',\n excerpt:\n 'What state each version is in, which one installs today, and why ' +\n 'anything was sent back.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n {/* The AGL-966 guarantee, in a sentence. It has been true since the\n day it shipped and nobody has ever read it. */}\n {installsTodayRevoked ? (\n <Alert severity=\"error\">\n {`v${latestApprovedVersion} was disabled by the platform. It is ` +\n 'still your latest approved version, but nobody can install ' +\n 'it and sites already running it render a placeholder. ' +\n 'Publishing a new version is what clears this — these bytes ' +\n 'cannot be re-enabled by editing them.'}\n </Alert>\n ) : behind && inReview.length ? (\n <Alert severity=\"info\">\n {`v${latestVersion} is in review. New installs get ` +\n `v${latestApprovedVersion} until it is approved, and anyone ` +\n 'already running it stays where they are.'}\n </Alert>\n ) : behind ? (\n <Alert severity=\"warning\">\n {`v${latestVersion} is not approved, so new installs get ` +\n `v${latestApprovedVersion}.`}\n </Alert>\n ) : latestApprovedVersion ? (\n <Alert severity=\"success\">\n {`v${latestApprovedVersion} is approved and is what installs today.`}\n </Alert>\n ) : (\n <Alert severity=\"info\">\n {'Nothing is approved yet, so nobody else can install this. A ' +\n 'reviewer sees your submission in the queue. You can still ' +\n 'install it on your own sites to test it — that is the only ' +\n 'way these bytes run today.'}\n </Alert>\n )}\n\n {/* Stated rather than implied (AGL-1079). Both halves are things a\n publisher currently has to infer from behaviour. */}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'While a version is in review you can still edit this listing — ' +\n 'name, description, README, screenshots and links — and it will ' +\n 'not send anything back to the queue, because approval is about ' +\n 'the bundle’s bytes. The bytes themselves are never edited: ' +\n 'changing them means publishing a new version.'}\n </Typography>\n\n <Divider />\n\n <Stack spacing={2}>\n {versions.map((entry) => {\n const state = reviewLabel(entry)\n const claimed = entry.attestation.length\n // Which set applied to THIS version: the first one published is\n // not asked for a changelog, so measuring every version against\n // the update set would report the first as permanently short.\n const expected = requiredAttestationIds(\n entry.version !== versions[versions.length - 1]?.version,\n ).length\n return (\n <Stack key={entry.version} spacing={0.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"subtitle2\">{`v${entry.version}`}</Typography>\n <Chip size=\"small\" color={state.color} label={state.label} />\n {/* Beside the review chip, never instead of it: a version\n is normally approved and THEN revoked, and collapsing\n the two would erase the verdict the publisher earned. */}\n {entry.revoked ? (\n <Chip size=\"small\" color=\"error\" label=\"Disabled\" />\n ) : null}\n {/* Not on a revoked row — it does not install today, and\n the whole defect here was a card that said it did. */}\n {entry.version === latestApprovedVersion && !entry.revoked ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Installs today\"\n />\n ) : null}\n {entry.signed ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Realm-trusted\"\n />\n ) : null}\n {entry.activeInstalls ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={\n state.label === 'Approved' ? undefined : 'warning'\n }\n label={`${entry.activeInstalls} on this version`}\n />\n ) : null}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {state.detail}\n {entry.publishedAtMs\n ? ` Published ${new Date(\n entry.publishedAtMs,\n ).toLocaleDateString()}.`\n : ''}\n </Typography>\n {/* An unreviewed version that is nonetheless RUNNING\n somewhere. The publisher-testing carve-out in\n `install-plugin` is deliberate — you cannot test a\n version you cannot install — but it is invisible, and a\n card that said \"nothing installs it yet\" over a live\n install was worse than saying nothing. */}\n {entry.activeInstalls &&\n entry.reviewState !== 'approved' &&\n !entry.grandfathered ? (\n <Alert severity=\"warning\" sx={{ mt: 0.5 }}>\n {/* \"Installs\", not \"sites\" (AGL-1418). An org-wide\n install counts once and covers every site in that\n organization, so this used to report a five-site\n exposure as one site — understating the very risk the\n alert exists to raise. */}\n {`${entry.activeInstalls} of your installs ${\n entry.activeInstalls === 1 ? 'is' : 'are'\n } running this version, which no reviewer has approved. ` +\n 'Only your own organization can install it — but an ' +\n 'org-wide install covers every site in it, so if any ' +\n 'of them is live, unreviewed code is serving real ' +\n 'visitors.'}\n </Alert>\n ) : null}\n {/* In place, on the version it belongs to — it only ever\n reached an email before. */}\n {entry.rejectionReason ? (\n <Alert severity=\"error\" sx={{ mt: 0.5 }}>\n {entry.rejectionReason}\n </Alert>\n ) : null}\n {/* The kill switch, said to the publisher it happened to\n (AGL-2328). The consumer whose site broke was told the\n reason first; the publisher who caused it was told\n nothing at all, on the one page that exists to tell them\n what state their versions are in. */}\n {entry.revoked ? (\n <Alert severity=\"error\" sx={{ mt: 0.5 }}>\n {'The platform disabled this version. Nobody can install ' +\n 'it, and sites already running it render a placeholder.'}\n {/* Only when one was recorded. A revocation written\n before the reason was required says so, rather than\n showing an empty quotation that reads as \"no reason\n given\". */}\n {revocationReason ? (\n <Typography variant=\"body2\" sx={{ mt: 1 }}>\n {`Reason: ${revocationReason}`}\n </Typography>\n ) : null}\n {revokedAtMs ? (\n <Typography\n variant=\"caption\"\n sx={{ display: 'block', mt: 0.5 }}\n >\n {`Disabled ${new Date(\n revokedAtMs,\n ).toLocaleDateString()}`}\n </Typography>\n ) : null}\n </Alert>\n ) : null}\n {entry.changelog ? (\n <Typography variant=\"caption\">\n {entry.changelog}\n </Typography>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {claimed\n ? `You confirmed ${claimed}/${expected}: ` +\n attestationLabels(entry.attestation)\n .map((label) => label.replace(/\\s+/g, ' '))\n .join('; ')\n : `Nothing was confirmed for these bytes — published ` +\n 'before the pre-publish checklist existed, or ' +\n 'republished since.'}\n </Typography>\n {entry.sha256 ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ fontFamily: 'monospace' }}\n >\n {`sha256 ${entry.sha256.slice(0, 12)}…`}\n </Typography>\n ) : null}\n </Stack>\n )\n })}\n {versions.length ? null : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'No versions published yet.'}\n </Typography>\n )}\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\n\nListingReviewStatus.displayName = 'ListingReviewStatus'\n\nexport default ListingReviewStatus\n\n/** Exported for the spec: the words each state is said in. */\nexport { reviewLabel, PUBLISHER_ATTESTATION }\n"],"names":["CardDisplay","Alert","Chip","Divider","Stack","Typography","useEffect","useState","useUser","attestationLabels","PUBLISHER_ATTESTATION","requiredAttestationIds","authorizedFetch","pluginDocsHelp","reviewLabel","entry","grandfathered","label","color","detail","reviewState","ListingReviewStatus","props","payload","listingId","isPlugin","data","user","setPayload","failed","setFailed","cancelled","undefined","response","encodeURIComponent","ok","json","versions","latestApprovedVersion","latestVersion","revocationReason","String","trim","revokedAtMs","inReview","filter","installsTodayRevoked","Boolean","some","version","revoked","behind","header","help","anchor","excerpt","contentGutterX","contentGutterY","spacing","severity","length","variant","map","state","claimed","attestation","expected","direction","sx","alignItems","flexWrap","size","signed","activeInstalls","publishedAtMs","Date","toLocaleDateString","mt","rejectionReason","display","changelog","replace","join","sha256","fontFamily","slice","displayName"],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type VerifiableListing } from '@aglyn/aglyn/app-utils/marketplace-verification';
17
+ import { type VerifiableListing } from '../model/listing-verification';
18
18
  export interface ListingVerificationRequestProps {
19
19
  listingId: string;
20
20
  listing: VerifiableListing | null | undefined;
@@ -18,7 +18,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
18
18
  import { CardDisplay } from "@aglyn/shared-ui-jsx";
19
19
  import { Alert, Button, Stack, Typography } from "@mui/material";
20
20
  import { useState } from "react";
21
- import { timestampMs, VERIFICATION_DECLINE_COOLDOWN_DAYS, verificationRequestBlock } from "@aglyn/aglyn/app-utils/marketplace-verification";
21
+ import { timestampMs, VERIFICATION_DECLINE_COOLDOWN_DAYS, verificationRequestBlock } from "../model/listing-verification.js";
22
22
  import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
23
23
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
24
24
  import { pluginDocsHelp } from "@aglyn/aglyn";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-verification-request.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\nimport {\n timestampMs,\n VERIFICATION_DECLINE_COOLDOWN_DAYS,\n verificationRequestBlock,\n type VerifiableListing,\n} from '@aglyn/aglyn/app-utils/marketplace-verification'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface ListingVerificationRequestProps {\n listingId: string\n listing: VerifiableListing | null | undefined\n /** The org the viewer is acting as. */\n viewerOrgId: string | null | undefined\n user: unknown\n /** Verified is a plugin-listing claim; other artifact types never ask. */\n isPlugin: boolean\n}\n\n/** What the badge actually claims, said once, in the place someone asks for\n * it — so the request is informed rather than a guess at what it buys.\n *\n * `PLATFORM_BRAND_NAME` and NOT the org's resolved `productName` (AGL-2351).\n * The sentence names WHO DID THE VERIFYING, and that is whoever operates this\n * deployment's marketplace — the reviewers who read the listing. Substituting\n * the viewing org's brand would make a white-label agency's console assert\n * that the agency vetted this publisher, which nobody at the agency did. The\n * marketplace is a platform-relationship surface: one catalog, one review\n * queue, shared by every org on the deployment. On a self-host install the\n * operator's own staff really are the humans in this sentence, and the env var\n * renames them with the rest of the product.\n */\nconst WHAT_VERIFIED_CLAIMS =\n `Verified says a human at ${PLATFORM_BRAND_NAME} confirmed who you are, ` +\n 'and that this listing describes what the code does. It is a claim about ' +\n 'you as a publisher, so it survives a version bump. It is not a security ' +\n 'guarantee, and it does not review the bytes of any particular release — ' +\n 'that is what Reviewed means, and it is earned per version.'\n\n/**\n * The publisher's door to the Verified badge (AGL-1217).\n *\n * Before this there was none: verification was reviewer-initiated only, so a\n * publisher who wanted the badge had no affordance and no way to learn that\n * asking was even possible.\n *\n * Eligibility comes from `verificationRequestBlock` — the same function the\n * server enforces. That sharing is deliberate: a rule the button knows and\n * the server does not is decoration, and a rule the server knows and the\n * button does not is a button that fails with no explanation.\n */\nexport function ListingVerificationRequest(\n props: ListingVerificationRequestProps,\n) {\n const { listingId, listing, viewerOrgId, user, isPlugin } = props\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n // Optimistically reflects the action just taken, so the card does not sit\n // unchanged while the listing snapshot catches up.\n const [justDid, setJustDid] = useState<'pending' | 'withdrawn' | null>(null)\n\n const request = listing?.verificationRequest\n const state = justDid ?? request?.state\n const block = verificationRequestBlock({\n listing,\n viewerOrgId,\n nowMs: Date.now(),\n })\n\n const post = async (action: 'request' | 'withdraw') => {\n setBusy(true)\n setError(null)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/verification-request',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ listingId, action }),\n },\n )\n const data = (await response.json().catch(() => ({}))) as {\n error?: string\n }\n if (!response.ok) {\n // The server's own words. It re-checks the same policy, so when it\n // refuses it knows something this component's snapshot did not.\n setError(data.error ?? 'That did not work. Try again.')\n return\n }\n setJustDid(action === 'request' ? 'pending' : 'withdrawn')\n } catch {\n setError('That did not work. Try again.')\n } finally {\n setBusy(false)\n }\n }\n\n if (!isPlugin) return null\n // Not the publisher, or already carrying the badge: nothing to offer. The\n // badge itself is rendered on the listing body, so saying so here would be\n // repeating it in a card about asking.\n if (block === 'not-publisher' || block === 'already-verified') return null\n // A listing still in the queue has a review card above already explaining\n // where it stands. A second card saying \"and you cannot ask for a badge\n // yet\" adds noise to a publisher who has not asked for anything.\n if (block === 'not-listed' && state !== 'pending') return null\n\n const declinedAt = timestampMs(request?.decidedAt)\n const canAskAgain =\n declinedAt === null\n ? null\n : new Date(\n declinedAt + VERIFICATION_DECLINE_COOLDOWN_DAYS * 86_400_000,\n ).toLocaleDateString()\n\n return (\n <CardDisplay\n header={'Verified publisher'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#asking-to-be-verified',\n excerpt:\n 'When you can ask, what happens while it waits, and what a decline ' +\n 'does and does not mean.',\n })}\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {WHAT_VERIFIED_CLAIMS}\n </Typography>\n\n {state === 'pending' ? (\n <>\n <Alert severity=\"info\">\n {'Your request is with the review team. Your plugin stays ' +\n 'listed and installable while they look.'}\n </Alert>\n <Stack direction=\"row\">\n <Button\n variant=\"outlined\"\n color=\"inherit\"\n disabled={busy}\n onClick={() => void post('withdraw')}\n >\n {'Withdraw request'}\n </Button>\n </Stack>\n </>\n ) : block === 'cooling-down' ? (\n <Alert severity=\"warning\">\n <Stack spacing={1}>\n <Typography variant=\"body2\">\n {request?.declineReason\n ? `Declined: ${request.declineReason}`\n : 'A previous request was declined.'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {canAskAgain\n ? `You can ask again from ${canAskAgain}.`\n : `You can ask again ${VERIFICATION_DECLINE_COOLDOWN_DAYS} days after the decision.`}\n </Typography>\n </Stack>\n </Alert>\n ) : (\n <Stack direction=\"row\">\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={busy || block !== null}\n onClick={() => void post('request')}\n >\n {'Request verification'}\n </Button>\n </Stack>\n )}\n\n {error ? <Alert severity=\"error\">{error}</Alert> : null}\n </Stack>\n </CardDisplay>\n )\n}\n\nexport default ListingVerificationRequest\n"],"names":["CardDisplay","Alert","Button","Stack","Typography","useState","timestampMs","VERIFICATION_DECLINE_COOLDOWN_DAYS","verificationRequestBlock","PLATFORM_BRAND_NAME","authorizedFetch","pluginDocsHelp","WHAT_VERIFIED_CLAIMS","ListingVerificationRequest","props","listingId","listing","viewerOrgId","user","isPlugin","busy","setBusy","error","setError","justDid","setJustDid","request","verificationRequest","state","block","nowMs","Date","now","post","action","response","method","headers","body","JSON","stringify","data","json","catch","ok","declinedAt","decidedAt","canAskAgain","toLocaleDateString","header","help","anchor","excerpt","spacing","variant","color","severity","direction","disabled","onClick","declineReason"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,WAAW,QAAQ,uBAAsB;AAClD,SAASC,KAAK,EAAEC,MAAM,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAChE,SAASC,QAAQ,QAAQ,QAAO;AAChC,SACEC,WAAW,EACXC,kCAAkC,EAClCC,wBAAwB,QAEnB,kDAAiD;AACxD,SAASC,mBAAmB,QAAQ,wCAAuC;AAC3E,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,cAAc,QAAQ,eAAc;AAY7C;;;;;;;;;;;;CAYC,GACD,MAAMC,uBACJ,CAAC,yBAAyB,EAAEH,oBAAoB,wBAAwB,CAAC,GACzE,6EACA,6EACA,6EACA;AAEF;;;;;;;;;;;CAWC,GACD,OAAO,SAASI,2BACdC,KAAsC;IAEtC,MAAM,EAAEC,SAAS,EAAEC,OAAO,EAAEC,WAAW,EAAEC,IAAI,EAAEC,QAAQ,EAAE,GAAGL;IAC5D,MAAM,CAACM,MAAMC,QAAQ,GAAGhB,SAAS;IACjC,MAAM,CAACiB,OAAOC,SAAS,GAAGlB,SAAwB;IAClD,0EAA0E;IAC1E,mDAAmD;IACnD,MAAM,CAACmB,SAASC,WAAW,GAAGpB,SAAyC;IAEvE,MAAMqB,UAAUV,2BAAAA,QAASW,mBAAmB;IAC5C,MAAMC,QAAQJ,kBAAAA,UAAWE,2BAAAA,QAASE,KAAK;IACvC,MAAMC,QAAQrB,yBAAyB;QACrCQ;QACAC;QACAa,OAAOC,KAAKC,GAAG;IACjB;IAEA,MAAMC,OAAO,OAAOC;QAClBb,QAAQ;QACRE,SAAS;QACT,IAAI;YACF,MAAMY,WAAW,MAAMzB,gBACrBQ,MACA,yCACA;gBACEkB,QAAQ;gBACRC,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9CC,MAAMC,KAAKC,SAAS,CAAC;oBAAEzB;oBAAWmB;gBAAO;YAC3C;YAEF,MAAMO,OAAQ,MAAMN,SAASO,IAAI,GAAGC,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;YAGlD,IAAI,CAACR,SAASS,EAAE,EAAE;oBAGPH;gBAFT,mEAAmE;gBACnE,gEAAgE;gBAChElB,UAASkB,cAAAA,KAAKnB,KAAK,YAAVmB,cAAc;gBACvB;YACF;YACAhB,WAAWS,WAAW,YAAY,YAAY;QAChD,EAAE,eAAM;YACNX,SAAS;QACX,SAAU;YACRF,QAAQ;QACV;IACF;IAEA,IAAI,CAACF,UAAU,OAAO;IACtB,0EAA0E;IAC1E,2EAA2E;IAC3E,uCAAuC;IACvC,IAAIU,UAAU,mBAAmBA,UAAU,oBAAoB,OAAO;IACtE,0EAA0E;IAC1E,wEAAwE;IACxE,iEAAiE;IACjE,IAAIA,UAAU,gBAAgBD,UAAU,WAAW,OAAO;IAE1D,MAAMiB,aAAavC,YAAYoB,2BAAAA,QAASoB,SAAS;IACjD,MAAMC,cACJF,eAAe,OACX,OACA,IAAId,KACFc,aAAatC,qCAAqC,UAClDyC,kBAAkB;IAE1B,qBACE,KAAChD;QACCiD,QAAQ;QACRC,MAAMvC,eAAe,qBAAqB;YACxCwC,QAAQ;YACRC,SACE,uEACA;QACJ;kBAEA,cAAA,MAACjD;YAAMkD,SAAS;;8BACd,KAACjD;oBAAWkD,SAAQ;oBAAQC,OAAM;8BAC/B3C;;gBAGFgB,UAAU,0BACT;;sCACE,KAAC3B;4BAAMuD,UAAS;sCACb,6DACC;;sCAEJ,KAACrD;4BAAMsD,WAAU;sCACf,cAAA,KAACvD;gCACCoD,SAAQ;gCACRC,OAAM;gCACNG,UAAUtC;gCACVuC,SAAS,IAAM,KAAK1B,KAAK;0CAExB;;;;qBAILJ,UAAU,+BACZ,KAAC5B;oBAAMuD,UAAS;8BACd,cAAA,MAACrD;wBAAMkD,SAAS;;0CACd,KAACjD;gCAAWkD,SAAQ;0CACjB5B,CAAAA,2BAAAA,QAASkC,aAAa,IACnB,CAAC,UAAU,EAAElC,QAAQkC,aAAa,EAAE,GACpC;;0CAEN,KAACxD;gCAAWkD,SAAQ;gCAAUC,OAAM;0CACjCR,cACG,CAAC,uBAAuB,EAAEA,YAAY,CAAC,CAAC,GACxC,CAAC,kBAAkB,EAAExC,mCAAmC,yBAAyB,CAAC;;;;mCAK5F,KAACJ;oBAAMsD,WAAU;8BACf,cAAA,KAACvD;wBACCoD,SAAQ;wBACRC,OAAM;wBACNG,UAAUtC,QAAQS,UAAU;wBAC5B8B,SAAS,IAAM,KAAK1B,KAAK;kCAExB;;;gBAKNX,sBAAQ,KAACrB;oBAAMuD,UAAS;8BAASlC;qBAAiB;;;;AAI3D;AAEA,eAAeT,2BAA0B"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-verification-request.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\nimport {\n timestampMs,\n VERIFICATION_DECLINE_COOLDOWN_DAYS,\n verificationRequestBlock,\n type VerifiableListing,\n} from '../model/listing-verification'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface ListingVerificationRequestProps {\n listingId: string\n listing: VerifiableListing | null | undefined\n /** The org the viewer is acting as. */\n viewerOrgId: string | null | undefined\n user: unknown\n /** Verified is a plugin-listing claim; other artifact types never ask. */\n isPlugin: boolean\n}\n\n/** What the badge actually claims, said once, in the place someone asks for\n * it — so the request is informed rather than a guess at what it buys.\n *\n * `PLATFORM_BRAND_NAME` and NOT the org's resolved `productName` (AGL-2351).\n * The sentence names WHO DID THE VERIFYING, and that is whoever operates this\n * deployment's marketplace — the reviewers who read the listing. Substituting\n * the viewing org's brand would make a white-label agency's console assert\n * that the agency vetted this publisher, which nobody at the agency did. The\n * marketplace is a platform-relationship surface: one catalog, one review\n * queue, shared by every org on the deployment. On a self-host install the\n * operator's own staff really are the humans in this sentence, and the env var\n * renames them with the rest of the product.\n */\nconst WHAT_VERIFIED_CLAIMS =\n `Verified says a human at ${PLATFORM_BRAND_NAME} confirmed who you are, ` +\n 'and that this listing describes what the code does. It is a claim about ' +\n 'you as a publisher, so it survives a version bump. It is not a security ' +\n 'guarantee, and it does not review the bytes of any particular release — ' +\n 'that is what Reviewed means, and it is earned per version.'\n\n/**\n * The publisher's door to the Verified badge (AGL-1217).\n *\n * Before this there was none: verification was reviewer-initiated only, so a\n * publisher who wanted the badge had no affordance and no way to learn that\n * asking was even possible.\n *\n * Eligibility comes from `verificationRequestBlock` — the same function the\n * server enforces. That sharing is deliberate: a rule the button knows and\n * the server does not is decoration, and a rule the server knows and the\n * button does not is a button that fails with no explanation.\n */\nexport function ListingVerificationRequest(\n props: ListingVerificationRequestProps,\n) {\n const { listingId, listing, viewerOrgId, user, isPlugin } = props\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n // Optimistically reflects the action just taken, so the card does not sit\n // unchanged while the listing snapshot catches up.\n const [justDid, setJustDid] = useState<'pending' | 'withdrawn' | null>(null)\n\n const request = listing?.verificationRequest\n const state = justDid ?? request?.state\n const block = verificationRequestBlock({\n listing,\n viewerOrgId,\n nowMs: Date.now(),\n })\n\n const post = async (action: 'request' | 'withdraw') => {\n setBusy(true)\n setError(null)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/verification-request',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ listingId, action }),\n },\n )\n const data = (await response.json().catch(() => ({}))) as {\n error?: string\n }\n if (!response.ok) {\n // The server's own words. It re-checks the same policy, so when it\n // refuses it knows something this component's snapshot did not.\n setError(data.error ?? 'That did not work. Try again.')\n return\n }\n setJustDid(action === 'request' ? 'pending' : 'withdrawn')\n } catch {\n setError('That did not work. Try again.')\n } finally {\n setBusy(false)\n }\n }\n\n if (!isPlugin) return null\n // Not the publisher, or already carrying the badge: nothing to offer. The\n // badge itself is rendered on the listing body, so saying so here would be\n // repeating it in a card about asking.\n if (block === 'not-publisher' || block === 'already-verified') return null\n // A listing still in the queue has a review card above already explaining\n // where it stands. A second card saying \"and you cannot ask for a badge\n // yet\" adds noise to a publisher who has not asked for anything.\n if (block === 'not-listed' && state !== 'pending') return null\n\n const declinedAt = timestampMs(request?.decidedAt)\n const canAskAgain =\n declinedAt === null\n ? null\n : new Date(\n declinedAt + VERIFICATION_DECLINE_COOLDOWN_DAYS * 86_400_000,\n ).toLocaleDateString()\n\n return (\n <CardDisplay\n header={'Verified publisher'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#asking-to-be-verified',\n excerpt:\n 'When you can ask, what happens while it waits, and what a decline ' +\n 'does and does not mean.',\n })}\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {WHAT_VERIFIED_CLAIMS}\n </Typography>\n\n {state === 'pending' ? (\n <>\n <Alert severity=\"info\">\n {'Your request is with the review team. Your plugin stays ' +\n 'listed and installable while they look.'}\n </Alert>\n <Stack direction=\"row\">\n <Button\n variant=\"outlined\"\n color=\"inherit\"\n disabled={busy}\n onClick={() => void post('withdraw')}\n >\n {'Withdraw request'}\n </Button>\n </Stack>\n </>\n ) : block === 'cooling-down' ? (\n <Alert severity=\"warning\">\n <Stack spacing={1}>\n <Typography variant=\"body2\">\n {request?.declineReason\n ? `Declined: ${request.declineReason}`\n : 'A previous request was declined.'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {canAskAgain\n ? `You can ask again from ${canAskAgain}.`\n : `You can ask again ${VERIFICATION_DECLINE_COOLDOWN_DAYS} days after the decision.`}\n </Typography>\n </Stack>\n </Alert>\n ) : (\n <Stack direction=\"row\">\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={busy || block !== null}\n onClick={() => void post('request')}\n >\n {'Request verification'}\n </Button>\n </Stack>\n )}\n\n {error ? <Alert severity=\"error\">{error}</Alert> : null}\n </Stack>\n </CardDisplay>\n )\n}\n\nexport default ListingVerificationRequest\n"],"names":["CardDisplay","Alert","Button","Stack","Typography","useState","timestampMs","VERIFICATION_DECLINE_COOLDOWN_DAYS","verificationRequestBlock","PLATFORM_BRAND_NAME","authorizedFetch","pluginDocsHelp","WHAT_VERIFIED_CLAIMS","ListingVerificationRequest","props","listingId","listing","viewerOrgId","user","isPlugin","busy","setBusy","error","setError","justDid","setJustDid","request","verificationRequest","state","block","nowMs","Date","now","post","action","response","method","headers","body","JSON","stringify","data","json","catch","ok","declinedAt","decidedAt","canAskAgain","toLocaleDateString","header","help","anchor","excerpt","spacing","variant","color","severity","direction","disabled","onClick","declineReason"],"mappings":"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"}
@@ -28,7 +28,7 @@ import { MarkdownLiteView } from "@aglyn/aglyn-markdown-editor";
28
28
  import { pluginDocsHelp } from "@aglyn/aglyn";
29
29
  import { PLUGIN_REVIEW_CHECKLIST } from "../model/plugin-review-checklist.js";
30
30
  import { PLUGIN_REJECTION_CATEGORIES, pluginRejectionCategory, rejectionHeadline, rejectionInputError } from "../model/plugin-rejection-categories.js";
31
- import { PUBLISHER_ATTESTATION } from "@aglyn/aglyn/app-utils/publisher-attestation";
31
+ import { PUBLISHER_ATTESTATION } from "../model/publisher-attestation.js";
32
32
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
33
33
  import { reviewStatusMeaning } from "../model/plugin-review-status.js";
34
34
  /** Verifier findings read as a wall of text otherwise; group by severity. */ const SEVERITY_ORDER = [