From 6e64f7b2517563b1a22be0a81de232e2cf917dd1 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Fri, 28 Aug 2026 19:58:37 -0400 Subject: [PATCH 1/2] fix(install): 'Work from the browser' resyncs to the released v0.12 web UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.12.0 rewrite shipped a new client (JSON API + zero-dependency shell); v0.12.1 put keeltrading.com's palette and mark on it (#594); v0.12.2 gave the header a brand anchor, a light/dark theme choice and a read-only mode badge (#598). Every clause of browserBody re-verified against keel@v0.12.2, all three locales, rev 2026-08-28.6 -> .7. Added, each traced to a release: the interface wears this site's identity (v0.12.1); the header's theme choice and mode badge, reporting only (v0.12.2); money arrives as text, computed and formatted by the engine — the page displays, never derives (#544); profit and loss differ in brightness as well as colour, and form fields carry a visible border (#543); the page is served under a content-security policy that stops it sending positions or trade history off-machine (#545). Corrected: 'keel update moves the whole install' was already false for the desktop app — ADR 0001 ships per-release installers and the app never replaces itself, so the copy now says that, plus the releases' 'config, database and credentials are never touched by an update'. 'Attestations from a page' narrowed to asset attestations: the browser write surface at v0.12.2 is setup.ACTIONS (config, database, rules seed, keychain credential, asset attestation, gate-checked promotion, background fetch); every capability increase — resume/arm, record-flow, autonomy, withdrawals attest, update — is a CLI command at a terminal (keel/capabilities.py). Kept and re-verified: OS keychain, background fetch, WAL mode, cannot-arm/release/spend. features.ts and home.ts carry no browser-app copy; every 'Verify in the repository' target exists at v0.12.2 and none pointed at the deleted render.py. Closes #89 --- src/i18n/pages/install.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/i18n/pages/install.ts b/src/i18n/pages/install.ts index f810c09..52f8ac4 100644 --- a/src/i18n/pages/install.ts +++ b/src/i18n/pages/install.ts @@ -125,7 +125,7 @@ export interface InstallContent { export const install: LocalizedPage = { en: { - rev: "2026-08-28.6", + rev: "2026-08-28.7", 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.", @@ -194,7 +194,7 @@ export const install: LocalizedPage = { link: "Open Get Started", }, browserTitle: "Work from the browser — the desktop app", - browserBody: "The desktop app opens keel in your browser: a first-run checklist, credentials kept in your OS keychain, market data fetched as a background job, and attestations and promotions done from a page instead of a terminal. That setup surface cannot arm a rule, release funds or spend anything — it is built not to. The database beneath it runs in WAL mode, so watching a live fetch never again blocks the engine writing it. macOS gets the double-clickable app; since v0.12.0, Windows installs through a proper setup wizard instead of a zip, and keel update moves the whole install to each new release, in place. The desktop builds are unsigned — the note below says exactly what your computer will show, and how to verify what you downloaded — and the changelog lists each version the day it ships.", + browserBody: "The desktop app opens keel in your browser: a first-run checklist, credentials kept in your OS keychain, market data fetched as a background job, and asset attestations and rule promotions done from a page instead of a terminal — a promotion launched from the page advances only if it clears the gate, with no force option, because bypassing a gate needs a terminal. That setup surface cannot arm a rule, release funds or spend anything — every capability increase in keel still requires a person at a terminal. Since v0.12.1 the interface wears this site's own identity — the same paper, the same teal, the same ship mark — and since v0.12.2 its header carries a light-or-dark theme choice beside a badge naming the mode you are running; the badge only reports, for changing mode stays a deliberate edit to the config file, not a click in a page. Every money figure arrives as text, already computed and formatted by the engine — the page displays numbers, it never derives them — profit and loss differ in brightness as well as colour, so the distinction survives a greyscale screen, form fields carry a visible border, and the page is served under a content-security policy that stops it sending your positions or trade history anywhere but your own machine. The database beneath runs in WAL mode, so watching a live fetch never blocks the engine writing it. macOS gets the double-clickable app; since v0.12.0, Windows installs through a proper setup wizard. Updating the desktop app means downloading the new installer — it never replaces itself — and your config, database and credentials are never touched by an update. The desktop builds are unsigned — the note below says exactly what your computer will show, and how to verify what you downloaded — and the changelog lists each version the day it ships.", fromSource: { title: "From source — try it in five minutes", lead: "Everything in this path is read-only and paper-side: no funds, and nothing here can place an order. You need uv and a free, read-only Coinbase Developer Platform (CDP) API key — candle history is fetched through the authenticated client, so keel fetch without a key fails with an AuthenticationError. We say so here rather than let it surprise you at step four.", @@ -248,8 +248,8 @@ export const install: LocalizedPage = { }, ar: { - rev: "2026-08-28.6", - translatedFromRev: "2026-08-28.6", + rev: "2026-08-28.7", + translatedFromRev: "2026-08-28.7", title: "تنزيل كيل — macOS وWindows", description: "نزّل كيل لنظام macOS أو Windows. ويأتي رقمُ الإصدار وروابطه من GitHub Releases وقت البناء؛ ومسارُ التثبيت من المصدر في خمس دقائق هنا أيضًا.", @@ -318,7 +318,7 @@ export const install: LocalizedPage = { link: "افتح دليل البداية", }, browserTitle: "العمل من المتصفّح — تطبيق سطح المكتب", - browserBody: "تطبيقُ سطح المكتب يفتح كيل في متصفّحك: قائمةُ تحقّقٍ للتشغيل الأول، وبياناتُ الدخول تُحفظ في سلسلة مفاتيح نظام التشغيل، وبياناتُ السوق تُجلب مهمّةً في الخلفية، وتتمُّ الشهاداتُ والترقيات من صفحةٍ لا من طرفية. وسطحُ الإعداد ذاك لا يستطيع تسليحَ قاعدةٍ ولا تحريرَ أموالٍ ولا إنفاقَ شيء — بُني ليعجز عن ذلك عمدًا. وتحته تعمل قاعدةُ البيانات بوضع WAL، فمراقبةُ جلبٍ مباشرٍ لن تُعطّل المحرّك أثناء كتابته. و‏macOS يحصل على التطبيق القابل للفتح بنقرةٍ مزدوجة؛ ومنذ الإصدار v0.12.0 يتم تثبيت ‏Windows عبر معالجِ إعدادٍ حقيقي بدل ملفٍ مضغوط، والأمرُ keel update ينقل التثبيتَ كلَّه إلى كلِّ إصدارٍ جديد في مكانه. وتُسلَّم حزمتا سطح المكتب دون توقيعٍ رقمي — والملاحظةُ أدناه تقول بالضبط ما سيعرضه حاسوبك وكيف تتحقّق ممّا نزّلته — وسجلُّ التغييرات يُدرج كلَّ إصدارٍ يومَ صدوره.", + browserBody: "تطبيقُ سطح المكتب يفتح كيل في متصفّحك: قائمةُ تحقّقٍ للتشغيل الأول، وبياناتُ الدخول تُحفظ في سلسلة مفاتيح نظام التشغيل، وبياناتُ السوق تُجلب مهمّةً في الخلفية، وتُسجَّل شهاداتُ تصنيف الأصول وترقياتُ القواعد من صفحةٍ لا من طرفية — والترقيةُ التي تُطلَق من الصفحة لا تتقدّم إلا إذا اجتازت القاعدةُ البوابة، وبلا خيارِ تجاوز، لأنّ تجاوز البوابة يقتضي طرفية. وسطحُ الإعداد ذاك لا يستطيع تسليحَ قاعدةٍ ولا تحريرَ أموالٍ ولا إنفاقَ شيء — فكلُّ توسيعٍ لقدرات كيل ما يزال يتطلّب إنسانًا عند طرفية. ومنذ الإصدار v0.12.1 تلبس الواجهةُ هويةَ هذا الموقع نفسِه — الورقُ نفسُه، والأخضرُ المزرقُ نفسُه، وعلامةُ السفينة نفسُها — ومنذ v0.12.2 تحمل ترويسةُ الواجهة اختيارًا بين الفاتح والداكن إلى جانب شارةٍ تسمّي النمطَ الذي تعمل عليه؛ والشارةُ تُخبر فقط، فتغييرُ النمط يبقى تعديلًا مقصودًا في ملفّ الإعداد لا نقرةً في صفحة. وكلُّ رقمٍ ماليٍّ يصل نصًّا، محسوبًا ومنسّقًا من المحرّك نفسه — فالصفحةُ تعرض الأرقام ولا تشتقّها أبدًا — والربحُ والخسارة يفترقان في السطوع لا في اللون وحده فينجو التمييزُ على شاشةٍ رمادية، وحقولُ الإدخال تحمل حدودًا مرئية، وتُقدَّم الصفحةُ تحت سياسةِ أمانِ المحتوى التي تمنعها إرسالَ مراكزك أو تاريخِ تداولك إلى غير جهازك. وقاعدةُ البيانات تحتها تعمل بوضع WAL، فمراقبةُ جلبٍ مباشرٍ لن تُعطّل المحرّك أثناء كتابته. و‏macOS يحصل على التطبيق القابل للفتح بنقرةٍ مزدوجة؛ ومنذ الإصدار v0.12.0 يتم تثبيت ‏Windows عبر معالجِ إعدادٍ حقيقي. وتحديثُ تطبيق سطح المكتب يعني تنزيلَ المُثبِّت الجديد — فهو لا يستبدل نفسه أبدًا — وملفُّ إعدادك وقاعدةُ بياناتك وبياناتُ دخولك لا يمسّها تحديثٌ أبدًا. وتُسلَّم حزمتا سطح المكتب دون توقيعٍ رقمي — والملاحظةُ أدناه تقول بالضبط ما سيعرضه حاسوبك وكيف تتحقّق ممّا نزّلته — وسجلُّ التغييرات يُدرج كلَّ إصدارٍ يومَ صدوره.", fromSource: { title: "من المصدر — جرّبه في خمس دقائق", lead: "كلُّ ما في هذا المسار بصلاحية القراءة فقط وعلى جانب التداول التجريبي: لا أموال، ولا شيء هنا يستطيع تقديم أمر تداول. وتحتاج إلى uv وإلى مفتاح API مجّانيٍّ للقراءة فقط من منصّة Coinbase Developer Platform‏ (CDP) — إذ تُجلب بيانات الشموع عبر العميل المُصادَق عليه، ولذلك يفشل الأمر keel fetch من دون مفتاحٍ بخطأ AuthenticationError؛ نقولها مقدَّمًا كي لا تكون مفاجأةً في الخطوة الرابعة.", @@ -372,8 +372,8 @@ export const install: LocalizedPage = { }, fr: { - rev: "2026-08-28.6", - translatedFromRev: "2026-08-28.6", + rev: "2026-08-28.7", + translatedFromRev: "2026-08-28.7", title: "Télécharger keel — macOS et Windows", description: "Téléchargez keel pour macOS ou Windows. Le numéro de version et les liens proviennent de GitHub Releases, récupérés au moment du build ; le parcours en cinq minutes depuis les sources figure également ici.", @@ -442,7 +442,7 @@ export const install: LocalizedPage = { link: "Ouvrir Premiers pas", }, browserTitle: "Travailler depuis le navigateur — l'application de bureau", - browserBody: "L'application de bureau ouvre keel dans votre navigateur : liste de contrôle au premier lancement, identifiants gardés dans le trousseau du système, données de marché rapatriées en tâche de fond, attestations et promotions faites depuis une page plutôt qu'un terminal. Cette surface de configuration ne peut armer une règle, libérer des fonds ni rien dépenser — elle est construite pour ne pas le pouvoir. En dessous, la base de données tourne en mode WAL : regarder une récupération en direct ne bloquera jamais le moteur qui l'écrit. macOS reçoit l'application à double-clic ; depuis la v0.12.0, Windows s'installe via un véritable assistant d'installation plutôt qu'un zip, et keel update déplace l'installation entière vers chaque nouvelle version, en place. Les builds bureau ne sont pas signés — la note ci-dessous dit exactement ce que votre ordinateur affichera et comment vérifier ce que vous avez téléchargé — et le journal des versions inscrit chaque sortie le jour de sa publication.", + browserBody: "L'application de bureau ouvre keel dans votre navigateur : liste de contrôle au premier lancement, identifiants gardés dans le trousseau du système, données de marché rapatriées en tâche de fond, attestations d'actifs et promotions de règles faites depuis une page plutôt qu'un terminal — une promotion lancée depuis la page n'avance que si la règle franchit le verrou, sans option de contournement, car contourner un verrou exige un terminal. Cette surface de configuration ne peut armer une règle, libérer des fonds ni rien dépenser — chaque élargissement des capacités de keel exige encore une personne devant un terminal. Depuis la v0.12.1, l'interface porte l'identité de ce site lui-même — même papier, même sarcelle, même marque au navire — et depuis la v0.12.2 son en-tête offre un choix clair ou sombre à côté d'un badge nommant le mode en cours ; le badge informe seulement, car changer de mode reste une modification délibérée du fichier de configuration, pas un clic dans une page. Chaque montant arrive en texte, déjà calculé et mis en forme par le moteur — la page affiche des nombres, elle n'en dérive jamais — gain et perte se distinguent par la luminosité autant que par la couleur, donc la distinction survit à un écran en niveaux de gris, les champs de formulaire portent une bordure visible, et la page est servie sous une politique de sécurité du contenu qui lui interdit d'envoyer vos positions ou votre historique de transactions ailleurs que sur votre propre machine. En dessous, la base tourne en mode WAL : regarder une récupération en direct ne bloquera jamais le moteur qui l'écrit. macOS reçoit l'application à double-clic ; depuis la v0.12.0, Windows s'installe via un véritable assistant d'installation. Mettre à jour l'application de bureau passe par le téléchargement du nouvel installateur — elle ne se remplace jamais elle-même — et votre configuration, votre base et vos identifiants ne sont jamais touchés par une mise à jour. Les builds bureau ne sont pas signés — la note ci-dessous dit exactement ce que votre ordinateur affichera et comment vérifier ce que vous avez téléchargé — et le journal des versions inscrit chaque sortie le jour de sa publication.", fromSource: { title: "Depuis les sources — essayez keel en cinq minutes", lead: "Tout ce parcours est en lecture seule et côté papier : aucun fonds, et rien ici ne peut passer d'ordre. Il vous faut uv et une clé d'API Coinbase Developer Platform (CDP) gratuite, en lecture seule — l'historique des bougies passe par le client authentifié, si bien que keel fetch sans clé échoue sur une AuthenticationError. Autant le dire tout de suite, pour que l'étape 4 ne surprenne personne.", From 83d7db6ca74af94e620b0122dad552d571d30c35 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Fri, 28 Aug 2026 19:58:44 -0400 Subject: [PATCH 2/2] feat(docs): ADRs 0001-0003 sync from docs/decisions under a Decisions section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The engine repo records its architecture decisions in docs/decisions/ — short, dated Context/Options/Decision/Consequences records, numbered and superseded, never quietly edited. Until now every ADR lived repo-only: a site reader never saw why keel is built the way it is. Adds the three records at the pinned release to engine-docs.manifest.json, each with slug/title/en/ar/fr blurb under a new 'decisions' section (Decisions / سجلّات القرارات / Décisions): 0001 the desktop update path — per-release installer, no self-update 0002 SQLite persistence — one writer per file, named triggers only 0003 the commands-layer survey — measured, and the premise dissolved Reading order: guides -> reference -> decisions -> research, ADR number within the section — sidebar, index cards and prev/next all derive from one order (nav.ts readingOrder). The sync stays the only writer of src/content/engine-docs/; docs/decisions/README.md (the directory index) is deliberately not synced — the docs index plays that role. Each path verified HTTP 200 at v0.12.2; a move upstream still fails the build loudly per FR-4, with no fallback to main. Closes #120 --- engine-docs.manifest.json | 33 ++++++++++++++++++++++++ src/components/docs/DocsSidebar.astro | 2 +- src/components/docs/nav.ts | 12 ++++++--- src/components/pages/DocsIndexPage.astro | 1 + 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/engine-docs.manifest.json b/engine-docs.manifest.json index 6b59fdf..48b9542 100644 --- a/engine-docs.manifest.json +++ b/engine-docs.manifest.json @@ -21,6 +21,33 @@ "section": "reference", "fr": "Le raisonnement Shariah que keel encode, règle par règle, chacun avec sa source dans le dépôt — publié pour être audité et contesté." }, + { + "path": "docs/decisions/0001-desktop-update-path.md", + "slug": "decision-0001-desktop-update-path", + "title": "Decision 0001: the desktop update path", + "en": "Why the desktop app never self-updates: updating means deliberately downloading the new installer — a trust posture chosen over an update channel that would itself need securing.", + "ar": "لماذا لا يُحدِّث تطبيقُ سطح المكتب نفسَه أبدًا: التحديثُ يعني تنزيلَ المُثبِّت الجديد عن قصد — موقفُ ثقةٍ اختير على قناةِ تحديثٍ تحتاج هي نفسها إلى تحصين.", + "section": "decisions", + "fr": "Pourquoi l'application de bureau ne se met jamais à jour elle-même : la mise à jour passe par le téléchargement délibéré du nouvel installateur — une posture de confiance préférée à un canal de mise à jour qu'il faudrait lui-même sécuriser." + }, + { + "path": "docs/decisions/0002-sqlite-persistence.md", + "slug": "decision-0002-sqlite-persistence", + "title": "Decision 0002: SQLite persistence", + "en": "Why persistence is SQLite with one writer per file — stated as load-bearing, not a limitation, with the named triggers that alone would reopen the question.", + "ar": "لماذا هي مكانُ البيانات ‏SQLite بكاتبٍ واحد لكل ملف — قاعدةٌ حاملةٌ لا قيدًا مؤقتًا، مع مُحفِّزاتٍ مسمّاةٍ وحدها تفتح السؤالَ من جديد.", + "section": "decisions", + "fr": "Pourquoi la persistance est SQLite avec un seul écrivain par fichier — énoncé comme porteur et non comme limite, avec les déclencheurs nommés qui seuls rouvriraient la question." + }, + { + "path": "docs/decisions/0003-commands-layer-survey.md", + "slug": "decision-0003-commands-layer-survey", + "title": "Decision 0003: the commands-layer survey", + "en": "The measurement-first survey of the commands layer — what each module's role is, and why \"larger than the engine\" dissolved before the survey ran.", + "ar": "المسحُ القياسيُّ الأوّل لطبقة الأوامر — ما دورُ كلِّ وحدةٍ، ولماذا ذاب وصفُ «أكبر من المحرّك» قبل أن يبدأ المسح.", + "section": "decisions", + "fr": "L'enquête mesure-d'abord de la couche commands — le rôle de chaque module, et pourquoi « plus grande que le moteur » s'est dissous avant même que l'enquête ne commence." + }, { "path": "docs/desktop-install.md", "slug": "desktop-install", @@ -131,6 +158,12 @@ "ar": "مرجع", "fr": "Référence" }, + { + "id": "decisions", + "en": "Decisions", + "ar": "سجلّات القرارات", + "fr": "Décisions" + }, { "id": "research", "en": "Research & experiments", diff --git a/src/components/docs/DocsSidebar.astro b/src/components/docs/DocsSidebar.astro index f4245c0..8650557 100644 --- a/src/components/docs/DocsSidebar.astro +++ b/src/components/docs/DocsSidebar.astro @@ -88,7 +88,7 @@ const explainersLabel = { - (["guides", "reference", "research"] as const).map((sectionId) => { + (["guides", "reference", "decisions", "research"] as const).map((sectionId) => { const section = meta.sections.find((s) => s.id === sectionId); const docs = meta.docs.filter((doc) => doc.section === sectionId); if (!section || docs.length === 0) return null; diff --git a/src/components/docs/nav.ts b/src/components/docs/nav.ts index 7238e0c..b2b6bbc 100644 --- a/src/components/docs/nav.ts +++ b/src/components/docs/nav.ts @@ -20,7 +20,7 @@ export function readDataFile(name: string): T | null { } } -export type SectionId = "get-started" | "guides" | "reference" | "research"; +export type SectionId = "get-started" | "guides" | "reference" | "decisions" | "research"; export interface DocMeta { slug: string; @@ -75,6 +75,9 @@ export const sectionIcons: Record = { // open book reference: '', + // a record with a ruling: the document, and the check that settles it + decisions: + '', // flask research: '', @@ -99,9 +102,12 @@ export interface NavDoc { section: SectionId; } -/** Flattened reading order: sidebar order == prev/next order. */ +/** Flattened reading order: sidebar order == prev/next order. Decisions sit + * between reference and research: the "what it is" pages, then the recorded + * "why it is that way", then the measurements. Within the section, manifest + * order is ADR number (0001, 0002, 0003, …). */ export function readingOrder(meta: MetaFile): NavDoc[] { - const order: SectionId[] = ["guides", "reference", "research"]; + const order: SectionId[] = ["guides", "reference", "decisions", "research"]; return order.flatMap((section) => meta.docs.filter((doc) => doc.section === section).map((doc) => ({ slug: doc.slug, title: doc.title, section })), ); diff --git a/src/components/pages/DocsIndexPage.astro b/src/components/pages/DocsIndexPage.astro index bb53f1c..dc41ca4 100644 --- a/src/components/pages/DocsIndexPage.astro +++ b/src/components/pages/DocsIndexPage.astro @@ -21,6 +21,7 @@ const sections: { id: SectionId; extra?: boolean }[] = [ { id: "get-started", extra: true }, { id: "guides" }, { id: "reference" }, + { id: "decisions" }, { id: "research" }, ];