@aglyn/aglyn 1.0.0-beta.151 → 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.
- package/package.json +10 -10
- package/src/lib/app-utils/crm-csv.d.ts +10 -3
- package/src/lib/app-utils/crm-csv.js +37 -2
- package/src/lib/app-utils/crm-csv.js.map +1 -1
- package/src/lib/app-utils/crm-email-templates.js +15 -0
- package/src/lib/app-utils/crm-email-templates.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +69 -2
- package/src/lib/app-utils/crm.js +126 -1
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- package/src/lib/app-utils/docs-help.generated.js +2 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +30 -16
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/email-starter-policy.js +5 -3
- package/src/lib/app-utils/email-starter-policy.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +11 -0
- package/src/lib/app-utils/notifications.js +11 -1
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.d.ts +34 -0
- package/src/lib/app-utils/plan-entitlements.js +40 -0
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +0 -1
- package/src/lib/app-utils/server.js +0 -1
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +1 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-artifact-inventory.d.ts +145 -0
- package/src/lib/plugin-manager/plugin-artifact-inventory.js +126 -0
- package/src/lib/plugin-manager/plugin-artifact-inventory.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +32 -1
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-lead-conversion.d.ts +67 -0
- package/src/lib/plugin-manager/plugin-lead-conversion.js +93 -0
- package/src/lib/plugin-manager/plugin-lead-conversion.js.map +1 -0
- package/src/lib/plugin-manager/plugin-org-capacity.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-org-capacity.js.map +1 -1
- package/src/lib/plugin-manager/plugin-permissions.d.ts +7 -0
- package/src/lib/plugin-manager/plugin-permissions.js +30 -0
- package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +8 -1
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-trust-signing.d.ts +51 -0
- package/src/lib/plugin-manager/plugin-trust-signing.js +119 -0
- package/src/lib/plugin-manager/plugin-trust-signing.js.map +1 -0
- package/src/lib/app-utils/marketplace-verification.d.ts +0 -95
- package/src/lib/app-utils/marketplace-verification.js +0 -81
- package/src/lib/app-utils/marketplace-verification.js.map +0 -1
- package/src/lib/app-utils/publisher-attestation.d.ts +0 -99
- package/src/lib/app-utils/publisher-attestation.js +0 -158
- package/src/lib/app-utils/publisher-attestation.js.map +0 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /**
|
|
18
|
+
* WHO HOLDS THE PLATFORM'S TRUST KEY (AGL-3080).
|
|
19
|
+
*
|
|
20
|
+
* Realm trust is the strongest grant in the platform: it drops a marketplace
|
|
21
|
+
* bundle into the app realm, where neither the sandbox iframe nor the
|
|
22
|
+
* manifest CSP stands between it and user data. A grant is an Ed25519
|
|
23
|
+
* signature, made with `PLUGIN_TRUST_PRIVATE_KEY`, over the reviewed
|
|
24
|
+
* version's sha256 — and every loader verifies it before a byte executes.
|
|
25
|
+
*
|
|
26
|
+
* ## Why this is a seam rather than a move
|
|
27
|
+
*
|
|
28
|
+
* The queue around it is the MARKETPLACE's: `marketplaceListings` is its
|
|
29
|
+
* collection, "a version must have passed review" is its rule, and the
|
|
30
|
+
* fields the grant writes are its documents. All of that belongs with the
|
|
31
|
+
* plugin, the way the review queue and the report queue already do.
|
|
32
|
+
*
|
|
33
|
+
* The KEY does not. It is deployed to the console and nowhere else — never
|
|
34
|
+
* to a tenant runtime — and this package is published to npm, where a
|
|
35
|
+
* self-hoster generates their own pair. A plugin that read the key directly
|
|
36
|
+
* would put the platform's signing authority inside a package anyone can
|
|
37
|
+
* install, to no benefit: the plugin has nothing to do with it beyond
|
|
38
|
+
* wanting one string signed.
|
|
39
|
+
*
|
|
40
|
+
* So the shell signs, and the marketplace decides what is worth signing.
|
|
41
|
+
* The sibling of `core.site-cache`, and the same direction: the shell
|
|
42
|
+
* offering a capability down.
|
|
43
|
+
*
|
|
44
|
+
* ## ⛔ AN ABSENT SIGNER REFUSES. IT DOES NOT GRANT.
|
|
45
|
+
*
|
|
46
|
+
* This is the opposite failure direction from every other contract here, and
|
|
47
|
+
* it is the one that matters. `core.site-cache` answers `complete: false`
|
|
48
|
+
* and the caller carries on, because a stale cache is survivable. An
|
|
49
|
+
* unsigned realm grant is not survivable: the loaders fail closed on a
|
|
50
|
+
* missing signature, so a "grant" without one produces a version that is
|
|
51
|
+
* marked trusted and cannot load — or, far worse if a loader is ever
|
|
52
|
+
* lenient, one that loads unverified.
|
|
53
|
+
*
|
|
54
|
+
* {@link signPluginTrust} therefore never returns a bare string. It answers
|
|
55
|
+
* `{ signed: false, reason }` when nothing is registered, when the
|
|
56
|
+
* deployment has no key, or when signing throws, and the caller MUST refuse
|
|
57
|
+
* the grant on it rather than writing a partial one.
|
|
58
|
+
*/ import { definePluginServiceContract, registerPluginService, resolvePluginService } from "./plugin-services.js";
|
|
59
|
+
/**
|
|
60
|
+
* One implementation: the app that holds the key. A second would be a second
|
|
61
|
+
* signing authority, and the loaders verify against ONE public key.
|
|
62
|
+
*/ export const PLUGIN_TRUST_SIGNER = definePluginServiceContract('core.plugin-trust-signer', {
|
|
63
|
+
multiple: false
|
|
64
|
+
});
|
|
65
|
+
/** Installs the app's signer. Called once, at server boot. */ export function registerPluginTrustSigner(signer, options) {
|
|
66
|
+
if (typeof (signer == null ? void 0 : signer.sign) !== 'function') {
|
|
67
|
+
throw new Error('a plugin trust signer needs a sign function');
|
|
68
|
+
}
|
|
69
|
+
registerPluginService(PLUGIN_TRUST_SIGNER, signer, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
70
|
+
pluginId: options.pluginId
|
|
71
|
+
} : {}));
|
|
72
|
+
}
|
|
73
|
+
/** Whether this deployment can sign at all — for a surface that says so. */ export function hasPluginTrustSigner() {
|
|
74
|
+
return Boolean(resolvePluginService(PLUGIN_TRUST_SIGNER));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Signs `sha256`, or says why it could not.
|
|
78
|
+
*
|
|
79
|
+
* NEVER THROWS, and never answers a signature it did not make. Read
|
|
80
|
+
* `signed` before reading `signature`; see the module docblock for why a
|
|
81
|
+
* caller must refuse rather than continue.
|
|
82
|
+
*/ export async function signPluginTrust(sha256) {
|
|
83
|
+
const digest = sha256 == null ? void 0 : sha256.trim();
|
|
84
|
+
if (!digest) {
|
|
85
|
+
return {
|
|
86
|
+
signed: false,
|
|
87
|
+
reason: 'No version hash to sign.'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const signer = resolvePluginService(PLUGIN_TRUST_SIGNER);
|
|
91
|
+
if (!signer) {
|
|
92
|
+
console.error('[trust-signer] no implementation is installed, so realm trust cannot ' + 'be granted. The app registers this at boot.');
|
|
93
|
+
return {
|
|
94
|
+
signed: false,
|
|
95
|
+
reason: 'Trust signing is not available on this deployment.'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const result = await signer.sign(digest);
|
|
100
|
+
// A signer that answered `signed` with nothing to show is a bug in the
|
|
101
|
+
// signer, and granting on it would write the trust flag with no
|
|
102
|
+
// signature — the one state the loaders cannot distinguish from tampering.
|
|
103
|
+
if (result.signed && !result.signature) {
|
|
104
|
+
return {
|
|
105
|
+
signed: false,
|
|
106
|
+
reason: 'Trust signing returned no signature.'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('[trust-signer] signing failed', error);
|
|
112
|
+
return {
|
|
113
|
+
signed: false,
|
|
114
|
+
reason: 'Trust signing failed.'
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=plugin-trust-signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-trust-signing.ts"],"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/**\n * WHO HOLDS THE PLATFORM'S TRUST KEY (AGL-3080).\n *\n * Realm trust is the strongest grant in the platform: it drops a marketplace\n * bundle into the app realm, where neither the sandbox iframe nor the\n * manifest CSP stands between it and user data. A grant is an Ed25519\n * signature, made with `PLUGIN_TRUST_PRIVATE_KEY`, over the reviewed\n * version's sha256 — and every loader verifies it before a byte executes.\n *\n * ## Why this is a seam rather than a move\n *\n * The queue around it is the MARKETPLACE's: `marketplaceListings` is its\n * collection, \"a version must have passed review\" is its rule, and the\n * fields the grant writes are its documents. All of that belongs with the\n * plugin, the way the review queue and the report queue already do.\n *\n * The KEY does not. It is deployed to the console and nowhere else — never\n * to a tenant runtime — and this package is published to npm, where a\n * self-hoster generates their own pair. A plugin that read the key directly\n * would put the platform's signing authority inside a package anyone can\n * install, to no benefit: the plugin has nothing to do with it beyond\n * wanting one string signed.\n *\n * So the shell signs, and the marketplace decides what is worth signing.\n * The sibling of `core.site-cache`, and the same direction: the shell\n * offering a capability down.\n *\n * ## ⛔ AN ABSENT SIGNER REFUSES. IT DOES NOT GRANT.\n *\n * This is the opposite failure direction from every other contract here, and\n * it is the one that matters. `core.site-cache` answers `complete: false`\n * and the caller carries on, because a stale cache is survivable. An\n * unsigned realm grant is not survivable: the loaders fail closed on a\n * missing signature, so a \"grant\" without one produces a version that is\n * marked trusted and cannot load — or, far worse if a loader is ever\n * lenient, one that loads unverified.\n *\n * {@link signPluginTrust} therefore never returns a bare string. It answers\n * `{ signed: false, reason }` when nothing is registered, when the\n * deployment has no key, or when signing throws, and the caller MUST refuse\n * the grant on it rather than writing a partial one.\n */\n\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginService,\n} from './plugin-services'\n\nexport interface PluginTrustSignature {\n /** Base64 Ed25519 signature over the sha256 hex. Absent unless `signed`. */\n signature?: string\n /**\n * Whether a signature was produced. FALSE when nothing is registered, the\n * deployment holds no key, or signing failed — and a caller must refuse\n * the grant, never write one without a signature.\n */\n signed: boolean\n /** Why not, in the operator's terms, when `signed` is false. */\n reason?: string\n}\n\nexport interface PluginTrustSigner {\n /** Signs a version's sha256 hex with the platform's Ed25519 key. */\n sign(sha256: string): Promise<PluginTrustSignature>\n}\n\n/**\n * One implementation: the app that holds the key. A second would be a second\n * signing authority, and the loaders verify against ONE public key.\n */\nexport const PLUGIN_TRUST_SIGNER =\n definePluginServiceContract<PluginTrustSigner>('core.plugin-trust-signer', {\n multiple: false,\n })\n\n/** Installs the app's signer. Called once, at server boot. */\nexport function registerPluginTrustSigner(\n signer: PluginTrustSigner,\n options?: { pluginId?: string },\n): void {\n if (typeof signer?.sign !== 'function') {\n throw new Error('a plugin trust signer needs a sign function')\n }\n registerPluginService(PLUGIN_TRUST_SIGNER, signer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** Whether this deployment can sign at all — for a surface that says so. */\nexport function hasPluginTrustSigner(): boolean {\n return Boolean(resolvePluginService(PLUGIN_TRUST_SIGNER))\n}\n\n/**\n * Signs `sha256`, or says why it could not.\n *\n * NEVER THROWS, and never answers a signature it did not make. Read\n * `signed` before reading `signature`; see the module docblock for why a\n * caller must refuse rather than continue.\n */\nexport async function signPluginTrust(\n sha256: string,\n): Promise<PluginTrustSignature> {\n const digest = sha256?.trim()\n if (!digest) {\n return { signed: false, reason: 'No version hash to sign.' }\n }\n const signer = resolvePluginService(PLUGIN_TRUST_SIGNER)\n if (!signer) {\n console.error(\n '[trust-signer] no implementation is installed, so realm trust cannot ' +\n 'be granted. The app registers this at boot.',\n )\n return {\n signed: false,\n reason: 'Trust signing is not available on this deployment.',\n }\n }\n try {\n const result = await signer.sign(digest)\n // A signer that answered `signed` with nothing to show is a bug in the\n // signer, and granting on it would write the trust flag with no\n // signature — the one state the loaders cannot distinguish from tampering.\n if (result.signed && !result.signature) {\n return {\n signed: false,\n reason: 'Trust signing returned no signature.',\n }\n }\n return result\n } catch (error) {\n console.error('[trust-signer] signing failed', error)\n return { signed: false, reason: 'Trust signing failed.' }\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginService","PLUGIN_TRUST_SIGNER","multiple","registerPluginTrustSigner","signer","options","sign","Error","pluginId","hasPluginTrustSigner","Boolean","signPluginTrust","sha256","digest","trim","signed","reason","console","error","result","signature"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,oBAAoB,QACf,uBAAmB;AAoB1B;;;CAGC,GACD,OAAO,MAAMC,sBACXH,4BAA+C,4BAA4B;IACzEI,UAAU;AACZ,GAAE;AAEJ,4DAA4D,GAC5D,OAAO,SAASC,0BACdC,MAAyB,EACzBC,OAA+B;IAE/B,IAAI,QAAOD,0BAAAA,OAAQE,IAAI,MAAK,YAAY;QACtC,MAAM,IAAIC,MAAM;IAClB;IACAR,sBAAsBE,qBAAqBG,QAAQ,aAC7CC,CAAAA,2BAAAA,QAASG,QAAQ,IAAG;QAAEA,UAAUH,QAAQG,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,0EAA0E,GAC1E,OAAO,SAASC;IACd,OAAOC,QAAQV,qBAAqBC;AACtC;AAEA;;;;;;CAMC,GACD,OAAO,eAAeU,gBACpBC,MAAc;IAEd,MAAMC,SAASD,0BAAAA,OAAQE,IAAI;IAC3B,IAAI,CAACD,QAAQ;QACX,OAAO;YAAEE,QAAQ;YAAOC,QAAQ;QAA2B;IAC7D;IACA,MAAMZ,SAASJ,qBAAqBC;IACpC,IAAI,CAACG,QAAQ;QACXa,QAAQC,KAAK,CACX,0EACE;QAEJ,OAAO;YACLH,QAAQ;YACRC,QAAQ;QACV;IACF;IACA,IAAI;QACF,MAAMG,SAAS,MAAMf,OAAOE,IAAI,CAACO;QACjC,uEAAuE;QACvE,gEAAgE;QAChE,2EAA2E;QAC3E,IAAIM,OAAOJ,MAAM,IAAI,CAACI,OAAOC,SAAS,EAAE;YACtC,OAAO;gBACLL,QAAQ;gBACRC,QAAQ;YACV;QACF;QACA,OAAOG;IACT,EAAE,OAAOD,OAAO;QACdD,QAAQC,KAAK,CAAC,iCAAiCA;QAC/C,OAAO;YAAEH,QAAQ;YAAOC,QAAQ;QAAwB;IAC1D;AACF"}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Who may ask for the Verified badge, and when (AGL-1217).
|
|
19
|
-
*
|
|
20
|
-
* In core rather than beside the rest of the marketplace model for the same
|
|
21
|
-
* reason `marketplace-provenance` is (AGL-1016): the console's staff review
|
|
22
|
-
* route needs this policy, and `scope:app` may not depend on `aglyn:addons`.
|
|
23
|
-
* The marketplace model re-exports everything here, so publishing code keeps
|
|
24
|
-
* importing it from one place.
|
|
25
|
-
*
|
|
26
|
-
* A leaf module with no imports — it is reached from the browser, from React
|
|
27
|
-
* Server Components and from API routes, and every barrel that could carry it
|
|
28
|
-
* is illegal in at least one of those.
|
|
29
|
-
*/
|
|
30
|
-
export type VerificationRequestState = 'pending' | 'granted' | 'declined' | 'withdrawn';
|
|
31
|
-
/**
|
|
32
|
-
* Firestore timestamps arrive in two shapes: an SDK `Timestamp` carrying
|
|
33
|
-
* `toMillis()`, and the plain `{seconds}` object a serialized doc decays into.
|
|
34
|
-
* Policy has to read both or it silently scores a real date as absent.
|
|
35
|
-
*/
|
|
36
|
-
export type TimestampLike = {
|
|
37
|
-
toMillis?: () => number;
|
|
38
|
-
seconds?: number;
|
|
39
|
-
} | null | undefined;
|
|
40
|
-
/** Milliseconds for either timestamp shape; `null` when there is no date. */
|
|
41
|
-
export declare function timestampMs(value: TimestampLike): number | null;
|
|
42
|
-
/**
|
|
43
|
-
* A publisher's ask for the Verified badge.
|
|
44
|
-
*
|
|
45
|
-
* Deliberately NOT a member of `ListingReviewStatus`. That union's
|
|
46
|
-
* consequential axis is live vs. not live — `listed` is what makes a plugin
|
|
47
|
-
* installable by every workspace — and a listing can be live *and* awaiting a
|
|
48
|
-
* verification decision at the same time. A `verification_requested` member
|
|
49
|
-
* would have to answer "is this live?" and both answers are wrong.
|
|
50
|
-
*
|
|
51
|
-
* A record rather than a `verificationRequestedAt` timestamp because
|
|
52
|
-
* verification may become a paid offering. A timestamp has to be rewritten the
|
|
53
|
-
* day that lands; this absorbs an order reference without a migration.
|
|
54
|
-
*/
|
|
55
|
-
export interface ListingVerificationRequest {
|
|
56
|
-
requestedAt?: TimestampLike;
|
|
57
|
-
/** Publisher uid — a person asked, not "the org asked". */
|
|
58
|
-
requestedBy?: string;
|
|
59
|
-
state: VerificationRequestState;
|
|
60
|
-
decidedAt?: TimestampLike;
|
|
61
|
-
/** Staff uid, or the publisher's own on a withdrawal. */
|
|
62
|
-
decidedBy?: string;
|
|
63
|
-
declineReason?: string;
|
|
64
|
-
/** Room for a payment reference when verification becomes purchasable. */
|
|
65
|
-
orderRef?: string;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* How long a publisher waits after a decline before asking again.
|
|
69
|
-
*
|
|
70
|
-
* Queue hygiene, not punishment: without it a publisher can re-request in a
|
|
71
|
-
* loop and staff lose the signal that a request means something changed.
|
|
72
|
-
*/
|
|
73
|
-
export declare const VERIFICATION_DECLINE_COOLDOWN_DAYS = 30;
|
|
74
|
-
/** Why a publisher may not ask right now — `null` when they may. */
|
|
75
|
-
export type VerificationRequestBlock = 'not-publisher' | 'already-verified' | 'not-listed' | 'already-pending' | 'cooling-down';
|
|
76
|
-
/** The listing fields this policy reads, structurally — core cannot see the
|
|
77
|
-
* marketplace model's `MarketplaceListing`. */
|
|
78
|
-
export interface VerifiableListing {
|
|
79
|
-
profileId?: string;
|
|
80
|
-
reviewStatus?: string;
|
|
81
|
-
verificationRequest?: ListingVerificationRequest;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* A reason rather than a boolean, so the UI can say which and so the server
|
|
85
|
-
* and the button cannot drift into disagreeing about the rule. Both call this.
|
|
86
|
-
*/
|
|
87
|
-
export declare function verificationRequestBlock(options: {
|
|
88
|
-
listing: VerifiableListing | null | undefined;
|
|
89
|
-
/** The org the viewer is acting as. */
|
|
90
|
-
viewerOrgId: string | null | undefined;
|
|
91
|
-
/** `Date.now()` on the client; server time on the server. */
|
|
92
|
-
nowMs: number;
|
|
93
|
-
}): VerificationRequestBlock | null;
|
|
94
|
-
/** What to tell the publisher for each block reason. */
|
|
95
|
-
export declare const VERIFICATION_BLOCK_MESSAGES: Record<VerificationRequestBlock, string>;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/ /**
|
|
17
|
-
* Who may ask for the Verified badge, and when (AGL-1217).
|
|
18
|
-
*
|
|
19
|
-
* In core rather than beside the rest of the marketplace model for the same
|
|
20
|
-
* reason `marketplace-provenance` is (AGL-1016): the console's staff review
|
|
21
|
-
* route needs this policy, and `scope:app` may not depend on `aglyn:addons`.
|
|
22
|
-
* The marketplace model re-exports everything here, so publishing code keeps
|
|
23
|
-
* importing it from one place.
|
|
24
|
-
*
|
|
25
|
-
* A leaf module with no imports — it is reached from the browser, from React
|
|
26
|
-
* Server Components and from API routes, and every barrel that could carry it
|
|
27
|
-
* is illegal in at least one of those.
|
|
28
|
-
*/ /** Milliseconds for either timestamp shape; `null` when there is no date. */ export function timestampMs(value) {
|
|
29
|
-
if (!value) return null;
|
|
30
|
-
if (typeof value.toMillis === 'function') {
|
|
31
|
-
const ms = value.toMillis();
|
|
32
|
-
return Number.isFinite(ms) ? ms : null;
|
|
33
|
-
}
|
|
34
|
-
if (typeof value.seconds === 'number') return value.seconds * 1000;
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* How long a publisher waits after a decline before asking again.
|
|
39
|
-
*
|
|
40
|
-
* Queue hygiene, not punishment: without it a publisher can re-request in a
|
|
41
|
-
* loop and staff lose the signal that a request means something changed.
|
|
42
|
-
*/ export const VERIFICATION_DECLINE_COOLDOWN_DAYS = 30;
|
|
43
|
-
/**
|
|
44
|
-
* A reason rather than a boolean, so the UI can say which and so the server
|
|
45
|
-
* and the button cannot drift into disagreeing about the rule. Both call this.
|
|
46
|
-
*/ export function verificationRequestBlock(options) {
|
|
47
|
-
const { listing, viewerOrgId, nowMs } = options;
|
|
48
|
-
if (!listing || !viewerOrgId || listing.profileId !== viewerOrgId) {
|
|
49
|
-
return 'not-publisher';
|
|
50
|
-
}
|
|
51
|
-
if (listing.reviewStatus === 'verified') return 'already-verified';
|
|
52
|
-
// Only a LIVE listing can be verified. Asking while `submitted`, `in_review`
|
|
53
|
-
// or `rejected` is asking for a badge on something customers cannot install,
|
|
54
|
-
// and it would put verification requests into a queue that already has a
|
|
55
|
-
// different reason to look at the listing.
|
|
56
|
-
//
|
|
57
|
-
// A legacy listing with NO status is treated as `listed` elsewhere, but not
|
|
58
|
-
// here: verification is a deliberate act, and an absent field is not enough
|
|
59
|
-
// to hang a badge request on.
|
|
60
|
-
if (listing.reviewStatus !== 'listed') return 'not-listed';
|
|
61
|
-
const request = listing.verificationRequest;
|
|
62
|
-
if ((request == null ? void 0 : request.state) === 'pending') return 'already-pending';
|
|
63
|
-
if ((request == null ? void 0 : request.state) === 'declined') {
|
|
64
|
-
const decidedAt = timestampMs(request.decidedAt);
|
|
65
|
-
// Fail closed on an undated decline. Assuming it is old enough turns a
|
|
66
|
-
// missing field into a free retry, which is what the cooldown prevents.
|
|
67
|
-
if (decidedAt === null) return 'cooling-down';
|
|
68
|
-
const elapsedDays = (nowMs - decidedAt) / 86400000;
|
|
69
|
-
if (elapsedDays < VERIFICATION_DECLINE_COOLDOWN_DAYS) return 'cooling-down';
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
/** What to tell the publisher for each block reason. */ export const VERIFICATION_BLOCK_MESSAGES = {
|
|
74
|
-
'not-publisher': 'Only the publishing organization can request verification.',
|
|
75
|
-
'already-verified': 'This listing is already verified.',
|
|
76
|
-
'not-listed': 'Only a listed plugin can be verified. This one is still going through review.',
|
|
77
|
-
'already-pending': 'A verification request is already waiting for a decision.',
|
|
78
|
-
'cooling-down': `Verification was declined recently. You can ask again ${VERIFICATION_DECLINE_COOLDOWN_DAYS} days after the decision.`
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
//# sourceMappingURL=marketplace-verification.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/marketplace-verification.ts"],"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/**\n * Who may ask for the Verified badge, and when (AGL-1217).\n *\n * In core rather than beside the rest of the marketplace model for the same\n * reason `marketplace-provenance` is (AGL-1016): the console's staff review\n * route needs this policy, and `scope:app` may not depend on `aglyn:addons`.\n * The marketplace model re-exports everything here, so publishing code keeps\n * importing it from one place.\n *\n * A leaf module with no imports — it is reached from the browser, from React\n * Server Components and from API routes, and every barrel that could carry it\n * is illegal in at least one of those.\n */\n\nexport type VerificationRequestState =\n | 'pending'\n | 'granted'\n | 'declined'\n | 'withdrawn'\n\n/**\n * Firestore timestamps arrive in two shapes: an SDK `Timestamp` carrying\n * `toMillis()`, and the plain `{seconds}` object a serialized doc decays into.\n * Policy has to read both or it silently scores a real date as absent.\n */\nexport type TimestampLike =\n | { toMillis?: () => number; seconds?: number }\n | null\n | undefined\n\n/** Milliseconds for either timestamp shape; `null` when there is no date. */\nexport function timestampMs(value: TimestampLike): number | null {\n if (!value) return null\n if (typeof value.toMillis === 'function') {\n const ms = value.toMillis()\n return Number.isFinite(ms) ? ms : null\n }\n if (typeof value.seconds === 'number') return value.seconds * 1000\n return null\n}\n\n/**\n * A publisher's ask for the Verified badge.\n *\n * Deliberately NOT a member of `ListingReviewStatus`. That union's\n * consequential axis is live vs. not live — `listed` is what makes a plugin\n * installable by every workspace — and a listing can be live *and* awaiting a\n * verification decision at the same time. A `verification_requested` member\n * would have to answer \"is this live?\" and both answers are wrong.\n *\n * A record rather than a `verificationRequestedAt` timestamp because\n * verification may become a paid offering. A timestamp has to be rewritten the\n * day that lands; this absorbs an order reference without a migration.\n */\nexport interface ListingVerificationRequest {\n requestedAt?: TimestampLike\n /** Publisher uid — a person asked, not \"the org asked\". */\n requestedBy?: string\n state: VerificationRequestState\n decidedAt?: TimestampLike\n /** Staff uid, or the publisher's own on a withdrawal. */\n decidedBy?: string\n declineReason?: string\n /** Room for a payment reference when verification becomes purchasable. */\n orderRef?: string\n}\n\n/**\n * How long a publisher waits after a decline before asking again.\n *\n * Queue hygiene, not punishment: without it a publisher can re-request in a\n * loop and staff lose the signal that a request means something changed.\n */\nexport const VERIFICATION_DECLINE_COOLDOWN_DAYS = 30\n\n/** Why a publisher may not ask right now — `null` when they may. */\nexport type VerificationRequestBlock =\n | 'not-publisher'\n | 'already-verified'\n | 'not-listed'\n | 'already-pending'\n | 'cooling-down'\n\n/** The listing fields this policy reads, structurally — core cannot see the\n * marketplace model's `MarketplaceListing`. */\nexport interface VerifiableListing {\n profileId?: string\n reviewStatus?: string\n verificationRequest?: ListingVerificationRequest\n}\n\n/**\n * A reason rather than a boolean, so the UI can say which and so the server\n * and the button cannot drift into disagreeing about the rule. Both call this.\n */\nexport function verificationRequestBlock(options: {\n listing: VerifiableListing | null | undefined\n /** The org the viewer is acting as. */\n viewerOrgId: string | null | undefined\n /** `Date.now()` on the client; server time on the server. */\n nowMs: number\n}): VerificationRequestBlock | null {\n const { listing, viewerOrgId, nowMs } = options\n if (!listing || !viewerOrgId || listing.profileId !== viewerOrgId) {\n return 'not-publisher'\n }\n if (listing.reviewStatus === 'verified') return 'already-verified'\n // Only a LIVE listing can be verified. Asking while `submitted`, `in_review`\n // or `rejected` is asking for a badge on something customers cannot install,\n // and it would put verification requests into a queue that already has a\n // different reason to look at the listing.\n //\n // A legacy listing with NO status is treated as `listed` elsewhere, but not\n // here: verification is a deliberate act, and an absent field is not enough\n // to hang a badge request on.\n if (listing.reviewStatus !== 'listed') return 'not-listed'\n\n const request = listing.verificationRequest\n if (request?.state === 'pending') return 'already-pending'\n if (request?.state === 'declined') {\n const decidedAt = timestampMs(request.decidedAt)\n // Fail closed on an undated decline. Assuming it is old enough turns a\n // missing field into a free retry, which is what the cooldown prevents.\n if (decidedAt === null) return 'cooling-down'\n const elapsedDays = (nowMs - decidedAt) / 86_400_000\n if (elapsedDays < VERIFICATION_DECLINE_COOLDOWN_DAYS) return 'cooling-down'\n }\n return null\n}\n\n/** What to tell the publisher for each block reason. */\nexport const VERIFICATION_BLOCK_MESSAGES: Record<\n VerificationRequestBlock,\n string\n> = {\n 'not-publisher': 'Only the publishing organization can request verification.',\n 'already-verified': 'This listing is already verified.',\n 'not-listed':\n 'Only a listed plugin can be verified. This one is still going through review.',\n 'already-pending': 'A verification request is already waiting for a decision.',\n 'cooling-down': `Verification was declined recently. You can ask again ${VERIFICATION_DECLINE_COOLDOWN_DAYS} days after the decision.`,\n}\n"],"names":["timestampMs","value","toMillis","ms","Number","isFinite","seconds","VERIFICATION_DECLINE_COOLDOWN_DAYS","verificationRequestBlock","options","listing","viewerOrgId","nowMs","profileId","reviewStatus","request","verificationRequest","state","decidedAt","elapsedDays","VERIFICATION_BLOCK_MESSAGES"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;CAYC,GAkBD,2EAA2E,GAC3E,OAAO,SAASA,YAAYC,KAAoB;IAC9C,IAAI,CAACA,OAAO,OAAO;IACnB,IAAI,OAAOA,MAAMC,QAAQ,KAAK,YAAY;QACxC,MAAMC,KAAKF,MAAMC,QAAQ;QACzB,OAAOE,OAAOC,QAAQ,CAACF,MAAMA,KAAK;IACpC;IACA,IAAI,OAAOF,MAAMK,OAAO,KAAK,UAAU,OAAOL,MAAMK,OAAO,GAAG;IAC9D,OAAO;AACT;AA4BA;;;;;CAKC,GACD,OAAO,MAAMC,qCAAqC,GAAE;AAkBpD;;;CAGC,GACD,OAAO,SAASC,yBAAyBC,OAMxC;IACC,MAAM,EAAEC,OAAO,EAAEC,WAAW,EAAEC,KAAK,EAAE,GAAGH;IACxC,IAAI,CAACC,WAAW,CAACC,eAAeD,QAAQG,SAAS,KAAKF,aAAa;QACjE,OAAO;IACT;IACA,IAAID,QAAQI,YAAY,KAAK,YAAY,OAAO;IAChD,6EAA6E;IAC7E,6EAA6E;IAC7E,yEAAyE;IACzE,2CAA2C;IAC3C,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,8BAA8B;IAC9B,IAAIJ,QAAQI,YAAY,KAAK,UAAU,OAAO;IAE9C,MAAMC,UAAUL,QAAQM,mBAAmB;IAC3C,IAAID,CAAAA,2BAAAA,QAASE,KAAK,MAAK,WAAW,OAAO;IACzC,IAAIF,CAAAA,2BAAAA,QAASE,KAAK,MAAK,YAAY;QACjC,MAAMC,YAAYlB,YAAYe,QAAQG,SAAS;QAC/C,uEAAuE;QACvE,wEAAwE;QACxE,IAAIA,cAAc,MAAM,OAAO;QAC/B,MAAMC,cAAc,AAACP,CAAAA,QAAQM,SAAQ,IAAK;QAC1C,IAAIC,cAAcZ,oCAAoC,OAAO;IAC/D;IACA,OAAO;AACT;AAEA,sDAAsD,GACtD,OAAO,MAAMa,8BAGT;IACF,iBAAiB;IACjB,oBAAoB;IACpB,cACE;IACF,mBAAmB;IACnB,gBAAgB,CAAC,sDAAsD,EAAEb,mCAAmC,yBAAyB,CAAC;AACxI,EAAC"}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
export interface PublisherAttestationItem {
|
|
18
|
-
id: string;
|
|
19
|
-
/** First person — the publisher is the one saying it. */
|
|
20
|
-
label: string;
|
|
21
|
-
/** What they are actually committing to, shown under the item. */
|
|
22
|
-
detail: string;
|
|
23
|
-
/**
|
|
24
|
-
* Only asked when the listing already exists. A first submission has no
|
|
25
|
-
* "since the last version", and asking anyway teaches publishers that
|
|
26
|
-
* some of these boxes are noise.
|
|
27
|
-
*/
|
|
28
|
-
updateOnly?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
* The submitted field this item is a claim ABOUT (AGL-1076).
|
|
31
|
-
*
|
|
32
|
-
* An attestation whose subject is absent is a statement about nothing:
|
|
33
|
-
* `repository` shipped for a while confirming that a repository URL is
|
|
34
|
-
* public and matches, in a form with nowhere to name a repository, on
|
|
35
|
-
* listings whose `repositoryUrl` was empty. A reviewer opened it first and
|
|
36
|
-
* found nothing to open.
|
|
37
|
-
*
|
|
38
|
-
* So a subject makes the tick enforceable: the publish route refuses with
|
|
39
|
-
* the same 428 as a missing tick when the value is not in the request that
|
|
40
|
-
* stores it. Declared here rather than in the route so the form knows to
|
|
41
|
-
* ask for it and the route knows to require it from one place.
|
|
42
|
-
*/
|
|
43
|
-
subject?: {
|
|
44
|
-
/** Key in the publish body, and on the listing/version doc. */
|
|
45
|
-
field: string;
|
|
46
|
-
/** How the field is labelled to the publisher, for the error. */
|
|
47
|
-
label: string;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
export declare const PUBLISHER_ATTESTATION: readonly PublisherAttestationItem[];
|
|
51
|
-
/** A stored attestation: who said it, when, and against which bytes. */
|
|
52
|
-
export interface AttestationEntry {
|
|
53
|
-
by?: string;
|
|
54
|
-
at?: unknown;
|
|
55
|
-
sha256?: string;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* The ids a submission must carry.
|
|
59
|
-
*
|
|
60
|
-
* `isUpdate` is decided server-side from whether a listing already exists,
|
|
61
|
-
* never from a client flag — otherwise "this is my first version" becomes a
|
|
62
|
-
* way to skip an item.
|
|
63
|
-
*/
|
|
64
|
-
export declare function requiredAttestationIds(isUpdate: boolean): string[];
|
|
65
|
-
/**
|
|
66
|
-
* Which required attestations a submission is missing, given the ids the
|
|
67
|
-
* publisher ticked.
|
|
68
|
-
*/
|
|
69
|
-
export declare function missingAttestations(ticked: readonly string[] | null | undefined, isUpdate: boolean): string[];
|
|
70
|
-
/** The fields a submission must carry alongside its ticks (AGL-1076). */
|
|
71
|
-
export declare function attestationSubjects(isUpdate: boolean): Array<{
|
|
72
|
-
id: string;
|
|
73
|
-
field: string;
|
|
74
|
-
label: string;
|
|
75
|
-
}>;
|
|
76
|
-
/**
|
|
77
|
-
* Which required attestations have nothing to be about (AGL-1076).
|
|
78
|
-
*
|
|
79
|
-
* `values` is the submission being stored, never the listing doc: on a first
|
|
80
|
-
* publish there is no listing, and on an update the tick is a statement about
|
|
81
|
-
* what this publish declares, not about whatever the listing said last year.
|
|
82
|
-
*/
|
|
83
|
-
export declare function missingAttestationSubjects(values: Record<string, unknown> | null | undefined, isUpdate: boolean): Array<{
|
|
84
|
-
id: string;
|
|
85
|
-
field: string;
|
|
86
|
-
label: string;
|
|
87
|
-
}>;
|
|
88
|
-
/** Human labels for a set of ids, for an error a publisher can act on. */
|
|
89
|
-
export declare function attestationLabels(ids: readonly string[]): string[];
|
|
90
|
-
/**
|
|
91
|
-
* Which stored attestations count for a given bundle.
|
|
92
|
-
*
|
|
93
|
-
* Keyed to sha256 exactly like the staff checklist (AGL-963) and the
|
|
94
|
-
* verifier verdict (AGL-962): an attestation is a statement about specific
|
|
95
|
-
* bytes, so a republish under the same version string re-asks. Carrying
|
|
96
|
-
* ticks across a rebuild would let a publisher attest to code they then
|
|
97
|
-
* replaced.
|
|
98
|
-
*/
|
|
99
|
-
export declare function attestationsForBytes(attestation: Record<string, AttestationEntry> | null | undefined, sha256: string): string[];
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/ /**
|
|
17
|
-
* What a publisher states about their own bundle before it enters the queue
|
|
18
|
-
* (AGL-969).
|
|
19
|
-
*
|
|
20
|
-
* Deliberately NOT the staff checklist. Those items are judgements *about*
|
|
21
|
-
* the publisher — "publisher is identifiable and contactable" is worthless
|
|
22
|
-
* as self-attestation, because the answer is always yes. Every item here is
|
|
23
|
-
* something only the publisher can know and a reviewer would otherwise have
|
|
24
|
-
* to guess at or chase by email: whether the repo really holds this code,
|
|
25
|
-
* whether they have the right to publish it, whether each declared host is
|
|
26
|
-
* actually reached.
|
|
27
|
-
*
|
|
28
|
-
* Two things come out of this. Most review round-trips are questions the
|
|
29
|
-
* publisher could have answered at submit time, so the queue moves faster.
|
|
30
|
-
* And a false attestation is a timestamped statement by a named publisher
|
|
31
|
-
* against specific bytes, which makes takedown a documented consequence
|
|
32
|
-
* rather than a judgement call.
|
|
33
|
-
*
|
|
34
|
-
* This module is shared on purpose: the publish API is the enforcement
|
|
35
|
-
* point, the upload dialog renders the same list, and the review page shows
|
|
36
|
-
* staff what was claimed. Three copies of these strings would drift, and a
|
|
37
|
-
* drifted attestation is one nobody can be held to.
|
|
38
|
-
*/ import { PLATFORM_BRAND_NAME } from "./platform-brand.js";
|
|
39
|
-
export const PUBLISHER_ATTESTATION = [
|
|
40
|
-
{
|
|
41
|
-
id: 'repository',
|
|
42
|
-
label: 'The repository URL is public and contains the source for this bundle',
|
|
43
|
-
detail: 'A reviewer opens it first. A repo that 404s, is private, or holds something unrelated is the cheapest reason a submission gets sent back.',
|
|
44
|
-
subject: {
|
|
45
|
-
field: 'repositoryUrl',
|
|
46
|
-
label: 'Repository URL'
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: 'license',
|
|
51
|
-
label: 'A license is included and I have the right to publish this code',
|
|
52
|
-
detail: 'Missing or incompatible licensing is a legal problem for every workspace that installs it, not only for us.'
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
id: 'data-disclosure',
|
|
56
|
-
label: 'The README documents what data this plugin reads, stores or sends, and where it goes',
|
|
57
|
-
detail: 'Anything the plugin does with member or page data belongs in the README before it is installed, not in a support thread afterwards.'
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
id: 'network-hosts',
|
|
61
|
-
label: 'Every declared network host is required — I have removed the ones I do not use',
|
|
62
|
-
detail: 'Declared hosts become this plugin’s CSP connect-src. An allowlist wider than the plugin needs is the single most common thing review sends back, and it widens the blast radius if the bundle is ever compromised.'
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: 'tested',
|
|
66
|
-
label: 'I have tested this version on a site I control',
|
|
67
|
-
detail: 'These exact bytes, not an earlier build. Static review cannot tell a reviewer what your plugin does at runtime.'
|
|
68
|
-
},
|
|
69
|
-
/**
|
|
70
|
-
* Publisher Agreement §3(h), which binds "Neither the Artifact nor its
|
|
71
|
-
* listing" — so it already reached the element names and descriptions a
|
|
72
|
-
* plugin declares in its manifest, which ship INSIDE the artifact. This
|
|
73
|
-
* sentence did not, and a publisher can only be held to what they were
|
|
74
|
-
* asked (AGL-2486).
|
|
75
|
-
*
|
|
76
|
-
* Written as a CAPABILITY — any text you supply that renders in, or ranks
|
|
77
|
-
* within, the console — rather than as a list of today's fields. The list
|
|
78
|
-
* was already wrong once: `tags` and `keywords` reached the picker's search
|
|
79
|
-
* ranking days after `description` reached its detail panel, and a sentence
|
|
80
|
-
* enumerating fields goes stale the next time one is added. Ranking is
|
|
81
|
-
* named explicitly because it is the non-obvious half: a search term never
|
|
82
|
-
* appears on screen, yet it decides which element a customer is SHOWN.
|
|
83
|
-
*
|
|
84
|
-
* The id stays `listing-conduct` deliberately. A stored tick is keyed by
|
|
85
|
-
* id (see {@link attestationsForBytes}), so renaming it to something truer
|
|
86
|
-
* to the widened wording would orphan every attestation on file as an
|
|
87
|
-
* unrecognised id, and re-ask for bytes already attested.
|
|
88
|
-
*/ {
|
|
89
|
-
id: 'listing-conduct',
|
|
90
|
-
label: `Neither my listing nor any text on my elements disparages ${PLATFORM_BRAND_NAME}, quotes what its plans cost, or sells a way around one`,
|
|
91
|
-
detail: `Saying which ${PLATFORM_BRAND_NAME} plans or features your plugin needs, or works with, is fine and useful. Editorialising about ${PLATFORM_BRAND_NAME}’s prices or quotas, stating or implying what a plan contains, or pitching this version as a way to avoid a plan or an upgrade is not. This covers any text you supply that renders in — or ranks within — the console, not only the listing: an element’s name, description, tags, search keywords and attribute labels all ship inside your bundle, and a customer meets them in their own element picker. Search terms count even though nobody reads them, because they decide which element gets shown.`
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
id: 'changelog',
|
|
95
|
-
label: 'The changelog describes what changed since the last version',
|
|
96
|
-
detail: 'An update is where something slips through — the first version gets read closely, the fourth rarely does. Saying what moved is what makes reviewing a diff possible.',
|
|
97
|
-
updateOnly: true
|
|
98
|
-
}
|
|
99
|
-
];
|
|
100
|
-
/**
|
|
101
|
-
* The ids a submission must carry.
|
|
102
|
-
*
|
|
103
|
-
* `isUpdate` is decided server-side from whether a listing already exists,
|
|
104
|
-
* never from a client flag — otherwise "this is my first version" becomes a
|
|
105
|
-
* way to skip an item.
|
|
106
|
-
*/ export function requiredAttestationIds(isUpdate) {
|
|
107
|
-
return PUBLISHER_ATTESTATION.filter((item)=>isUpdate || !item.updateOnly).map((item)=>item.id);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Which required attestations a submission is missing, given the ids the
|
|
111
|
-
* publisher ticked.
|
|
112
|
-
*/ export function missingAttestations(ticked, isUpdate) {
|
|
113
|
-
const given = new Set(ticked != null ? ticked : []);
|
|
114
|
-
return requiredAttestationIds(isUpdate).filter((id)=>!given.has(id));
|
|
115
|
-
}
|
|
116
|
-
/** The fields a submission must carry alongside its ticks (AGL-1076). */ export function attestationSubjects(isUpdate) {
|
|
117
|
-
return PUBLISHER_ATTESTATION.filter((item)=>item.subject && (isUpdate || !item.updateOnly)).map((item)=>{
|
|
118
|
-
var _ref, _ref1;
|
|
119
|
-
var _item_subject, _item_subject1;
|
|
120
|
-
return {
|
|
121
|
-
id: item.id,
|
|
122
|
-
field: (_ref = (_item_subject = item.subject) == null ? void 0 : _item_subject.field) != null ? _ref : '',
|
|
123
|
-
label: (_ref1 = (_item_subject1 = item.subject) == null ? void 0 : _item_subject1.label) != null ? _ref1 : ''
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Which required attestations have nothing to be about (AGL-1076).
|
|
129
|
-
*
|
|
130
|
-
* `values` is the submission being stored, never the listing doc: on a first
|
|
131
|
-
* publish there is no listing, and on an update the tick is a statement about
|
|
132
|
-
* what this publish declares, not about whatever the listing said last year.
|
|
133
|
-
*/ export function missingAttestationSubjects(values, isUpdate) {
|
|
134
|
-
return attestationSubjects(isUpdate).filter((subject)=>{
|
|
135
|
-
const value = (values != null ? values : {})[subject.field];
|
|
136
|
-
return typeof value !== 'string' || !value.trim();
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
/** Human labels for a set of ids, for an error a publisher can act on. */ export function attestationLabels(ids) {
|
|
140
|
-
return ids.map((id)=>{
|
|
141
|
-
var _PUBLISHER_ATTESTATION_find;
|
|
142
|
-
return (_PUBLISHER_ATTESTATION_find = PUBLISHER_ATTESTATION.find((item)=>item.id === id)) == null ? void 0 : _PUBLISHER_ATTESTATION_find.label;
|
|
143
|
-
}).filter((label)=>Boolean(label));
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Which stored attestations count for a given bundle.
|
|
147
|
-
*
|
|
148
|
-
* Keyed to sha256 exactly like the staff checklist (AGL-963) and the
|
|
149
|
-
* verifier verdict (AGL-962): an attestation is a statement about specific
|
|
150
|
-
* bytes, so a republish under the same version string re-asks. Carrying
|
|
151
|
-
* ticks across a rebuild would let a publisher attest to code they then
|
|
152
|
-
* replaced.
|
|
153
|
-
*/ export function attestationsForBytes(attestation, sha256) {
|
|
154
|
-
if (!sha256) return [];
|
|
155
|
-
return Object.entries(attestation != null ? attestation : {}).filter(([, entry])=>(entry == null ? void 0 : entry.sha256) === sha256).map(([id])=>id);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
//# sourceMappingURL=publisher-attestation.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/publisher-attestation.ts"],"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/**\n * What a publisher states about their own bundle before it enters the queue\n * (AGL-969).\n *\n * Deliberately NOT the staff checklist. Those items are judgements *about*\n * the publisher — \"publisher is identifiable and contactable\" is worthless\n * as self-attestation, because the answer is always yes. Every item here is\n * something only the publisher can know and a reviewer would otherwise have\n * to guess at or chase by email: whether the repo really holds this code,\n * whether they have the right to publish it, whether each declared host is\n * actually reached.\n *\n * Two things come out of this. Most review round-trips are questions the\n * publisher could have answered at submit time, so the queue moves faster.\n * And a false attestation is a timestamped statement by a named publisher\n * against specific bytes, which makes takedown a documented consequence\n * rather than a judgement call.\n *\n * This module is shared on purpose: the publish API is the enforcement\n * point, the upload dialog renders the same list, and the review page shows\n * staff what was claimed. Three copies of these strings would drift, and a\n * drifted attestation is one nobody can be held to.\n */\nimport { PLATFORM_BRAND_NAME } from './platform-brand'\n\nexport interface PublisherAttestationItem {\n id: string\n /** First person — the publisher is the one saying it. */\n label: string\n /** What they are actually committing to, shown under the item. */\n detail: string\n /**\n * Only asked when the listing already exists. A first submission has no\n * \"since the last version\", and asking anyway teaches publishers that\n * some of these boxes are noise.\n */\n updateOnly?: boolean\n /**\n * The submitted field this item is a claim ABOUT (AGL-1076).\n *\n * An attestation whose subject is absent is a statement about nothing:\n * `repository` shipped for a while confirming that a repository URL is\n * public and matches, in a form with nowhere to name a repository, on\n * listings whose `repositoryUrl` was empty. A reviewer opened it first and\n * found nothing to open.\n *\n * So a subject makes the tick enforceable: the publish route refuses with\n * the same 428 as a missing tick when the value is not in the request that\n * stores it. Declared here rather than in the route so the form knows to\n * ask for it and the route knows to require it from one place.\n */\n subject?: {\n /** Key in the publish body, and on the listing/version doc. */\n field: string\n /** How the field is labelled to the publisher, for the error. */\n label: string\n }\n}\n\nexport const PUBLISHER_ATTESTATION: readonly PublisherAttestationItem[] = [\n {\n id: 'repository',\n label: 'The repository URL is public and contains the source for this bundle',\n detail:\n 'A reviewer opens it first. A repo that 404s, is private, or holds something unrelated is the cheapest reason a submission gets sent back.',\n subject: { field: 'repositoryUrl', label: 'Repository URL' },\n },\n {\n id: 'license',\n label: 'A license is included and I have the right to publish this code',\n detail:\n 'Missing or incompatible licensing is a legal problem for every workspace that installs it, not only for us.',\n },\n {\n id: 'data-disclosure',\n label:\n 'The README documents what data this plugin reads, stores or sends, and where it goes',\n detail:\n 'Anything the plugin does with member or page data belongs in the README before it is installed, not in a support thread afterwards.',\n },\n {\n id: 'network-hosts',\n label:\n 'Every declared network host is required — I have removed the ones I do not use',\n detail:\n 'Declared hosts become this plugin’s CSP connect-src. An allowlist wider than the plugin needs is the single most common thing review sends back, and it widens the blast radius if the bundle is ever compromised.',\n },\n {\n id: 'tested',\n label: 'I have tested this version on a site I control',\n detail:\n 'These exact bytes, not an earlier build. Static review cannot tell a reviewer what your plugin does at runtime.',\n },\n /**\n * Publisher Agreement §3(h), which binds \"Neither the Artifact nor its\n * listing\" — so it already reached the element names and descriptions a\n * plugin declares in its manifest, which ship INSIDE the artifact. This\n * sentence did not, and a publisher can only be held to what they were\n * asked (AGL-2486).\n *\n * Written as a CAPABILITY — any text you supply that renders in, or ranks\n * within, the console — rather than as a list of today's fields. The list\n * was already wrong once: `tags` and `keywords` reached the picker's search\n * ranking days after `description` reached its detail panel, and a sentence\n * enumerating fields goes stale the next time one is added. Ranking is\n * named explicitly because it is the non-obvious half: a search term never\n * appears on screen, yet it decides which element a customer is SHOWN.\n *\n * The id stays `listing-conduct` deliberately. A stored tick is keyed by\n * id (see {@link attestationsForBytes}), so renaming it to something truer\n * to the widened wording would orphan every attestation on file as an\n * unrecognised id, and re-ask for bytes already attested.\n */\n {\n id: 'listing-conduct',\n label:\n `Neither my listing nor any text on my elements disparages ${PLATFORM_BRAND_NAME}, quotes what its plans cost, or sells a way around one`,\n detail:\n `Saying which ${PLATFORM_BRAND_NAME} plans or features your plugin needs, or works with, is fine and useful. Editorialising about ${PLATFORM_BRAND_NAME}’s prices or quotas, stating or implying what a plan contains, or pitching this version as a way to avoid a plan or an upgrade is not. This covers any text you supply that renders in — or ranks within — the console, not only the listing: an element’s name, description, tags, search keywords and attribute labels all ship inside your bundle, and a customer meets them in their own element picker. Search terms count even though nobody reads them, because they decide which element gets shown.`,\n },\n {\n id: 'changelog',\n label: 'The changelog describes what changed since the last version',\n detail:\n 'An update is where something slips through — the first version gets read closely, the fourth rarely does. Saying what moved is what makes reviewing a diff possible.',\n updateOnly: true,\n },\n]\n\n/** A stored attestation: who said it, when, and against which bytes. */\nexport interface AttestationEntry {\n by?: string\n at?: unknown\n sha256?: string\n}\n\n/**\n * The ids a submission must carry.\n *\n * `isUpdate` is decided server-side from whether a listing already exists,\n * never from a client flag — otherwise \"this is my first version\" becomes a\n * way to skip an item.\n */\nexport function requiredAttestationIds(isUpdate: boolean): string[] {\n return PUBLISHER_ATTESTATION.filter(\n (item) => isUpdate || !item.updateOnly,\n ).map((item) => item.id)\n}\n\n/**\n * Which required attestations a submission is missing, given the ids the\n * publisher ticked.\n */\nexport function missingAttestations(\n ticked: readonly string[] | null | undefined,\n isUpdate: boolean,\n): string[] {\n const given = new Set(ticked ?? [])\n return requiredAttestationIds(isUpdate).filter((id) => !given.has(id))\n}\n\n/** The fields a submission must carry alongside its ticks (AGL-1076). */\nexport function attestationSubjects(\n isUpdate: boolean,\n): Array<{ id: string; field: string; label: string }> {\n return PUBLISHER_ATTESTATION.filter(\n (item) => item.subject && (isUpdate || !item.updateOnly),\n ).map((item) => ({\n id: item.id,\n field: item.subject?.field ?? '',\n label: item.subject?.label ?? '',\n }))\n}\n\n/**\n * Which required attestations have nothing to be about (AGL-1076).\n *\n * `values` is the submission being stored, never the listing doc: on a first\n * publish there is no listing, and on an update the tick is a statement about\n * what this publish declares, not about whatever the listing said last year.\n */\nexport function missingAttestationSubjects(\n values: Record<string, unknown> | null | undefined,\n isUpdate: boolean,\n): Array<{ id: string; field: string; label: string }> {\n return attestationSubjects(isUpdate).filter((subject) => {\n const value = (values ?? {})[subject.field]\n return typeof value !== 'string' || !value.trim()\n })\n}\n\n/** Human labels for a set of ids, for an error a publisher can act on. */\nexport function attestationLabels(ids: readonly string[]): string[] {\n return ids\n .map((id) => PUBLISHER_ATTESTATION.find((item) => item.id === id)?.label)\n .filter((label): label is string => Boolean(label))\n}\n\n/**\n * Which stored attestations count for a given bundle.\n *\n * Keyed to sha256 exactly like the staff checklist (AGL-963) and the\n * verifier verdict (AGL-962): an attestation is a statement about specific\n * bytes, so a republish under the same version string re-asks. Carrying\n * ticks across a rebuild would let a publisher attest to code they then\n * replaced.\n */\nexport function attestationsForBytes(\n attestation: Record<string, AttestationEntry> | null | undefined,\n sha256: string,\n): string[] {\n if (!sha256) return []\n return Object.entries(attestation ?? {})\n .filter(([, entry]) => entry?.sha256 === sha256)\n .map(([id]) => id)\n}\n"],"names":["PLATFORM_BRAND_NAME","PUBLISHER_ATTESTATION","id","label","detail","subject","field","updateOnly","requiredAttestationIds","isUpdate","filter","item","map","missingAttestations","ticked","given","Set","has","attestationSubjects","missingAttestationSubjects","values","value","trim","attestationLabels","ids","find","Boolean","attestationsForBytes","attestation","sha256","Object","entries","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,SAASA,mBAAmB,QAAQ,sBAAkB;AAoCtD,OAAO,MAAMC,wBAA6D;IACxE;QACEC,IAAI;QACJC,OAAO;QACPC,QACE;QACFC,SAAS;YAAEC,OAAO;YAAiBH,OAAO;QAAiB;IAC7D;IACA;QACED,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OACE;QACFC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OACE;QACFC,QACE;IACJ;IACA;QACEF,IAAI;QACJC,OAAO;QACPC,QACE;IACJ;IACA;;;;;;;;;;;;;;;;;;;GAmBC,GACD;QACEF,IAAI;QACJC,OACE,CAAC,0DAA0D,EAAEH,oBAAoB,uDAAuD,CAAC;QAC3II,QACE,CAAC,aAAa,EAAEJ,oBAAoB,8FAA8F,EAAEA,oBAAoB,4eAA4e,CAAC;IACzoB;IACA;QACEE,IAAI;QACJC,OAAO;QACPC,QACE;QACFG,YAAY;IACd;CACD,CAAA;AASD;;;;;;CAMC,GACD,OAAO,SAASC,uBAAuBC,QAAiB;IACtD,OAAOR,sBAAsBS,MAAM,CACjC,CAACC,OAASF,YAAY,CAACE,KAAKJ,UAAU,EACtCK,GAAG,CAAC,CAACD,OAASA,KAAKT,EAAE;AACzB;AAEA;;;CAGC,GACD,OAAO,SAASW,oBACdC,MAA4C,EAC5CL,QAAiB;IAEjB,MAAMM,QAAQ,IAAIC,IAAIF,iBAAAA,SAAU,EAAE;IAClC,OAAON,uBAAuBC,UAAUC,MAAM,CAAC,CAACR,KAAO,CAACa,MAAME,GAAG,CAACf;AACpE;AAEA,uEAAuE,GACvE,OAAO,SAASgB,oBACdT,QAAiB;IAEjB,OAAOR,sBAAsBS,MAAM,CACjC,CAACC,OAASA,KAAKN,OAAO,IAAKI,CAAAA,YAAY,CAACE,KAAKJ,UAAU,AAAD,GACtDK,GAAG,CAAC,CAACD;;YAEEA,eACAA;eAHQ;YACfT,IAAIS,KAAKT,EAAE;YACXI,KAAK,WAAEK,gBAAAA,KAAKN,OAAO,qBAAZM,cAAcL,KAAK,mBAAI;YAC9BH,KAAK,YAAEQ,iBAAAA,KAAKN,OAAO,qBAAZM,eAAcR,KAAK,oBAAI;QAChC;;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAASgB,2BACdC,MAAkD,EAClDX,QAAiB;IAEjB,OAAOS,oBAAoBT,UAAUC,MAAM,CAAC,CAACL;QAC3C,MAAMgB,QAAQ,CAACD,iBAAAA,SAAU,CAAC,EAAE,CAACf,QAAQC,KAAK,CAAC;QAC3C,OAAO,OAAOe,UAAU,YAAY,CAACA,MAAMC,IAAI;IACjD;AACF;AAEA,wEAAwE,GACxE,OAAO,SAASC,kBAAkBC,GAAsB;IACtD,OAAOA,IACJZ,GAAG,CAAC,CAACV;YAAOD;gBAAAA,8BAAAA,sBAAsBwB,IAAI,CAAC,CAACd,OAASA,KAAKT,EAAE,KAAKA,wBAAjDD,4BAAsDE,KAAK;OACvEO,MAAM,CAAC,CAACP,QAA2BuB,QAAQvB;AAChD;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASwB,qBACdC,WAAgE,EAChEC,MAAc;IAEd,IAAI,CAACA,QAAQ,OAAO,EAAE;IACtB,OAAOC,OAAOC,OAAO,CAACH,sBAAAA,cAAe,CAAC,GACnClB,MAAM,CAAC,CAAC,GAAGsB,MAAM,GAAKA,CAAAA,yBAAAA,MAAOH,MAAM,MAAKA,QACxCjB,GAAG,CAAC,CAAC,CAACV,GAAG,GAAKA;AACnB"}
|