From 540997b73769921fc530a433ffea7d2bf220c604 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Thu, 20 Aug 2026 16:47:34 -0400 Subject: [PATCH 1/2] feat(install): tell visitors why their computer will warn them, before they download The engine repo now explains the unsigned builds in three places, and none of them is where someone actually decides to click Download. This is that place. Shown ONLY when the release genuinely carries a platform bundle (`macAsset || winAsset`). The wheels are not signed either, but pip does not warn about them -- a Gatekeeper explanation beside a wheels-only release would be a warning about nothing. Verified in both states: absent on today's v0.10.0, present in all three locales once a .dmg and a .zip appear in the release data. WHAT IT SAYS, AND WHY IN THOSE WORDS. It names the cost. Apple's certificate is $99/yr, there is no cheaper tier and no free open-source tier, and a certificate we made ourselves would do nothing because macOS trusts only Apple-issued ones. A visitor told a build is unsigned with no reason assumes carelessness; a visitor told what it costs and that the project cannot commit to it has been given a fact they can weigh, and it happens to be true. It refuses to end on "click Open Anyway". keel is a program a visitor may hand exchange API keys to, so `gh attestation verify` and SHA256SUMS are part of the note rather than a footnote to it, with a plain instruction not to open the file if either check fails. And it corrects what the warning is NOT. "Damaged" is what macOS sometimes says and it is not what happened; nothing was scanned and nothing was detected. Leaving that uncorrected is how a working download gets deleted. Styled with the notice palette, not an alarm one. The message is "your computer will stop you, here is why, here is how to check what you have" -- a red box would say "something is wrong". Full copy in en/ar/fr, all three revs moved together so no locale is left stale. `astro check` clean (0 errors); build clean at 41 pages. Links to the full page in the engine repo (docs/desktop-install.md) rather than duplicating it, so the two cannot drift. --- src/components/pages/InstallPage.astro | 47 +++++++++ src/i18n/pages/install.ts | 129 ++++++++++++++++++++++++- src/styles/global.css | 45 +++++++++ 3 files changed, 216 insertions(+), 5 deletions(-) diff --git a/src/components/pages/InstallPage.astro b/src/components/pages/InstallPage.astro index 311a87a..bbafe32 100644 --- a/src/components/pages/InstallPage.astro +++ b/src/components/pages/InstallPage.astro @@ -46,6 +46,13 @@ const macAsset = platformAsset(["darwin", "macos", ".dmg", ".pkg"]); const winAsset = platformAsset(["windows", "win32", "win64", ".exe", ".msi", ".zip"]); const cardTargets = [macAsset, winAsset]; +/** + * Whether this release ships anything an operating system will refuse to open. Gates the + * unsigned-build note: the wheels are not signed either, but pip does not warn about them, so a + * Gatekeeper explanation shown beside a wheels-only release would be a warning about nothing. + */ +const hasPlatformBundle = Boolean(macAsset || winAsset); + const venvCommands = (os: "mac" | "win"): string => { const whl = "keel_trader-" + (version ?? "") + "-py3-none-any.whl"; if (os === "mac") { @@ -129,6 +136,46 @@ const venvCommands = (os: "mac" | "win"): string => {
+ { + /* + * Shown ONLY when the release actually carries a platform bundle. Until one exists there + * is nothing for an operating system to warn about, and a note about Gatekeeper beside a + * page that only offers wheels would be a warning about nothing. + */ + hasPlatformBundle && ( +
+

{c.unsigned.title}

+

{c.unsigned.lead}

+

{c.unsigned.reason}

+ +
+
+

{c.unsigned.macosTitle}

+
    {c.unsigned.macosSteps.map((step) =>
  1. {step}
  2. )}
+
+
+

{c.unsigned.windowsTitle}

+
    {c.unsigned.windowsSteps.map((step) =>
  1. {step}
  2. )}
+
+
+ +

{c.unsigned.verifyTitle}

+

{c.unsigned.verifyLead}

+
gh attestation verify <the file you downloaded> --repo CodeGateSoftware/keel
+

{c.unsigned.verifyFail}

+ +

{c.unsigned.notMeaningTitle}

+
    {c.unsigned.notMeaning.map((item) =>
  • {item}
  • )}
+ +

+ + {c.unsigned.more} → + +

+
+ ) + } +
{c.fromReleaseWarning} diff --git a/src/i18n/pages/install.ts b/src/i18n/pages/install.ts index a002325..af219d9 100644 --- a/src/i18n/pages/install.ts +++ b/src/i18n/pages/install.ts @@ -18,6 +18,34 @@ export interface PlatformCardCopy { codeComment: string; } +/** + * The unsigned-build note. Shown only when a release actually carries a platform bundle + * (see InstallPage.astro) — until then there is nothing for an OS to warn about. + * + * Apple notarisation needs a Developer ID certificate at $99/yr; keel cannot currently afford + * it, and there is no cheaper tier, no free open-source tier, and no self-signed substitute + * (macOS trusts only Apple-issued certificates). The page says so in those terms: a visitor told + * a build is unsigned with no reason assumes carelessness, and this is simply what is true. + * + * It also refuses to end on "click Open Anyway". keel is a program a visitor may give exchange + * API keys to, so the verification step is part of the note, not a footnote to it. + */ +export interface UnsignedNoteCopy { + title: string; + lead: string; + reason: string; + macosTitle: string; + macosSteps: string[]; + windowsTitle: string; + windowsSteps: string[]; + verifyTitle: string; + verifyLead: string; + verifyFail: string; + notMeaningTitle: string; + notMeaning: string[]; + more: string; +} + export interface InstallContent { rev: string; title: string; @@ -39,12 +67,13 @@ export interface InstallContent { next: string; }; fromReleaseWarning: string; + unsigned: UnsignedNoteCopy; translatedFromRev?: string; } export const install: LocalizedPage = { en: { - rev: "2026-08-20.2", + rev: "2026-08-20.3", title: "Download keel — macOS & Windows", description: "Download keel for macOS or Windows. Version and links come from GitHub Releases at build time; the five-minute source path is here too.", @@ -83,11 +112,41 @@ export const install: LocalizedPage = { }, fromReleaseWarning: "Never install by bare name. The distribution is keel-trader; the name keel on PyPI belongs to an unrelated project, so pip install keel fetches someone else's package. A build reporting DIRTY or [checkout] is not a release and must not be run against live funds.", + unsigned: { + title: "Your computer will warn you about this download", + lead: "The macOS and Windows builds are not code-signed, so your operating system will refuse the first open and tell you it cannot verify the developer. Nothing is broken, and nothing was detected — your computer simply does not know who wrote the program.", + reason: + "Code signing is a paid certificate. Apple's costs $99 per year, every year, and keel cannot currently afford it. There is no cheaper tier and no free option for open-source projects, and a certificate we made ourselves would do nothing at all, because macOS trusts only certificates Apple issued.", + macosTitle: "macOS", + macosSteps: [ + "Double-click keel. macOS refuses, saying it cannot verify the developer.", + "Open System Settings → Privacy & Security.", + "Scroll to the message about keel and click Open Anyway.", + "You only do this once.", + ], + windowsTitle: "Windows", + windowsSteps: [ + "Run the installer. SmartScreen says \"Windows protected your PC\".", + "Click More info, then Run anyway.", + ], + verifyTitle: "Please check what you downloaded first", + verifyLead: + "We would rather not simply ask you to click past a security warning — keel is a program you may give exchange API keys to. Every release carries proof of where its files came from, which answers the same question a certificate answers: was this built from keel's own source, by keel's own release pipeline? A SHA256SUMS.txt file is attached to every release too.", + verifyFail: + "If either check fails, do not open the file. A failing check means it is not the file we built, and no amount of clicking Open Anyway makes that safe.", + notMeaningTitle: "What this does not mean", + notMeaning: [ + "It does not mean the download is damaged.", + "It does not mean your computer found something wrong. Nothing was scanned and nothing was detected.", + "It does not mean the app behaves differently. A signed and an unsigned build of the same release are the same program.", + ], + more: "Full explanation, including how to avoid this entirely", + }, }, ar: { - rev: "2026-08-20.2", - translatedFromRev: "2026-08-20.2", + rev: "2026-08-20.3", + translatedFromRev: "2026-08-20.3", title: "تنزيل كيل — macOS وWindows", description: "نزّل كيل لـ macOS أو Windows. الإصدار والروابط من GitHub Releases وقت البناء؛ ومسار المصدر في خمس دقائق هنا أيضًا.", @@ -126,11 +185,41 @@ export const install: LocalizedPage = { }, fromReleaseWarning: "لا تثبّت بالاسم المجرد أبدًا. التوزيعة اسمها keel-trader؛ أما اسم keel على PyPI فلمشروعٍ آخر لا علاقة له بنا، فأمر pip install keel يجلب حزمة غيرنا. والبنية التي تُظهر DIRTY أو [checkout] ليست إصدارًا ويُمنع تشغيلها على أموالٍ حيّة.", + unsigned: { + title: "سيحذّرك جهازك من هذا التنزيل", + lead: "بِنى macOS وWindows غير موقّعة رقميًّا، لذا سيرفض نظامك فتحها أول مرّة ويقول إنه لا يستطيع التحقّق من المطوّر. لا شيء معطّل، ولم يُكتشف شيء — جهازك ببساطة لا يعرف من كتب البرنامج.", + reason: + "التوقيع الرقمي شهادةٌ مدفوعة. شهادة Apple تكلّف 99 دولارًا سنويًّا، كلّ سنة، وكيل لا يقدر عليها حاليًّا. ولا توجد فئة أرخص ولا خيارٌ مجانيٌّ لمشاريع المصدر المفتوح، وشهادةٌ نصنعها بأنفسنا لا تفيد شيئًا البتّة، لأن macOS لا يثق إلا بالشهادات الصادرة عن Apple.", + macosTitle: "macOS", + macosSteps: [ + "انقر كيل نقرًا مزدوجًا. سيرفض macOS قائلًا إنه لا يستطيع التحقّق من المطوّر.", + "افتح ‏System Settings ← Privacy & Security.", + "انزل إلى الرسالة الخاصّة بكيل واضغط ‏Open Anyway.", + "تفعل هذا مرّةً واحدة فقط.", + ], + windowsTitle: "Windows", + windowsSteps: [ + "شغّل المثبّت. سيقول ‏SmartScreen‏: «‏Windows protected your PC‏».", + "اضغط ‏More info ثم ‏Run anyway.", + ], + verifyTitle: "من فضلك تحقّق ممّا نزّلته أولًا", + verifyLead: + "لا نحبّ أن نطلب منك مجرّد تجاوز تحذيرٍ أمني — فكيل برنامجٌ قد تعطيه مفاتيح API لمنصّة تداول. كلّ إصدارٍ يحمل إثباتًا لمصدر ملفّاته، وهو يجيب عن السؤال نفسه الذي تجيب عنه الشهادة: هل بُني هذا من مصدر كيل نفسه، عبر خطّ إصدار كيل نفسه؟ ويُرفق بكلّ إصدارٍ ملفّ SHA256SUMS.txt أيضًا.", + verifyFail: + "إن فشل أيٌّ من الفحصين فلا تفتح الملفّ. الفشل يعني أنه ليس الملفّ الذي بنيناه، ولن يجعله الضغط على ‏Open Anyway آمنًا.", + notMeaningTitle: "ما لا يعنيه هذا التحذير", + notMeaning: [ + "لا يعني أن التنزيل تالف.", + "لا يعني أن جهازك وجد خطبًا ما. لم يُفحص شيء ولم يُكتشف شيء.", + "لا يعني أن التطبيق يسلك سلوكًا مختلفًا. البنية الموقّعة وغير الموقّعة من الإصدار نفسه هما البرنامج نفسه.", + ], + more: "الشرح الكامل، وكيف تتجنّب هذا كلّه", + }, }, fr: { - rev: "2026-08-20.1", - translatedFromRev: "2026-08-20.2", + rev: "2026-08-20.3", + translatedFromRev: "2026-08-20.3", title: "Télécharger keel — macOS et Windows", description: "Téléchargez keel pour macOS ou Windows. Version et liens viennent de GitHub Releases au build ; le parcours en cinq minutes depuis les sources est là aussi.", @@ -169,5 +258,35 @@ export const install: LocalizedPage = { }, fromReleaseWarning: "N'installez jamais par nom nu. La distribution s'appelle keel-trader ; le nom keel sur PyPI appartient à un projet sans rapport, donc pip install keel récupère le paquet de quelqu'un d'autre. Un build qui affiche DIRTY ou [checkout] n'est pas une version et ne doit jamais tourner sur des fonds réels.", + unsigned: { + title: "Votre ordinateur va vous avertir à propos de ce téléchargement", + lead: "Les builds macOS et Windows ne sont pas signés, donc votre système refusera la première ouverture en disant qu'il ne peut pas vérifier le développeur. Rien n'est cassé et rien n'a été détecté — votre ordinateur ne sait simplement pas qui a écrit le programme.", + reason: + "La signature de code est un certificat payant. Celui d'Apple coûte 99 $ par an, chaque année, et keel n'en a pas les moyens aujourd'hui. Il n'existe ni offre moins chère ni option gratuite pour les projets open source, et un certificat que nous fabriquerions nous-mêmes ne servirait à rien : macOS ne fait confiance qu'aux certificats émis par Apple.", + macosTitle: "macOS", + macosSteps: [ + "Double-cliquez sur keel. macOS refuse, en disant qu'il ne peut pas vérifier le développeur.", + "Ouvrez Réglages Système → Confidentialité et sécurité.", + "Descendez jusqu'au message concernant keel et cliquez sur Ouvrir quand même.", + "Vous ne le faites qu'une seule fois.", + ], + windowsTitle: "Windows", + windowsSteps: [ + "Lancez l'installeur. SmartScreen affiche « Windows a protégé votre ordinateur ».", + "Cliquez sur Informations complémentaires, puis Exécuter quand même.", + ], + verifyTitle: "Vérifiez d'abord ce que vous avez téléchargé", + verifyLead: + "Nous préférons ne pas vous demander simplement de passer outre un avertissement de sécurité — keel est un programme auquel vous confierez peut-être des clés API d'exchange. Chaque version porte une preuve de l'origine de ses fichiers, qui répond à la même question qu'un certificat : ceci a-t-il été construit depuis les sources de keel, par le pipeline de publication de keel ? Un fichier SHA256SUMS.txt est également joint à chaque version.", + verifyFail: + "Si l'une des deux vérifications échoue, n'ouvrez pas le fichier. Un échec signifie que ce n'est pas le fichier que nous avons construit, et aucun « Ouvrir quand même » ne rendra cela sûr.", + notMeaningTitle: "Ce que cela ne signifie pas", + notMeaning: [ + "Cela ne signifie pas que le téléchargement est endommagé.", + "Cela ne signifie pas que votre ordinateur a trouvé un problème. Rien n'a été analysé et rien n'a été détecté.", + "Cela ne signifie pas que l'application se comporte différemment. Un build signé et un build non signé de la même version sont le même programme.", + ], + more: "Explication complète, et comment éviter tout cela", + }, }, }; diff --git a/src/styles/global.css b/src/styles/global.css index 60a788d..d5f77f1 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -922,6 +922,51 @@ section { margin-bottom: 0.45em; } +/* -------------------------------------------------------------------------- + Unsigned-build note (install page) + + Reuses the notice palette rather than an alarm colour. The message is "your + computer will stop you, here is why and here is how to check what you have" + -- not "something is wrong". A red box would say the second. + -------------------------------------------------------------------------- */ + +.unsigned-note { + border: 1px solid var(--notice-border); + background: var(--notice-bg); + color: var(--notice-ink); + border-radius: var(--radius); + padding: 1.4rem 1.6rem; + margin-block: 2rem; +} + +.unsigned-note h2 { + margin-top: 0; + font-size: 1.15rem; +} + +.unsigned-note h3 { + font-size: 0.95rem; + margin-block: 1.4rem 0.4rem; +} + +.unsigned-steps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); + gap: 0 2rem; +} + +.unsigned-steps h3 { + margin-top: 1.2rem; +} + +.verify-command { + overflow-x: auto; + font-size: 0.85rem; + padding: 0.7rem 0.9rem; + border-radius: var(--radius); + background: var(--code-bg, rgba(0, 0, 0, 0.06)); +} + /* -------------------------------------------------------------------------- Stale translation banner (FR-8) -------------------------------------------------------------------------- */ From 84e3bd89bfd181ac7f75fdf82ad360e8eb4f971e Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Thu, 20 Aug 2026 16:48:49 -0400 Subject: [PATCH 2/2] feat(install): offer the five-minute source path from inside the unsigned-build note Someone reading a security warning is deciding whether to proceed, and the most useful thing to hand them at that exact moment is "there is a path with no warning at all". The five-minute source section was already further down the page; this puts the offer where the decision is being made and anchors straight to it. It is honest about the trade rather than selling it: that path needs a terminal and Python 3.11+, which is precisely the friction the desktop app exists to remove -- but if you already have both, it is the shorter road, and nothing is downloaded as an application so no OS warns about anything. en/ar/fr, `astro check` clean, and verified in a build with platform bundles present: the block renders in all three locales, the anchor resolves, and the target section now carries the id. --- src/components/pages/InstallPage.astro | 10 +++++++++- src/i18n/pages/install.ts | 21 ++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/components/pages/InstallPage.astro b/src/components/pages/InstallPage.astro index bbafe32..05ba480 100644 --- a/src/components/pages/InstallPage.astro +++ b/src/components/pages/InstallPage.astro @@ -167,6 +167,14 @@ const venvCommands = (os: "mac" | "win"): string => {

{c.unsigned.notMeaningTitle}

    {c.unsigned.notMeaning.map((item) =>
  • {item}
  • )}
+ {/* The friction-free alternative, offered here rather than left further down the + page: someone reading a security warning is deciding whether to proceed, and + "there is a path with no warning at all" is the most useful thing to hand them + at that exact moment. */} +

{c.unsigned.avoidTitle}

+

{c.unsigned.avoidBody}

+

{c.unsigned.avoidLink} →

+

{c.unsigned.more} → @@ -205,7 +213,7 @@ const venvCommands = (os: "mac" | "win"): string => {

{c.getStarted.link} →

-
+

{c.fromSource.title}

{c.fromSource.lead}

    diff --git a/src/i18n/pages/install.ts b/src/i18n/pages/install.ts index af219d9..651232a 100644 --- a/src/i18n/pages/install.ts +++ b/src/i18n/pages/install.ts @@ -43,6 +43,9 @@ export interface UnsignedNoteCopy { verifyFail: string; notMeaningTitle: string; notMeaning: string[]; + avoidTitle: string; + avoidBody: string; + avoidLink: string; more: string; } @@ -140,7 +143,11 @@ export const install: LocalizedPage = { "It does not mean your computer found something wrong. Nothing was scanned and nothing was detected.", "It does not mean the app behaves differently. A signed and an unsigned build of the same release are the same program.", ], - more: "Full explanation, including how to avoid this entirely", + avoidTitle: "Or skip the warning entirely", + avoidBody: + "The five-minute source path below installs the same engine with no installer and no warning, because nothing is downloaded as an application — pip and uv fetch the published wheels directly. It needs a terminal and Python 3.11+, which is exactly the friction the desktop app exists to remove; but if you already have both, it is the shorter road.", + avoidLink: "Try it in five minutes", + more: "Full explanation, including how to verify what you downloaded", }, }, @@ -213,7 +220,11 @@ export const install: LocalizedPage = { "لا يعني أن جهازك وجد خطبًا ما. لم يُفحص شيء ولم يُكتشف شيء.", "لا يعني أن التطبيق يسلك سلوكًا مختلفًا. البنية الموقّعة وغير الموقّعة من الإصدار نفسه هما البرنامج نفسه.", ], - more: "الشرح الكامل، وكيف تتجنّب هذا كلّه", + avoidTitle: "أو تجاوز التحذير من أصله", + avoidBody: + "مسار المصدر في خمس دقائق أدناه يثبّت المحرّك نفسه بلا مثبّت وبلا تحذير، لأنه لا يُنزَّل شيءٌ بوصفه تطبيقًا — بل يجلب pip وuv العجلات المنشورة مباشرةً. يحتاج طرفيّةً وPython 3.11 فأحدث، وهو بالضبط الاحتكاك الذي وُجد تطبيق سطح المكتب ليزيله؛ لكن إن كان كلاهما عندك أصلًا فهو الطريق الأقصر.", + avoidLink: "جرّبه في خمس دقائق", + more: "الشرح الكامل، وكيف تتحقّق ممّا نزّلته", }, }, @@ -286,7 +297,11 @@ export const install: LocalizedPage = { "Cela ne signifie pas que votre ordinateur a trouvé un problème. Rien n'a été analysé et rien n'a été détecté.", "Cela ne signifie pas que l'application se comporte différemment. Un build signé et un build non signé de la même version sont le même programme.", ], - more: "Explication complète, et comment éviter tout cela", + avoidTitle: "Ou évitez complètement l'avertissement", + avoidBody: + "Le parcours en cinq minutes depuis les sources, ci-dessous, installe le même moteur sans installeur et sans avertissement, parce que rien n'est téléchargé en tant qu'application — pip et uv récupèrent directement les wheels publiées. Il faut un terminal et Python 3.11+, ce qui est précisément la friction que l'application de bureau existe pour supprimer ; mais si vous avez déjà les deux, c'est le chemin le plus court.", + avoidLink: "Essayez-le en cinq minutes", + more: "Explication complète, et comment vérifier ce que vous avez téléchargé", }, }, };