diff --git a/.gitignore b/.gitignore index d495167..bf9727c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ xcuserdata/ *.xcuserstate ios/build/ +# Local handoff notes stay outside version control. +/HANDOFF.md + # Release archives are published separately. dist/ diff --git a/README.de.md b/README.de.md index 882bc89..25f891b 100644 --- a/README.de.md +++ b/README.de.md @@ -20,15 +20,17 @@ Git-Historie, die auch ohne SourceBraid nützlich bleiben. ## So funktioniert SourceBraid SourceBraid verbindet Capture-Clients, ein privates GitHub-Repository als -dauerhaft maßgebliche Datenquelle und zwei Zugriffswege. Das lokale Codex-Plugin -unterstützt Suche und Archivverwaltung. Die gehostete ChatGPT-Integration mit -reinem Lesezugriff ist für Bereitstellung und Prüfung vorbereitet; sie ist noch -kein öffentlich verfügbarer Dienst. Chrome-Erweiterung und iOS-App schreiben +dauerhaft maßgebliche Datenquelle und drei Zugriffswege. Die iOS-App bietet +einen lokalen Reader, Offline-Suche und optionalen Chat auf dem Gerät. Das lokale +Codex-Plugin unterstützt Suche und Archivverwaltung. Die gehostete +ChatGPT-Integration mit reinem Lesezugriff ist für Bereitstellung und Prüfung +vorbereitet; sie ist noch kein öffentlich verfügbarer Dienst. +Chrome-Erweiterung und iOS-App schreiben Captures direkt in das konfigurierte GitHub-Repository. Gehostete ChatGPT-Anfragen laufen künftig über den SourceBraid-Worker bei Cloudflare, ohne eine dauerhafte Inhaltsdatenbank anzulegen. -Der lokale SQLite-Index ist nur ein jederzeit neu aufbaubarer Such-Cache. +Lokale SQLite-Indizes sind nur jederzeit neu aufbaubare Such-Caches. Maßgeblich bleiben die Markdown-Dateien und die Git-Historie. ```mermaid @@ -56,6 +58,8 @@ flowchart TD Q --> R["Lokales Codex: suchen, abrufen, auflisten oder sicher löschen"] K --> S["Vorbereiteter gehosteter MCP: GitHub-Lesezugriff"] S --> T["ChatGPT nach Bereitstellung und Prüfung"] + K --> U["iOS: lokaler Cache und Volltextsuche"] + U --> V["Beiträge und PDFs lesen; optionaler Chat auf dem Gerät"] ``` Der Ablauf im Einzelnen: @@ -87,6 +91,9 @@ Der Ablauf im Einzelnen: bleibt der zuletzt synchronisierte Index lesbar. Der vorbereitete ChatGPT-Dienst sucht und liest über GitHub, nennt Suchgrenzen ausdrücklich und bietet weder lokalen Index noch Löschwerkzeuge. + Die iOS-App liest heruntergeladene Beiträge offline, durchsucht deren Text + und bietet auf unterstützten Geräten optionalen Apple-Intelligence-Chat mit + Quellenlinks. Die Trennung zwischen GitHub-Archiv und lokalem Such-Cache ist für größere Sammlungen entscheidend: Auch bei vielen Tausend Dokumenten muss eine normale @@ -246,8 +253,8 @@ Gist-Tab mit aktiver GitHub-Sitzung geladen werden. ## Chrome-Installation aus dem Quellcode -Bis der Eintrag im Chrome Web Store live ist, lässt sich die geprüfte -Erweiterung aus diesem Checkout laden: +Chrome-Version **1.1.0** ist für die Einreichung im Chrome Web Store vorbereitet. +Bis der Eintrag live ist, lässt sich die Erweiterung aus diesem Checkout laden: 1. `chrome://extensions` öffnen. 2. **Entwicklermodus** aktivieren. @@ -290,7 +297,7 @@ Datei bereit. Sie lässt sich lokal so bauen: ```bash python3 scripts/build_setup_package.py -python3 dist/sourcebraid-github-setup-v1.0.1.py --help +python3 dist/sourcebraid-github-setup-v1.1.0.py --help ``` ## GitHub-Token @@ -318,13 +325,20 @@ Optionale API-Einstellungen: ## SourceBraid in ChatGPT und Codex -Die primäre ChatGPT-Veröffentlichung nutzt den gehosteten MCP-Dienst unter +Die vorbereitete ChatGPT-Version **1.1.0** nutzt den gehosteten MCP-Dienst unter [`chatgpt-mcp/`](chatgpt-mcp/). Vorgesehener Endpunkt ist `https://mcp.sourcebraid.com/mcp`; Bereitstellung, Live-Authentifizierungstests und OpenAI-Prüfung samt Veröffentlichung stehen noch aus. Die Schritte stehen in der [Bereitstellungsanleitung](docs/CHATGPT_MCP_DEPLOYMENT.md) und im [OpenAI-MCP-Einreichungskit](marketing/OPENAI_MCP_SUBMISSION.md). +Das Release-Bundle enthält `sourcebraid-chatgpt-plugin-v1.1.0.zip` für den Upload +im OpenAI-Plugin-Portal. Es enthält Plugin-Metadaten, die Remote-MCP-Konfiguration +und das Listing-Icon. `sourcebraid-chatgpt-mcp-v1.1.0.zip` enthält den +Worker-Quellcode zur Bereitstellung. Beide entstehen mit +`python3 scripts/build_release.py`; der Paketbau stellt keinen Dienst bereit +und veröffentlicht keinen Eintrag. + Nach Bereitstellung verbinden sich Nutzer über die HTTPS-Zustimmungsseite von SourceBraid mit einem Fine-grained-GitHub-Token: **Contents: Read-only** für genau ein privates Archiv-Repository. Der Token gehört niemals in einen @@ -334,8 +348,8 @@ indexieren. Die Suche auf dem Standardbranch nutzt GitHub Code Search und prüft Treffer an einem festgehaltenen Commit. Indexierungsverzögerungen, Suchgrenzen und ein gegebenenfalls begrenzter Fallback werden ausgewiesen. -Das lokale Codex-Plugin bleibt die Variante mit voller Archivverwaltung. Es -benötigt Python 3 und lokale GitHub-Authentifizierung. Ein reines Skills-ZIP +Das lokale Codex-Plugin **1.1.0** bleibt die Variante mit voller Archivverwaltung. +Es benötigt Python 3 und lokale GitHub-Authentifizierung. Ein reines Skills-ZIP verschafft gewöhnlichem ChatGPT allein keinen Zugriff auf lokale Dateien oder Zugangsdaten. @@ -347,12 +361,12 @@ codex plugin marketplace add . codex plugin add sourcebraid@sourcebraid ``` -Sobald der geprüfte Tag `v1.0.1` veröffentlicht ist, lautet die an diesen Tag +Sobald der geprüfte Tag `v1.1.0` veröffentlicht ist, lautet die an diesen Tag gebundene Installation von GitHub: ```bash codex plugin marketplace add patrickschiller/sourcebraid \ - --ref v1.0.1 \ + --ref v1.1.0 \ --sparse .agents/plugins \ --sparse codex-plugin/sourcebraid codex plugin add sourcebraid@sourcebraid @@ -394,7 +408,7 @@ Der Index liegt getrennt pro Repository, Branch und Archivordner unter Eine Suche prüft höchstens alle 15 Minuten, ob sich der Remote-Head geändert hat; wenn GitHub nicht erreichbar ist, bleibt der lokale Index nutzbar. `search --scan` ist ein expliziter Diagnose-Fallback auf Basis von `rg`. -Der sicherere Cache-Pfad dieser Version löst einen erneuten Erstaufbau aus; +Bei einem älteren Cache-Layout wird der Index im aktuellen Cache-Pfad neu aufgebaut; Markdown-Dateien und Git-Historie bleiben unverändert. Neue Captures schreiben stabile URL-Hash-Shards wie @@ -429,13 +443,19 @@ Nach einmaliger Einrichtung von Repository und Token können URLs, ausgewählter Text, Safari-Artikel mit relevanten Bild-Assets, PDFs und andere Dateien über das Teilen-Menü an das konfigurierte private Archiv gesendet werden. -Der iOS-Release-Kandidat **1.1.0** ergänzt einen Markdown-Reader, die neuesten -sieben Beiträge, Offline-Downloads und Volltextsuche, optionalen lokalen -Apple-Intelligence-Chat mit Quellenlinks sowie adaptive iPad-/iPhone-Duo-Navigation. -Der Maintainer hat die Veröffentlichung nach einem eigenen Funktionstest am -15. Oktober 2026 freigegeben. Xcode-Cloud-Distribution und App-Store-Verfügbarkeit -werden getrennt verfolgt; die Freigabe belegt keinen erfolgreichen Cloud-Build -oder Store-Release. Siehe [Release-Hinweise 1.1.0](docs/IOS_RELEASE_1.1.0.de.md), +Der Maintainer hat am 15. Oktober 2026 bestätigt, dass iOS **1.1.0** im App Store +veröffentlicht ist. Die Version ergänzt einen Markdown-Reader, die neuesten +sieben Beiträge, Offline-Downloads und Volltextsuche, native PDF-Vorschauen, +optionalen lokalen Apple-Intelligence-Chat mit Quellenlinks, schreibgeschützte +App-Kurzbefehle sowie adaptive iPad-/iPhone-Duo-Navigation. + +Reader und Suche unterstützen iOS/iPadOS ab 17. Chat benötigt zusätzlich +iOS/iPadOS ab 26, unterstützte Hardware, aktivierte Apple Intelligence und ein +verfügbares Systemmodell in einer unterstützten Sprache. Die Suche erfasst +heruntergeladene Texte; anfangs kann die Archivabdeckung unvollständig sein. +iOS bestimmt die Hintergrund-Ausführungszeit. Die Veröffentlichung erklärt +die verbleibenden Geräteprüfungen nicht für bestanden. +Siehe [Release-Hinweise 1.1.0](docs/IOS_RELEASE_1.1.0.de.md), [Umsetzungs- und Testprotokoll](docs/IOS_IMPLEMENTATION_TESTING.de.md) und [verbleibende Geräteprüfungen](docs/IOS_DEVICE_ACCEPTANCE.de.md). Native Navigation für Neueste/Suche/Chat und begrenzte Hintergrundaktualisierung stehen diff --git a/README.md b/README.md index 3f801dd..4e6d1b7 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,15 @@ you with ordinary files and Git history that remain useful without SourceBraid. ## How SourceBraid works SourceBraid combines capture clients, a private GitHub repository as the durable -source of truth, and two archive-access paths. The local Codex plugin supports -search and archive management. The hosted, read-only ChatGPT integration is -prepared for deployment and review; it is not yet a live public service. The +source of truth, and three archive-access paths. The iOS app provides a local +reader, offline search, and optional on-device chat. The local Codex plugin +supports search and archive management. The hosted, read-only ChatGPT integration +is prepared for deployment and review; it is not yet a live public service. The Chrome extension and iOS app write captures directly to the configured GitHub repository. Hosted ChatGPT requests will pass through SourceBraid's Cloudflare Worker without creating a permanent content database. -The local SQLite index is only a rebuildable search cache. Markdown files and +Local SQLite indexes are only rebuildable search caches. Markdown files and Git history remain authoritative. ```mermaid @@ -53,6 +54,8 @@ flowchart TD Q --> R["Local Codex: search, fetch, list, or safely delete"] K --> S["Prepared hosted MCP: read-only GitHub access"] S --> T["ChatGPT after deployment and review"] + K --> U["iOS: local cache and full-text search"] + U --> V["Read clips and PDFs; optional on-device chat"] ``` The workflow in detail: @@ -79,6 +82,8 @@ The workflow in detail: when GitHub is unavailable. The prepared ChatGPT service searches and fetches through GitHub with read-only access, explicit coverage limits, and no local index or deletion tools. + The iOS app reads downloaded clips offline, searches their cached text, and + offers optional Apple Intelligence chat with source links on supported devices. Keeping the GitHub archive separate from the local search cache matters for large collections: a normal query does not need to reopen thousands of Markdown @@ -225,8 +230,8 @@ be loaded through the still-open Gist tab. ## Chrome installation from source -Until the Chrome Web Store listing is live, load the reviewed extension from -this checkout: +Chrome version **1.1.0** is prepared for Chrome Web Store submission. Until the +listing is live, load the extension from this checkout: 1. Open `chrome://extensions`. 2. Enable **Developer mode**. @@ -266,7 +271,7 @@ locally with: ```bash python3 scripts/build_setup_package.py -python3 dist/sourcebraid-github-setup-v1.0.1.py --help +python3 dist/sourcebraid-github-setup-v1.1.0.py --help ``` ## GitHub token @@ -293,12 +298,19 @@ Optional API configuration: ## SourceBraid in ChatGPT and Codex -The primary ChatGPT release uses the hosted MCP service in [`chatgpt-mcp/`](chatgpt-mcp/). +The prepared **1.1.0** ChatGPT release uses the hosted MCP service in +[`chatgpt-mcp/`](chatgpt-mcp/). Its intended endpoint is `https://mcp.sourcebraid.com/mcp`; deployment, live authentication tests, and OpenAI review/publication are still required. Follow the [deployment guide](docs/CHATGPT_MCP_DEPLOYMENT.md) and [OpenAI MCP submission kit](marketing/OPENAI_MCP_SUBMISSION.md). +The release bundle includes `sourcebraid-chatgpt-plugin-v1.1.0.zip` for upload +to OpenAI's plugin portal. It contains the plugin metadata, remote MCP +configuration, and listing icon. `sourcebraid-chatgpt-mcp-v1.1.0.zip` contains +the Worker source for deployment. Build both with `python3 scripts/build_release.py`; +building the files does not deploy the service or publish a listing. + After the service is deployed, users connect through SourceBraid's HTTPS consent page using a fine-grained GitHub token with **Contents: Read-only** for exactly one private archive repository. Never paste that token into a ChatGPT prompt. @@ -307,8 +319,8 @@ edit, delete, migrate metadata, or build a local index. Default-branch search uses GitHub Code Search and verifies matches at a pinned commit. Indexing delays, limits, and any bounded fallback are reported with the results. -The local Codex plugin remains the full archive-management option. It needs -Python 3 and local GitHub authentication; a skills-only ZIP does not by itself +The local Codex plugin **1.1.0** remains the full archive-management option. It +needs Python 3 and local GitHub authentication; a skills-only ZIP does not by itself give ordinary ChatGPT access to those local files or credentials. For local development, add this checkout as a marketplace and install the @@ -319,12 +331,12 @@ codex plugin marketplace add . codex plugin add sourcebraid@sourcebraid ``` -After the reviewed `v1.0.1` tag is published, the tag-bound GitHub installation +After the reviewed `v1.1.0` tag is published, the tag-bound GitHub installation is: ```bash codex plugin marketplace add patrickschiller/sourcebraid \ - --ref v1.0.1 \ + --ref v1.1.0 \ --sparse .agents/plugins \ --sparse codex-plugin/sourcebraid codex plugin add sourcebraid@sourcebraid @@ -364,8 +376,8 @@ The index is stored separately per repository, branch, and archive root under for a changed remote head at most every 15 minutes; when GitHub is unavailable, the local index remains usable. `search --scan` is an explicit `rg` diagnostic fallback. -The safer cache namespace introduced in this release triggers a fresh initial -build; existing Markdown and Git history are unchanged. +Archives with an older cache layout receive a fresh initial index build in the +current cache namespace; existing Markdown and Git history are unchanged. New captures write stable URL-hash shards such as `web-clips/index/47.jsonl`. Legacy archives remain readable. Preview and confirm @@ -395,12 +407,17 @@ After one-time repository and token setup, URLs, selected text, Safari articles with relevant image assets, PDFs, and other files can be sent to the configured private archive through the system share sheet. -The iOS **1.1.0** release candidate adds a Markdown reader, the latest seven -clips, offline downloads and full-text search, optional on-device Apple -Intelligence chat with source links, and adaptive iPad/iPhone Duo navigation. -The maintainer approved publication after a hands-on functional check on -October 15, 2026. Xcode Cloud distribution and App Store availability are tracked -separately; approval does not establish a successful cloud build or store release. +The maintainer confirmed on October 15, 2026 that iOS **1.1.0** is published +in the App Store. It adds a Markdown reader, the latest seven clips, offline +downloads and full-text search, native PDF previews, optional on-device Apple +Intelligence chat with source links, read-only App Shortcuts, and adaptive +iPad/iPhone Duo navigation. + +Reader and search support iOS/iPadOS 17 or later. Chat additionally requires +iOS/iPadOS 26 or later, supported hardware, enabled Apple Intelligence, and an +available system model in a supported language. Search covers downloaded text; +initial archive coverage can be partial. Background refresh remains subject to +iOS scheduling. Publication does not mark the remaining device checks as passed. See the [1.1.0 release notes](docs/IOS_RELEASE_1.1.0.md), [implementation and test record](docs/IOS_IMPLEMENTATION_TESTING.md), and [remaining device checks](docs/IOS_DEVICE_ACCEPTANCE.md). Native diff --git a/RELEASING.md b/RELEASING.md index 1c58809..c991f65 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -5,11 +5,12 @@ Store credentials, account verification, legal attestations, review accounts, and final submission decisions are intentionally maintained outside the public repository. -The iOS app is published in the App Store as `1.0.1`. The Chrome Web Store and -public ChatGPT/Codex directory releases remain separate maintainer actions and -must not be described as available before their listings are live. The first -coordinated Chrome and OpenAI plugin release uses `1.0.1` so all available -clients start from the same customer-facing version. +The maintainer confirmed on October 15, 2026 that iOS `1.1.0` is published in the +App Store. The current Chrome, local Codex plugin, and hosted ChatGPT MCP release +target is also `1.1.0`. These packages are prepared for release; the Chrome Web +Store and public ChatGPT/Codex directory releases remain separate maintainer +actions and must not be described as available before their listings are live. +The iOS publication does not establish approval or publication of another client. ## Release principles @@ -25,7 +26,7 @@ clients start from the same customer-facing version. ## Version alignment -Before creating the first release candidate, align these customer-facing +Before creating each release candidate, align these customer-facing versions: - Chrome: `version` in `chrome-extension/sourcebraid/manifest.json`. @@ -33,6 +34,7 @@ versions: `codex-plugin/sourcebraid/.codex-plugin/plugin.json`. - Hosted ChatGPT MCP: `version` in `chatgpt-mcp/package.json` and its server metadata. +- ChatGPT portal plugin: `version` in `chatgpt-plugin/sourcebraid/plugin.json`. - iOS app and Share Extension: `MARKETING_VERSION` in `ios/SourceBraid.xcodeproj/project.pbxproj`. @@ -40,7 +42,7 @@ Chrome versions use one to four dot-separated integers. The plugin uses semantic versioning. After the first iOS upload, increase `CURRENT_PROJECT_VERSION` for every new binary while keeping the app and Share Extension build numbers equal. -The first iOS publication uses the SourceBraid identities: +The iOS publication uses the SourceBraid identities: - app: `de.patrickschiller.sourcebraid`; - Share Extension: `de.patrickschiller.sourcebraid.share`; @@ -108,7 +110,7 @@ Local repository installations can continue to use the complete plugin source. This ZIP needs a runtime with Python and GitHub credentials. It is not a standalone normal-ChatGPT connection and is not the primary OpenAI submission. -## Hosted ChatGPT MCP package +## Hosted ChatGPT MCP source package The separate Worker in `chatgpt-mcp/` is prepared for `https://mcp.sourcebraid.com/mcp`; it is not deployed. Build the allowlisted @@ -117,7 +119,7 @@ service source package and synthetic reviewer archive: ```bash python3 scripts/build_mcp_package.py python3 scripts/build_review_fixture.py \ - --output dist/sourcebraid-review-fixture-v1.0.1.zip + --output dist/sourcebraid-review-fixture-v1.1.0.zip ``` The review ZIP contains only three synthetic Markdown clips and their three @@ -126,9 +128,26 @@ metadata shards. It does not read or copy a user's archive. Follow domain, KV, OAuth, and live smoke checks. A Worker dry-run or source ZIP is not a successful production deployment. +## ChatGPT portal plugin package + +The complete release builder also creates +`sourcebraid-chatgpt-plugin-v1.1.0.zip` for OpenAI's plugin portal. Its root +`plugin.json`, `mcp.json`, and listing icon describe the hosted read-only +connection. The separate `sourcebraid-chatgpt-mcp-v1.1.0.zip` contains the Worker +source to deploy. Neither ZIP contains credentials, and the portal package does +not deploy the Worker. + +Build the portal package on its own from the manifests in +`chatgpt-plugin/sourcebraid/` and the allowlisted icon: + +```bash +python3 scripts/build_chatgpt_plugin_package.py +unzip -l dist/sourcebraid-chatgpt-plugin-v1.1.0.zip +``` + ## OpenAI Plugins Directory submission -The primary `1.0.1` ChatGPT submission is **With MCP**, using the hosted +The primary `1.1.0` ChatGPT submission is **With MCP**, using the hosted read-only service after deployment and live tests. Supply the actual public HTTPS endpoint and complete domain verification. Do not submit the intended `https://mcp.sourcebraid.com/mcp` URL as a working service before it is live. @@ -143,11 +162,14 @@ notes, privacy disclosures, and the final portal checklist. Before submitting: Worker with verified domain and OAuth configuration; 3. seed the dedicated private review repository from the synthetic fixture ZIP and validate the live OAuth/search/fetch flow; -4. restrict its review PAT to Contents read-only on that one repository and - provide credentials only through OpenAI's protected submission fields; -5. select country availability deliberately and complete the policy - attestations; and -6. submit for review, then publish only after approval. +4. upload `sourcebraid-chatgpt-plugin-v1.1.0.zip` through **Upload new or existing + plugin**, inspect its metadata checks, then connect and scan its hosted MCP; +5. restrict its review PAT to Contents read-only on that one repository and + provide credentials only through OpenAI's protected review fields; +6. provide five positive and three negative tests and a reviewer-accessible + video walkthrough, select country availability deliberately, and complete + the policy attestations; and +7. submit for review, then publish only after approval. Follow the current [OpenAI submission guide](https://developers.openai.com/plugins/deploy/submission) and [plugin packaging guide](https://developers.openai.com/plugins/build/plugins) @@ -163,7 +185,7 @@ synthetic fixture from the final reviewed commit: ```bash python3 scripts/build_release.py --verify-reproducible --require-clean -python3 scripts/build_release.py --verify dist/sourcebraid-release-v1.0.1 +python3 scripts/build_release.py --verify dist/sourcebraid-release-v1.1.0 ``` Use the generated `RELEASE_MANIFEST.json` and `SHA256SUMS` as the artifact and @@ -171,6 +193,11 @@ digest record. Do not hard-code a ZIP digest in documentation: runtime changes require rebuilding and verifying the bundle. The builders export only explicit public allowlists, never broad copies or private repository history. +During local preparation, omit `--require-clean` to build an inspectable +candidate with uncommitted changes. Its provenance records modified release +inputs. Rebuild from the reviewed clean commit before publishing; a locally +verified candidate is not a released artifact. + ## iOS archive Confirm that the SourceBraid bundle identifiers, App Group, and Keychain access @@ -202,7 +229,7 @@ with the same embedded allowlist: ```bash python3 scripts/build_setup_package.py -python3 dist/sourcebraid-github-setup-v1.0.1.py --help +python3 dist/sourcebraid-github-setup-v1.1.0.py --help ``` The setup and builder must avoid printing or embedding credentials and must @@ -218,7 +245,7 @@ GitHub with both paths included in the sparse checkout: ```bash codex plugin marketplace add patrickschiller/sourcebraid \ - --ref v1.0.1 \ + --ref v1.1.0 \ --sparse .agents/plugins \ --sparse codex-plugin/sourcebraid codex plugin add sourcebraid@sourcebraid @@ -233,9 +260,10 @@ Do not advertise that command as live before the referenced tag exists. 2. Record the passing check results and package SHA-256 digests. 3. Create an annotated version tag on the reviewed public commit. 4. Push the tag to the public repository. -5. Attach the Chrome ZIP, hosted MCP source ZIP, optional local skills ZIP, - standalone GitHub setup script, synthetic fixture, and generated digest - record to the corresponding GitHub release when artifacts are distributed. +5. Attach the Chrome ZIP, ChatGPT portal plugin ZIP, hosted MCP source ZIP, + optional local skills ZIP, standalone GitHub setup script, synthetic fixture, + and generated digest record to the corresponding GitHub release when + artifacts are distributed. 6. Verify that every published archive can be traced back to the tagged commit. Store review and publication remain separate maintainer actions. A GitHub tag or diff --git a/chatgpt-mcp/package-lock.json b/chatgpt-mcp/package-lock.json index 2e6a5b1..f4891fe 100644 --- a/chatgpt-mcp/package-lock.json +++ b/chatgpt-mcp/package-lock.json @@ -1,12 +1,12 @@ { "name": "sourcebraid-chatgpt-mcp", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sourcebraid-chatgpt-mcp", - "version": "1.0.1", + "version": "1.1.0", "dependencies": { "@cloudflare/workers-oauth-provider": "0.10.3" }, diff --git a/chatgpt-mcp/package.json b/chatgpt-mcp/package.json index 89b18fb..2ae9419 100644 --- a/chatgpt-mcp/package.json +++ b/chatgpt-mcp/package.json @@ -1,6 +1,6 @@ { "name": "sourcebraid-chatgpt-mcp", - "version": "1.0.1", + "version": "1.1.0", "private": true, "type": "module", "engines": { "node": ">=22.13.0" }, diff --git a/chatgpt-mcp/src/archive.mjs b/chatgpt-mcp/src/archive.mjs index 4e9df4a..23fae6c 100644 --- a/chatgpt-mcp/src/archive.mjs +++ b/chatgpt-mcp/src/archive.mjs @@ -172,9 +172,10 @@ function metadata(text, path, archive, head) { const repositoryUrl = `https://github.com/${archive.repo}/blob/${head}/${path.split('/').map(encodeURIComponent).join('/')}`; let sourceUrl = ''; try { const url = new URL(fields.url); if (['https:', 'http:'].includes(url.protocol) && !url.username && !url.password) sourceUrl = url.href; } catch { /* Source provenance can be absent. */ } + const capturedDate = [fields.date, fields.capture_date, fields.captured_at].find(value => typeof value === 'string' && value) || ''; return { title: typeof fields.title === 'string' && fields.title ? fields.title : path, url: sourceUrl || repositoryUrl, source_url: sourceUrl, repository_url: repositoryUrl, - date: typeof fields.date === 'string' ? fields.date : '', + date: capturedDate, source: typeof fields.source === 'string' ? fields.source : typeof fields.site === 'string' ? fields.site : '', tags: Array.isArray(fields.tags) ? fields.tags.filter(tag => typeof tag === 'string') : [] }; } diff --git a/chatgpt-mcp/src/auth.mjs b/chatgpt-mcp/src/auth.mjs index 8ce5dc6..ac0677f 100644 --- a/chatgpt-mcp/src/auth.mjs +++ b/chatgpt-mcp/src/auth.mjs @@ -1,7 +1,7 @@ import { ArchiveError, connectArchive } from './archive.mjs'; export const READ_SCOPE = 'archive:read'; -export const VERSION = '1.0.1'; +export const VERSION = '1.1.0'; const COOKIE = '__Host-SourceBraidConsent'; const CONSENT_TTL = 600; const enc = new TextEncoder(); diff --git a/chatgpt-mcp/tests/archive.test.mjs b/chatgpt-mcp/tests/archive.test.mjs index 9035406..47401a0 100644 --- a/chatgpt-mcp/tests/archive.test.mjs +++ b/chatgpt-mcp/tests/archive.test.mjs @@ -68,6 +68,33 @@ test('search returns saved provenance and fetch returns complete verified Markdo assert.equal(fetched.metadata.head_sha, HEAD); }); +test('iOS capture dates survive listing and fetch with local-plugin precedence', async () => { + const timestamp = '2026-10-15T07:45:12.345Z'; + const cases = [ + { fields: `capture_date: "2026-10-15"\ncaptured_at: "${timestamp}"`, expected: '2026-10-15' }, + { fields: `date: "2026-10-14"\ncapture_date: "2026-10-15"\ncaptured_at: "${timestamp}"`, expected: '2026-10-14' }, + { fields: `date: ""\ncapture_date: "2026-10-15"\ncaptured_at: "${timestamp}"`, expected: '2026-10-15' }, + { fields: `captured_at: "${timestamp}"`, expected: timestamp }, + { fields: `date: ""\ncapture_date: ""\ncaptured_at: "${timestamp}"`, expected: timestamp }, + { fields: 'publication_date: "2026-10-10"', expected: '' }, + ]; + for (const { fields, expected } of cases) { + const text = `---\ntitle: "Synthetic iOS capture"\nurl: "https://example.com/ios-capture"\n${fields}\ncapture_method: "ios-share-safari"\ntags: ["review"]\n---\nSynthetic saved article.\n`; + const entry = { ...clip('ios-capture.md'), size: Buffer.byteLength(text) }; + const fixture = mockGitHub({ + [`/repos/${props.repo}/git/trees/${ROOT}`]: { tree: [entry], truncated: false }, + [`/repos/${props.repo}/git/trees/${ROOT}?recursive=1`]: { tree: [entry], truncated: false }, + [`/repos/${props.repo}/git/blobs/${CLIP}`]: { size: entry.size, encoding: 'base64', content: Buffer.from(text).toString('base64') }, + }); + const listing = await callArchiveTool('sourcebraid_list', {}, props, fixture.fetch); + assert.equal(listing.sources[0].date, expected, fields); + const fetched = await callArchiveTool('fetch', { id: 'web-clips/ios-capture.md' }, props, fixture.fetch); + assert.equal(fetched.metadata.date, expected, fields); + assert.equal(fetched.text, text); + assert.equal(fetched.url, 'https://example.com/ios-capture'); + } +}); + test('private repository and identity are revalidated on every call', async () => { for (const overrides of [{ [`/repos/${props.repo}`]: { private: false, full_name: props.repo } }, { '/user': { id: 43, login: 'other-user' } }]) { const fixture = mockGitHub(overrides); diff --git a/chatgpt-plugin/sourcebraid/mcp.json b/chatgpt-plugin/sourcebraid/mcp.json new file mode 100644 index 0000000..b835f9a --- /dev/null +++ b/chatgpt-plugin/sourcebraid/mcp.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "sourcebraid": { + "type": "streamable-http", + "url": "https://mcp.sourcebraid.com/mcp", + "extensions": { + "com.openai": { + "auth": { + "type": "oauth", + "client": { "mode": "dcr" }, + "baseScopes": ["archive:read"] + } + } + } + } + } +} diff --git a/chatgpt-plugin/sourcebraid/plugin.json b/chatgpt-plugin/sourcebraid/plugin.json new file mode 100644 index 0000000..cc42fc4 --- /dev/null +++ b/chatgpt-plugin/sourcebraid/plugin.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "sourcebraid", + "version": "1.1.0", + "description": "Search and read sources in your private GitHub Markdown archive through SourceBraid's read-only hosted MCP service.", + "author": { + "name": "Patrick Schiller", + "url": "https://github.com/patrickschiller" + }, + "homepage": "https://sourcebraid.com", + "repository": "https://github.com/patrickschiller/sourcebraid", + "license": "MIT", + "extensions": { + "com.openai": { + "interface": { + "displayName": "SourceBraid", + "shortDescription": "Search your Markdown archive.", + "longDescription": "Search, list and read saved sources in one private GitHub Markdown archive you control. Original source links remain available for citations. Connect through SourceBraid's HTTPS consent page with a dedicated fine-grained GitHub token restricted to Contents: Read-only for one repository; never paste a token into chat. Search results disclose indexing and coverage limits, and documents are limited to 200 KiB. This connection cannot write, delete or build a local index. Requested sources pass through SourceBraid on Cloudflare to ChatGPT. OAuth grant data, including encrypted GitHub credentials, is stored in Cloudflare KV; there is no permanent SourceBraid content database. Markdown files and Git history remain authoritative.", + "developerName": "Patrick Schiller", + "category": "Productivity", + "capabilities": ["Read"], + "websiteURL": "https://sourcebraid.com", + "supportURL": "https://github.com/patrickschiller/sourcebraid/issues", + "privacyPolicyURL": "https://github.com/patrickschiller/sourcebraid/blob/main/PRIVACY.md", + "termsOfServiceURL": "https://github.com/patrickschiller/sourcebraid/blob/main/TERMS.md", + "defaultPrompt": [ + "Search my SourceBraid archive for Markdown ownership and cite the sources.", + "List sources in my SourceBraid archive and help me choose one to read.", + "Find my saved sources about retrieval and summarize them with original links." + ], + "brandColor": "#166B68", + "composerIcon": "./assets/sourcebraid-icon.png", + "logo": "./assets/sourcebraid-icon.png" + }, + "publication": { + "translations": { + "de": { + "subtitle": "Durchsuche dein Markdownarchiv", + "description": "Durchsuche und lies gespeicherte Quellen in einem privaten GitHub-Markdown-Archiv mit ursprünglichen Quell-Links. Verbinde es auf der HTTPS-Zustimmungsseite von SourceBraid mit einem eigenen Fine-grained-GitHub-Token: nur Contents-Leserechte für genau ein Repository, niemals im Chat eingeben. Die Suche weist Index- und Abdeckungsgrenzen aus; Dokumente sind auf 200 KiB begrenzt. Diese Verbindung schreibt, löscht und indexiert nicht lokal. Angeforderte Quellen durchlaufen SourceBraid auf Cloudflare und ChatGPT. OAuth-Grant-Daten einschließlich verschlüsselter GitHub-Zugangsdaten liegen in Cloudflare KV; eine dauerhafte SourceBraid-Inhaltsdatenbank gibt es nicht. Markdown-Dateien und Git-Historie bleiben maßgeblich." + } + } + } + } + } +} diff --git a/chrome-extension/STORE_LISTING.md b/chrome-extension/STORE_LISTING.md index 871fac6..6103894 100644 --- a/chrome-extension/STORE_LISTING.md +++ b/chrome-extension/STORE_LISTING.md @@ -161,7 +161,7 @@ the archive view uses the real Markdown and path builders. ## Submission sequence -Build `dist/sourcebraid-chrome-v1.0.1.zip` through `python3 scripts/build_chrome_package.py` +Build `dist/sourcebraid-chrome-v1.1.0.zip` through `python3 scripts/build_chrome_package.py` or the full `python3 scripts/build_release.py` command. Upload that ZIP to the [Chrome Developer Dashboard](https://chrome.google.com/webstore/devconsole). Complete the listing, privacy practices and protected test instructions above; diff --git a/chrome-extension/sourcebraid/manifest.json b/chrome-extension/sourcebraid/manifest.json index d01393b..f5e32eb 100644 --- a/chrome-extension/sourcebraid/manifest.json +++ b/chrome-extension/sourcebraid/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "SourceBraid", - "version": "1.0.1", + "version": "1.1.0", "description": "__MSG_extensionDescription__", "default_locale": "en", "homepage_url": "https://sourcebraid.com", diff --git a/codex-plugin/sourcebraid/.codex-plugin/plugin.json b/codex-plugin/sourcebraid/.codex-plugin/plugin.json index 9506cfc..55ae9c8 100644 --- a/codex-plugin/sourcebraid/.codex-plugin/plugin.json +++ b/codex-plugin/sourcebraid/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "sourcebraid", - "version": "1.0.1", + "version": "1.1.0", "description": "Search, fetch, index, and safely manage a private SourceBraid Markdown archive using a local Python runtime in Codex.", "author": { "name": "Patrick Schiller", @@ -21,7 +21,7 @@ "mcpServers": "./.mcp.json", "interface": { "displayName": "SourceBraid", - "shortDescription": "Search your private Markdown knowledge archive.", + "shortDescription": "Search your Markdown archive.", "longDescription": "Search and fetch saved Markdown sources from a private GitHub archive in Codex, maintain a local full-text index, and safely preview and delete clips. Requires Python 3 and local GitHub authentication. The hosted read-only ChatGPT connection is a separate distribution.", "developerName": "Patrick Schiller", "category": "Productivity", @@ -31,6 +31,7 @@ "Write" ], "websiteURL": "https://sourcebraid.com", + "supportURL": "https://github.com/patrickschiller/sourcebraid/issues", "privacyPolicyURL": "https://github.com/patrickschiller/sourcebraid/blob/main/PRIVACY.md", "termsOfServiceURL": "https://github.com/patrickschiller/sourcebraid/blob/main/TERMS.md", "defaultPrompt": [ diff --git a/codex-plugin/sourcebraid/scripts/sourcebraid_mcp.py b/codex-plugin/sourcebraid/scripts/sourcebraid_mcp.py index 96d74ae..11a31b1 100755 --- a/codex-plugin/sourcebraid/scripts/sourcebraid_mcp.py +++ b/codex-plugin/sourcebraid/scripts/sourcebraid_mcp.py @@ -19,7 +19,7 @@ SERVER_NAME = "sourcebraid" -SERVER_VERSION = "1.0.1" +SERVER_VERSION = "1.1.0" PROTOCOL_VERSION = "2025-06-18" DEFAULT_SEARCH_LIMIT = 10 diff --git a/docs/CHATGPT_MCP_DEPLOYMENT.md b/docs/CHATGPT_MCP_DEPLOYMENT.md index 1d0d2de..a9d6ef3 100644 --- a/docs/CHATGPT_MCP_DEPLOYMENT.md +++ b/docs/CHATGPT_MCP_DEPLOYMENT.md @@ -1,6 +1,6 @@ # SourceBraid ChatGPT service — deployment / Bereitstellung -Release candidate: **1.0.1**. The repository prepares a Cloudflare Worker for +Release candidate: **1.1.0**. The repository prepares a Cloudflare Worker for `https://mcp.sourcebraid.com/mcp`. This is a deployment target, **not a claim that the service or directory listing is already live**. Deploying creates paid or metered infrastructure; the maintainer must approve the account and domain. @@ -46,12 +46,19 @@ npm test npm run build ``` -Or extract the checked `sourcebraid-chatgpt-mcp-v1.0.1.zip` into a new directory +Or extract the checked `sourcebraid-chatgpt-mcp-v1.1.0.zip` into a new directory and run the same npm commands there. The ZIP has `package.json` at its root. `npm run build` is a **dry run** and does not publish. Source archives contain neither installed dependencies nor credentials. The release's `SHA256SUMS` and `RELEASE_MANIFEST.json` identify the actual reviewed files. +The separate `sourcebraid-chatgpt-plugin-v1.1.0.zip` is the OpenAI portal upload. +It contains root `plugin.json`, `mcp.json`, and the listing icon; it connects to +the deployed service and does not contain or deploy the Worker. The complete +release builder creates both packages. After live acceptance, follow the +[submission kit](../marketing/OPENAI_MCP_SUBMISSION.md) to upload the plugin ZIP, +verify the domain, connect OAuth, and inspect the automated metadata/tool checks. + The locked development toolchain follows stable Wrangler `4.131.1`, including its own pinned Miniflare `5.20260911.0-alpha` dependency. Miniflare is only the local test runtime, not a production dependency; keep its integration test green @@ -111,7 +118,7 @@ curl --include --request POST https://mcp.sourcebraid.com/mcp \ --data '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` -Expect healthy version `1.0.1`, HTTPS OAuth endpoints, resource exactly +Expect healthy version `1.1.0`, HTTPS OAuth endpoints, resource exactly `https://mcp.sourcebraid.com/mcp`, scope `archive:read`, and **401 with WWW-Authenticate** for the unauthenticated request. Health checks do not prove GitHub authorization works. @@ -193,6 +200,9 @@ zusätzlich zu den Bedingungen der beteiligten Dienste. 2. Mit Node.js ab 22.13 im Ordner `chatgpt-mcp` die oben aufgeführten Befehle `npm ci`, `npm test`, `npm run build` ausführen. Alternativ das geprüfte MCP-Quell-ZIP in einen neuen Ordner entpacken. Der Build veröffentlicht nichts. + Das separate `sourcebraid-chatgpt-plugin-v1.1.0.zip` mit `plugin.json`, + `mcp.json` und Icon ist für das OpenAI-Portal bestimmt. Es verbindet sich mit + dem bereitgestellten Dienst und enthält keinen Worker-Quellcode. Die gepinnte stabile Wrangler-Toolchain enthält eine als Alpha bezeichnete Miniflare-Testlaufzeit; diese wird nicht als Produktionsabhängigkeit ausgeliefert. 3. In `wrangler.jsonc` HTTPS-Origin/Route, persistente `OAUTH_KV`-Zuordnung und @@ -209,7 +219,8 @@ zusätzlich zu den Bedingungen der beteiligten Dienste. alle Reviewer-Fälle, zwei getrennte GitHub-Identitäten, Token-Erneuerung, Trennen/Neuverbindung und Widerruf live in ChatGPT prüfen. Commit, Worker-Version und Ergebnisse ohne Geheimnisse dokumentieren. Erst danach - scannen und einreichen. + das Portal-ZIP hochladen, Domain und OAuth-Verbindung prüfen, Metadaten und + Werkzeuge scannen und gemäß Einreichungskit zur Prüfung einreichen. ### Grenzen und Betrieb diff --git a/docs/CHATGPT_PLUGIN.md b/docs/CHATGPT_PLUGIN.md index dce5476..05c64d1 100644 --- a/docs/CHATGPT_PLUGIN.md +++ b/docs/CHATGPT_PLUGIN.md @@ -2,8 +2,8 @@ SourceBraid has two runtime paths: a hosted, read-only MCP service for ChatGPT and the complete local Codex plugin for search, index maintenance, and guarded -archive changes. The hosted service is prepared in `chatgpt-mcp/` but has not -been deployed or approved for a public OpenAI listing. +archive changes. Both target release **1.1.0**. The hosted service is prepared in +`chatgpt-mcp/` but has not been deployed or approved for a public OpenAI listing. ## Primary ChatGPT release: hosted MCP @@ -14,6 +14,15 @@ verified production service as **With MCP**, with the actual HTTPS endpoint, domain verification, authentication setup, and protected review credentials. A local build or a planned URL is not evidence that the service is live. +Build the release bundle with `python3 scripts/build_release.py`, or build the +portal package alone with `python3 scripts/build_chatgpt_plugin_package.py`. Upload +`sourcebraid-chatgpt-plugin-v1.1.0.zip` to the OpenAI plugin portal; its root +`plugin.json` and `mcp.json` declare the listing and remote MCP connection, and +the ZIP includes the referenced icon. Deploy the Worker from +`sourcebraid-chatgpt-mcp-v1.1.0.zip` separately. Complete domain verification, +OAuth connection, metadata/tool checks, and reviewer details before submitting +for review. The local skills ZIP described below serves a separate runtime. + After deployment, the connection flow uses OAuth 2.1 authorization codes with PKCE S256. The user enters a fine-grained GitHub PAT on SourceBraid's HTTPS consent page, restricted to **Contents: Read-only** for exactly one private @@ -64,12 +73,12 @@ and [authentication guide](https://developers.openai.com/plugins/build/auth). ## Codex installation from GitHub -After the reviewed `v1.0.1` tag is published, add the tag-bound GitHub +After the reviewed `v1.1.0` tag is published, add the tag-bound GitHub marketplace and install the plugin: ```bash codex plugin marketplace add patrickschiller/sourcebraid \ - --ref v1.0.1 \ + --ref v1.1.0 \ --sparse .agents/plugins \ --sparse codex-plugin/sourcebraid codex plugin add sourcebraid@sourcebraid @@ -89,8 +98,8 @@ flags, SourceBraid environment variables, or Read-only tasks need repository read access. Guarded deletion or metadata migration additionally needs Contents write access and fresh confirmation. The -local SQLite cache is separated by repository, branch, and root folder; this -release rebuilds it automatically under its safer cache namespace. Requested +local SQLite cache is separated by repository, branch, and root folder. Older +cache layouts trigger a fresh initial build under the current namespace. Requested results are processed by the active Codex environment under its terms and data settings. Local archive copies are not uploaded to the hosted MCP service. @@ -102,8 +111,8 @@ ordinary ChatGPT to a private archive. It is not the primary ChatGPT submission. ```bash python3 scripts/build_plugin_package.py -unzip -l dist/sourcebraid-plugin-skills-v1.0.1.zip -shasum -a 256 dist/sourcebraid-plugin-skills-v1.0.1.zip +unzip -l dist/sourcebraid-plugin-skills-v1.1.0.zip +shasum -a 256 dist/sourcebraid-plugin-skills-v1.1.0.zip ``` Its local review material remains in @@ -120,12 +129,19 @@ local Codex cache. Local credentials and caches can be removed separately. ## Deutsch Die primäre ChatGPT-Anbindung ist ein gehosteter MCP-Dienst mit reinem -Lesezugriff. `https://mcp.sourcebraid.com/mcp` ist als Ziel vorbereitet, aber -noch nicht bereitgestellt oder öffentlich freigegeben. Nach Bereitstellung -erfolgt die Verbindung über OAuth 2.1 mit PKCE S256. Der Fine-grained-GitHub-PAT +Lesezugriff für Version **1.1.0**. `https://mcp.sourcebraid.com/mcp` ist als Ziel +vorbereitet, aber noch nicht bereitgestellt oder öffentlich freigegeben. Nach +Bereitstellung erfolgt die Verbindung über OAuth 2.1 mit PKCE S256. Der Fine-grained-GitHub-PAT gehört ausschließlich auf die HTTPS-Zustimmungsseite von SourceBraid und erhält Contents-Leserechte für genau ein privates Repository; niemals in den Chat. +Im OpenAI-Portal wird `sourcebraid-chatgpt-plugin-v1.1.0.zip` mit `plugin.json`, +`mcp.json` und Icon hochgeladen. `sourcebraid-chatgpt-mcp-v1.1.0.zip` liefert +separat den Worker-Quellcode zur Bereitstellung. Beide Dateien entstehen mit +`python3 scripts/build_release.py`. Vor der Einreichung müssen Domainprüfung, +OAuth-Verbindung, Metadaten-/Werkzeugprüfungen und Reviewer-Angaben vollständig +sein. Der Paketbau veröffentlicht weder Dienst noch Verzeichniseintrag. + Die gehosteten Werkzeuge suchen, rufen Quellen ab, listen sie auf und zeigen den Status. Suchgrenzen und unvollständige Ergebnisse werden ausgewiesen. Löschen, Schreiben und lokales Indexieren bleiben dem lokalen Codex-Plugin diff --git a/docs/IOS_ONBOARDING_INSTALLER_PLAN.de.md b/docs/IOS_ONBOARDING_INSTALLER_PLAN.de.md new file mode 100644 index 0000000..cb3ea45 --- /dev/null +++ b/docs/IOS_ONBOARDING_INSTALLER_PLAN.de.md @@ -0,0 +1,855 @@ +# SourceBraid: Installer-Plan für iOS und iPadOS nach Version 1.1.0 + +**SourceBraid — Weave the web into Markdown.** + +Stand: **15. Oktober 2026 · Revision 2** · Basis: **iOS/iPadOS 1.1.0, Build 11** +· Status: **Neuer Umsetzungsplan; Installer noch nicht implementiert** +· [English version](IOS_ONBOARDING_INSTALLER_PLAN.md) + +Diese Fassung ersetzt den Entwurf vom 12. Oktober. Der Maintainer hat die +App-Store-Veröffentlichung von 1.1.0 bestätigt. Geprüfte Quellbasis ist der +Tag `ios-v1.1.0`, Commit `e55f1fc17483096c4c05286713d6725b951d4d06`, verglichen +mit `bb7c77a`. Eine Versionsnummer für den nächsten Installer-Release wird +hier nicht vorweggenommen. + +## 1. Empfehlung und Ziel + +**Die veröffentlichte iPhone-/iPad-App wird zum zentralen Einrichtungsweg.** Der +„Installer“ ist in erster Linie ein geführter Einrichtungsassistent in der App. +Er verbindet GitHub, legt ein privates Archiv an oder verbindet ein vorhandenes, +prüft dessen Nutzbarkeit und führt zum ersten gespeicherten Beitrag. Chrome +und ChatGPT/Codex werden anschließend als freiwillige Ergänzungen angeboten. + +Das Ziel für neue Nutzer lautet: **App installieren → bei GitHub anmelden → +privates Archiv bestätigen → ersten Inhalt speichern.** Niemand muss dafür +Git, einen Branch, einen Personal Access Token oder ein Terminal verstehen. +GitHub-Konto, Anmeldung und ausdrückliche Freigaben bleiben erforderlich; +SourceBraid automatisiert weder Konto-Verifikation noch Zwei-Faktor-Anmeldung. + +Empfohlene technische Richtung: + +- Eine vom Maintainer registrierte **GitHub App**, die Nutzer für ihr Archiv + freigeben. Nutzer erstellen keine eigene Entwickler-App. +- Browser-Anmeldung aus iOS mit PKCE und einem kleinen SourceBraid-Dienst für + den sicheren Austausch und die Erneuerung von Zugangsdaten. +- Direkte Übertragung der Captures zwischen iOS und GitHub. Markdown und + Git-Historie bleiben maßgeblich; lokale Datenbanken sind wiederaufbaubare Caches. +- Automatische Erstellung eines privaten Archivs nach einem frühen technischen + Nachweis; ein geführter GitHub-Formularweg bleibt als einfachere erste Stufe. +- Bestehende PAT-Konfigurationen funktionieren weiter. Die manuelle Eingabe + wandert unter „Erweiterte Einrichtung“. + +Reader, Suche, Chat-Oberfläche und Aktualisierung aus 1.1.0 werden weiterverwendet. +Der Installer ergänzt ihren Einstieg und ihre Verbindungsverwaltung. Ein +Update darf bestehende Nutzer nicht erneut durch eine Pflichteinrichtung führen. + +Dieses Dokument plant die Arbeit. Es registriert keine GitHub App, betreibt +keinen neuen Dienst und ändert keine bestehenden Datenschutz-Zusagen. Die +[Release-Hinweise](IOS_RELEASE_1.1.0.de.md), der +[Reader-Plan](IOS_READER_INTELLIGENCE_DUO_PLAN.de.md) und die +[offenen Geräteprüfungen](IOS_DEVICE_ACCEPTANCE.de.md) bleiben Referenzen für +den bestehenden Funktionsumfang. + +## 2. Ausgangslage im Repository + +### Release-Abgrenzung und Änderungen seit dem alten Plan + +Die iOS-Laufzeitquellen stimmen mit `ios-v1.1.0` überein. Der Release umfasst +58 geänderte Dateien gegenüber dem Vorgänger. Neu nutzbare Grundlage sind: + +- „Neueste“ mit sieben Beiträgen, Markdown-Reader und lokale Volltextsuche. +- Cache-Start ohne erneuten Download aller Texte; ein leerer Cache lädt zunächst + höchstens sieben Artikeltexte. Suchabdeckung darf ausdrücklich teilweise sein. +- Begrenzte Hintergrundaktualisierung sowie Abbruch- und Cache-Koordination. +- Adaptive Navigation für iPhone/iPad, optionaler lokaler Chat und lesende + Kurzbefehle. Apple Intelligence bleibt unabhängig von der Einrichtung. + +Die Share Extension war bereits vorhanden. GitHub-Anmeldung, Repository- +Erstellung und Token-Erneuerung wurden in 1.1.0 **nicht** ergänzt. + +App-Store-Veröffentlichung: durch den Maintainer bestätigt. Quellversion: +durch Git-Tag, Versionsfelder und lokalen Build geprüft. Daraus folgt keine +eigenständige Prüfung des im Store ausgelieferten Binärpakets. Im iOS-Tag sind +Chrome, Codex und MCP noch auf 1.0.1. Die während der Überprüfung separat +eingecheckten Folgearbeiten in `467542a` bereiten diese Komponenten auf 1.1.0 +vor; die iOS-Laufzeitquellen bleiben gegenüber dem iOS-Tag unverändert. +Dieser zusätzliche Commit belegt keine öffentliche Plugin-Verfügbarkeit. +Die Folgearbeiten werden durch diesen Plan nicht verändert. + +| Bereich | Heute vorhanden | Für einfache Einrichtung fehlt | +| --- | --- | --- | +| [iOS-Einstellungen](../ios/SourceBraid/App/SettingsView.swift), [ViewModel](../ios/SourceBraid/App/SettingsViewModel.swift) | Formular für Owner, Repository, Branch, Archivordner und Token | Geführte Anmeldung, Archivwahl, Erstellung, verständliche Zustände | +| [GitHubClient](../ios/SourceBraid/Shared/GitHubClient.swift) | Repository-Abfrage, Capture-Schreibzugriff | Vollständige Prüfung von Branch, Privatsphäre, Schreibfähigkeit und PDF-Unterstützung | +| [Konfiguration](../ios/SourceBraid/Shared/SourceBraidConfiguration.swift), [Keychain](../ios/SourceBraid/Shared/KeychainStore.swift) | Gemeinsame Einstellungen und ein Token für App/Share Extension | Authentifizierungstyp, Ablauf, Refresh, Kontobindung und sichere Migration | +| [Reader](../ios/SourceBraid/Archive/ArchiveLibrary.swift) | Veröffentlichtes Lesen, Suche, Cache-Start und Neueste sieben | Neues leeres Archiv korrekt behandeln; ersten Capture gezielt zurücklesen | +| [Hintergrundaktualisierung](../ios/SourceBraid/Archive/ArchiveBackgroundRefresh.swift) | Begrenzte Abrufe und koordinierte Cache-Schreibvorgänge | Verbindungsstatus über Neustarts beachten; erneuerbare Credentials integrieren | +| [Kurzbefehle](../ios/SourceBraid/Archive/ArchiveIntents.swift) | Suche und Öffnen bereits geladener Einträge | Neues Credential-Modell übernehmen, lokale Identitäten und Offline-Verhalten erhalten | +| [Setup-Skript](../scripts/setup_github.py), [Paketbau](../scripts/build_setup_package.py) | Privates Repository, Support-Dateien und Konfiguration ohne Token | Python und angemeldetes `gh` sind nötig; am Ende bleibt die manuelle PAT-Erstellung | +| [Chrome-Erweiterung](../chrome-extension/sourcebraid/popup.js) | Manuelle Einstellungen, Capture und Export der Plugin-Konfiguration ohne Token | Anmeldung und Import einer von iOS vorbereiteten Konfiguration | +| [Gehostetes ChatGPT](../chatgpt-mcp/src/auth.mjs) | Vorbereiteter OAuth-Dienst mit nutzerbezogenen Grants | GitHub-Anmeldung statt separatem PAT; Deployment und öffentliche Freigabe | +| [Lokales Codex-Plugin](../codex-plugin/sourcebraid/scripts/sourcebraid.py) | Python-Standardbibliothek, lokale Suche, vorhandene GitHub-Zugangsdaten | Optional vereinfachter Konfigurationsimport; kein Bestandteil des iOS-Grundsetups | + +### Konkrete Befunde für die Umsetzung + +1. „Verbindung testen“ speichert derzeit die neue Konfiguration vor der + Netzwerkprüfung. Ein fehlgeschlagener Versuch kann dadurch das bisherige + Archiv umstellen und dessen lokalen Cache entfernen. Der Assistent braucht + einen Entwurf, der erst nach erfolgreicher Prüfung aktiv wird. +2. Die aktuelle Verbindungsprüfung liest nur Repository-Metadaten. Das beweist + weder einen vorhandenen Branch noch einen erfolgreichen Capture. „Verbunden“, + „Lesen möglich“ und „Speichern bestätigt“ müssen getrennte Zustände werden. +3. Während der Prüfung bleiben Felder und Speichern bedienbar. Die Erfolgsmeldung + verwendet nach dem Netzwerkaufruf die dann aktuellen Eingaben. Sie kann damit + ein anderes Repository als das geprüfte nennen. Ein unveränderlicher Entwurf + mit Versionsprüfung muss Anfrage, Ergebnis und Aktivierung zusammenhalten. +4. Ein fehlender Archivordner erzeugt weiterhin einen 404-Fehler. Die neue + Leerliste repariert das nicht. Bootstrap und echter Zustand „noch keine + Beiträge“ müssen von fehlendem Zugriff oder falschem Branch getrennt werden. +5. Nach einem Capture stellt die Rückkehr in die App einen vorhandenen Cache + wieder her, ohne automatisch einen neuen GitHub-Stand zu laden. Der Assistent + muss den ersten Capture gezielt abrufen; auf Hintergrundaktualisierung zu + warten ist kein verlässlicher Abschluss. +6. Die Sperre gegen Hintergrundabrufe nach Abmeldung liegt derzeit nur im + Arbeitsspeicher. Gespeicherte Repository-Felder bleiben erhalten; nach einem + Neustart kann die Planung wieder anlaufen, obwohl das Token entfernt wurde. + Ein dauerhafter Verbindungsstatus muss die Planung und Ausführung begrenzen. + +Die Befunde liegen in `SettingsViewModel.testConnection`, +`GitHubClient.testConnection`, `ArchiveReadClient.snapshot`, +`ArchiveLibrary.configure` und `ArchiveBackgroundRefresh.schedule`. +Sie sind Prüfergebnisse dieses Plans und wurden hier nicht im Produktcode behoben. + +### Nachweis dieser Überprüfung + +- Quellvalidator: **1.1.0/Build 11 bestanden**. +- Unsignierter generischer Simulator-Build mit **Xcode 27.1 / 27A9275 bestanden**; + das erzeugte App-Bundle besteht ebenfalls den Validator. +- Aktueller iPad-Testlauf und seine Grenzen werden in Abschnitt 12 festgehalten. +- Frühere Zahlen aus dem [Release-Protokoll](IOS_RELEASE_1.1.0.de.md) bleiben + historische Nachweise. Sie werden nicht als erneut ausgeführte Tests ausgegeben. + +Das Setup-Skript ist eine gute Vorlage für erlaubte Dateien und Wiederholbarkeit. +Seine pauschale Aktivierung von GitHub Actions mit `allowed_actions: all` soll +jedoch **nicht** in den neuen Assistenten übernommen werden. + +## 3. Was der Installer leisten soll + +### Pflichtumfang der ersten nutzerfreundlichen Version + +- Einstieg ohne Terminal, Python oder GitHub CLI. +- Erklärung in Alltagssprache: „Dein Archiv ist ein privater Ordner bei GitHub. + Die Dateien gehören dir und bleiben unabhängig von SourceBraid nutzbar.“ +- Anmeldung bei GitHub, verständliche Repository-Freigabe, Kontoanzeige. +- Neues privates Archiv oder vorhandenes Archiv verbinden. +- Technische Felder automatisch ermitteln; nur bei Bedarf unter „Erweitert“. +- Initialisierung, Wiederaufnahme nach Abbruch und Prüfung mit konkreten + Handlungsoptionen bei Fehlern. +- Einführung in die iOS-Teilen-Funktion und erster echter Capture mit Rücklesen. +- Ein später erreichbarer Bereich „Verbindungen“ für Erneuerung und Reparatur. +- Vollständige deutsche und englische Texte, VoiceOver und Dynamic Type. +- Bestandsnutzer direkt zum Reader führen; PAT-Migration freiwillig anbieten. +- Ein iPad ohne vorherige iPhone-Einrichtung vollständig einrichten können. +- Auf einem zweiten Gerät das vorhandene Archiv verbinden, statt ein Duplikat + anzulegen. Jede Installation meldet sich eigenständig bei GitHub an. + +### Bewusst später + +PDF-Konvertierung, Chrome-Verknüpfung, gehostetes ChatGPT und lokale +Codex-Einrichtung dürfen den erfolgreichen Grundstart nicht blockieren. Auch +Apple Intelligence ist keine Voraussetzung. Ein separater Desktop-Installer +ist zunächst nachrangig; das vorhandene Skript bleibt für technische Nutzer und +als unabhängiger Weg ohne iPhone erhalten. Android bleibt Roadmap. + +## 4. Anmeldung: Optionen und Entscheidung + +| Option | Vorteil | Aufwand und Einschränkung | Einordnung | +| --- | --- | --- | --- | +| Geführter Fine-grained PAT | Kleine Änderung; vorhandene Clients bleiben kompatibel | Token muss weiter erstellt, kopiert und später ersetzt werden | Übergang und Expertenweg | +| GitHub App, Browser-Flow + PKCE + Auth-Dienst | Verständliche Anmeldung; gezielte Repository-Freigabe; automatische Erneuerung | Betrieb eines kleinen Dienstes und neuer Umgang mit Zugangsdaten | Empfohlenes Produktziel | +| GitHub App, Device Flow | Kein App-Secret im Client; kein eigener Token-Austauschdienst notwendig | Codeeingabe, Polling und erhöhte Phishing-Angriffsfläche | Begründete Alternative prüfen, kein iOS-Standard | +| OAuth App | Bekannter Anmeldeweg | Private Repositories benötigen typischerweise den breiten `repo`-Zugriff | Gegenüber GitHub App nachrangig | +| Desktop-Skript mit `gh auth login` | Vieles bereits vorhanden | Zusätzliche Installation und Terminalwissen | Ergänzung für Fortgeschrittene | + +Zum Vergleich der OAuth-App-Reichweite: +[GitHub: OAuth-Scopes](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps). + +GitHub dokumentiert PKCE im Web-Flow, verlangt beim Code-Austausch aber weiterhin +ein `client_secret`. Deshalb verspricht der Plan keinen rein nativen, +secretfreien Web-Flow. Der Auth-Dienst ist unsere Architekturentscheidung, um +dieses Secret serverseitig zu halten. +[GitHub: User-Access-Token-Flow](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-web-application-flow-to-generate-a-user-access-token) + +GitHub bevorzugt PKCE gegenüber Device Flow und empfiehlt Letzteren nur mit +begründetem Bedarf in eingeschränkten Umgebungen. Auch ein natives eingebautes +Client-Secret wäre nicht vertraulich. Deshalb wird Device Flow nicht allein +wegen eingesparter Serverarbeit zum Standard. +[GitHub: Best Practices](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/best-practices-for-creating-a-github-app#client-secrets) + +Für den bestehenden PAT-Weg können Name, Owner, Ablauf und Contents-Recht über +einen GitHub-Link vorbefüllt werden. Die Auswahl des einen Archiv-Repositories +muss der Nutzer prüfen; der Link erstellt keinen Token automatisch. Tokens +gehören ausschließlich in das geschützte Eingabefeld, niemals in Chat, QR-Code, +Support-Ticket oder Konfigurationsexport. +[GitHub: PAT-Vorlagen](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#pre-filling-fine-grained-personal-access-token-details-using-url-parameters) + +## 5. Ablauf aus Nutzersicht + +| Schritt | Anzeige und Aktion | Was SourceBraid übernimmt | +| --- | --- | --- | +| 1. Willkommen | „Dein eigenes Wissensarchiv“; „Einrichten“ | Kurze Erklärung zu GitHub und privatem Eigentum; bestehende Nutzer überspringen diesen Schritt | +| 2. GitHub verbinden | „Mit GitHub verbinden“; alternativ „GitHub-Konto erstellen“ | Browser-Anmeldung, Rückkehr und Prüfung des angemeldeten Kontos | +| 3. Archiv wählen | „Neues privates Archiv“ oder „Vorhandenes Archiv“ | Vorschlag `sourcebraid-archive`, Kollisionsprüfung und bestehende Auswahlmöglichkeiten | +| 4. Zugriff bestätigen | GitHub zeigt SourceBraid und die freizugebenden Repositories | Anleitung für genau das Archiv; Rückprüfung der tatsächlichen Installation | +| 5. Archiv vorbereiten | „Archiv wird vorbereitet“ mit benannten Teilschritten | Branch bestimmen, Archivordner mit `.gitkeep` initialisieren, vorhandene Dateien erhalten | +| 6. Erstes Speichern | Kurze Anleitung für Safari → Teilen → SourceBraid | Tatsächlichen Capture speichern, gezielt abrufen und im vorhandenen Reader öffnen | +| 7. Fertig | „Dein Archiv ist bereit“ | Optional PDF, Chrome oder ChatGPT verbinden; alles überspringbar | + +Die Reihenfolge von Archiv-Erstellung und Installationsfreigabe hängt vom +gewählten Bootstrap-Verfahren ab; die Oberfläche zeigt nur den jeweils nötigen +nächsten Schritt. Die GitHub-Anmeldung und die Installation der GitHub App sind +unterschiedliche Freigaben. Eine erfolgreiche Anmeldung allein bedeutet noch +keinen Archivzugriff. + +Standardwerte gelten nur für neue Archive. Vorhandene Namen, Ordner und Branches +werden nicht umbenannt. Insbesondere wird der tatsächliche Default-Branch +ermittelt, statt überall `main` anzunehmen. Der bisherige Name +`sourcebraid-private` ist kein Beweis für Privatsphäre und darf nicht mit dem +privaten Autoren-Repository des Maintainers verwechselt werden. + +Kein Test erzeugt heimlich einen scheinbar echten Artikel. Eine optionale +synthetische Einrichtungsdatei muss als solche angekündigt sein. Für den +Abschluss ist ein vom Nutzer gestarteter Capture mit korrekter Herkunft der +aussagekräftigste Nachweis. Reiner Lesezugriff bleibt ein nutzbarer Modus mit +dem Hinweis „Zum Speichern fehlt die Freigabe“. + +### Einstiege für neue und bestehende Installationen + +| Ausgangslage | Einstieg | Erfolg | +| --- | --- | --- | +| Neues iPhone oder iPad ohne Konfiguration | Assistent ersetzt „Archiv verbinden → Einstellungen“ und den leeren Reader-Platzhalter | Privates Archiv verbunden; erster Capture abrufbar | +| Update einer funktionierenden 1.1.0-Installation | Direkt „Zuletzt“, optionaler Hinweis in „Verbindungen“ | Bestehendes Archiv und Cache bleiben nutzbar | +| Zweites Gerät / Neuinstallation | „Vorhandenes Archiv verbinden“ deutlich anbieten | Dasselbe Repository auswählen, eigenes Credential erhalten | +| Abgelaufenes oder widerrufenes Credential | Reparatur der Verbindung statt neuer Archiv-Erstellung | Identität bestätigen und Zugriff erneuern | +| Nur Leserechte | Reader und Suche mit klarer Kennzeichnung | Lesen unabhängig von Capture nutzbar | + +Die vorhandene synthetische Demo ist ein Debug-/Testmodus und keine +veröffentlichte Kundenfunktion. Eine öffentliche Vorschau wäre eigene Arbeit +und ist keine Voraussetzung für diesen Installer. + +### iPhone und iPadOS + +Ein gemeinsamer `OnboardingCoordinator` steuert denselben fachlichen Ablauf. +Auf dem iPhone und in schmalen iPad-Fenstern wird jeweils ein Schritt gezeigt; +ein breites iPad-Fenster kann Fortschritt und Erklärung neben dem aktuellen +Formular darstellen. Seitenwechsel, Scrollposition und Eingaben bleiben bei +Rotation, Größenwechsel und Browser-Rückkehr erhalten. Keine festen +Bildschirmbreiten und kein Zwang zum Vollbild. + +Tastatur, Tab-Navigation, sichtbarer Fokus, VoiceOver, große Schrift sowie +Hoch-/Querformat gehören zur Abnahme. Die vorhandene adaptive Navigation wird +weiterverwendet; ein iPad wird nicht lediglich als vergrößertes iPhone getestet. + +Mehrere gleichzeitig nutzbare App-Fenster werden durch `WindowGroup` allein +nicht nachgewiesen und sind kein stillschweigendes neues Release-Versprechen. +O00 prüft Scene-Konfiguration und tatsächliche Fensterunterstützung. Der +Verbindungskoordinator erlaubt höchstens eine aktive Anmeldung/Übernahme +appweit. Weitere unterstützte Fenster zeigen den Vorgang, statt einen zweiten +zu starten. Späte Ergebnisse eines geschlossenen oder überholten Assistenten +dürfen keine neuere Verbindung überschreiben. + +## 6. Technische Architektur für iOS und iPadOS + +Apple stellt mit `ASWebAuthenticationSession` einen Systemweg für die +Web-Anmeldung bereit. Wir verwenden ihn anstelle eines selbst gebauten +Passwortformulars oder der Capture-WebView. Die Umsetzung berücksichtigt die +vorhandene iOS-17-Unterstützung und prüft Callback-APIs auf Verfügbarkeit. +[Apple: ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) + +Der Presentation-Anchor kommt aus dem Fenster, das die Anmeldung gestartet +hat. Sitzung und Presentation-Delegate werden bis zum Abschluss gehalten; +kein globales „erstes Fenster“ aus allen Scenes. Ein Startfehler oder Abbruch +kehrt zum vorhandenen Entwurf zurück. +[Apple: Web-Anmeldung](https://developer.apple.com/documentation/authenticationservices/authenticating-a-user-through-a-web-service) + +Der neuere HTTPS-Callback benötigt iOS/iPadOS 17.4 und eine zugeordnete Domain. +Bei weiterhin unterstütztem OS 17.0–17.3 ist ein geprüfter älterer +Custom-Scheme-Callback mit PKCE und Zustandsbindung nötig. Callback-APIs und +Associated Domains sind neue Konfiguration, kein Bestand von 1.1.0. Eine +Anhebung der Mindestversion ist in diesem Plan nicht vorgesehen. +[Apple: Callback-API](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/callback) + +```mermaid +flowchart LR + I["iOS-Assistent"] --> W["System-Browser: GitHub-Anmeldung"] + W --> A["SourceBraid Auth-Dienst"] + I -->|"Code-Austausch / Refresh"| A + A -->|"OAuth"| G["GitHub"] + A -->|"Zugangsdaten über HTTPS"| I + I -->|"Lokal speichern"| K["Gemeinsame iOS-Keychain"] + K --> I + K --> S["Share Extension"] + I -->|"Archiv lesen / vorbereiten"| R["Privates GitHub-Archiv"] + S -->|"Capture direkt speichern"| R +``` + +### Aufgaben des Auth-Dienstes + +Der Dienst hält das Client-Secret in einer geschützten Server-Umgebung und +unterstützt Anmeldung und Refresh. Er ist kein Proxy für Archivdateien und +benötigt im Grundmodell keinen GitHub-App-Privatschlüssel zum Minten von +Installationstokens. iOS verwendet nutzerbezogene GitHub-App-Tokens. + +Geplanter Vertrag: + +1. iOS beginnt eine kurzlebige Anmeldung mit zufälligem `state` und PKCE S256. +2. Der Dienst bindet die Transaktion an die initiierende App-Sitzung und eine + exakt erlaubte Callback-Adresse; keine frei übergebenen Weiterleitungsziele. +3. GitHub authentifiziert den Nutzer. Bei Rückkehr werden Zustand, Ablauf und + die Bindung an die ursprüngliche Anfrage geprüft. +4. Ein einmaliger Rückkehrcode wird aus der App über HTTPS eingelöst. Verifier + und Code-Austausch müssen dieselbe PKCE-Transaktion bilden. GitHub-Tokens + dürfen nicht in Callback-URLs oder Browser-Verläufen landen. +5. Der Dienst tauscht den GitHub-Code mit dem serverseitigen Secret aus und + liefert die Tokens ausschließlich an die initiierende App. Die App prüft + GitHub-Identität und Archivzugriff vor Aktivierung. +6. Token-Erneuerung erfolgt über einen geschützten, sitzungsgebundenen Endpunkt. + Er darf kein offener Token- oder beliebiger HTTP-Proxy sein. + +Der Broker-Sitzungsnachweis muss auch nach App-Neustart und abgelaufenem +GitHub-Access-Token überprüfbar bleiben. O00 legt seine sichere Speicherung, +Bindung an die autorisierte Verbindung, Laufzeit, Erneuerung und seinen +Widerruf fest. Ein gewöhnlicher Refresh soll keine neue Browser-Anmeldung +erfordern; eine abgelaufene oder widerrufene Broker-Sitzung darf sie verlangen. + +Das ist ein zu prüfender Schnittstellenentwurf, kein neues selbst erfundenes +OAuth-Protokoll. Eine gepflegte OAuth-Implementierung und ein Security-Review +müssen Replay, Login-CSRF, App-Imitation und fehlgeschlagene Übergaben abdecken. + +**Datenschutzwirkung:** Der Dienst verarbeitet Tokens zumindest kurzzeitig und +kann deshalb nicht als „ohne Zugriff auf Zugangsdaten“ beworben werden. +Vorgeschlagen sind keine dauerhafte Token-Speicherung für iOS, keine +Inhaltsdatenbank und keine Protokollierung von Tokens, Codes oder Request-Bodies. +Kurzlebige Sitzungsdaten, Betriebsmetadaten, Löschfristen, Hosting und +Secret-Rotation sind vor Veröffentlichung festzulegen. Die bestehende +Cloudflare-Infrastruktur ist ein möglicher Betreiberpfad, keine bereits +vorhandene native Anmeldelösung. + +### Konsistente Verbindung statt einzelner Token-Zeichenfolge + +Die neue Verbindung erhält eine stabile lokale ID und eine Revision. Ein +`CredentialProvider` liefert Zugangsdaten und die zugehörige GitHub-/Archiv- +Identität zusammen. Einstellungen, Reader und Extension dürfen nicht zu +verschiedenen Zeitpunkten einen neuen Owner und ein altes Token kombinieren. + +Keychain und App-Group-Defaults bilden keine gemeinsame Datenbanktransaktion. +Deshalb wird eine neue Credential-Version zunächst getrennt vorbereitet und +geprüft, dann unter Koordination über einen aktiven Verweis freigegeben. +Teilweise geschriebene Zustände müssen nach Prozessabbruch erkannt werden; +bis zur erfolgreichen Übernahme bleibt die letzte gültige Verbindung aktiv. +Ein konkreter Commit-/Wiederherstellungsvertrag gehört in O01. + +Ein gleiches Archiv mit erneuertem Credential behält Cache und Kurzbefehls-IDs. +Ein expliziter Archivwechsel nutzt die vorhandene Invalidierung und +Cache-Bereinigung erst nach erfolgreicher Übernahme. Alle laufenden Vorgänge +prüfen ihre Verbindungsrevision vor Übernahme eines Ergebnisses. Beim Wechsel +wird ein begonnener Capture kontrolliert beendet oder abgebrochen, niemals +gegen das inzwischen neu ausgewählte Repository wiederholt. + +### Lebenszyklus aller Credential-Verbraucher + +Die gemeinsame Keychain erhält ein versioniertes Credential-Modell mit +Auth-Typ, stabiler GitHub-Nutzer-ID, App-/Installationsbezug, Access- und +Refresh-Token sowie Ablaufzeiten. Nicht geheime Archivkonfiguration bleibt +getrennt. Bestehende Keychain- und App-Group-Identitäten bleiben erhalten. + +| Verbraucher in 1.1.0 | Anpassung für erneuerbare Credentials | +| --- | --- | +| `SettingsViewModel` und Assistent | Unveränderlichen Entwurf prüfen; nur dieselbe Revision aktivieren | +| `ArchiveLibrary` / `ArchiveReadClient` | Credential vor Requests beziehen; Tokenwechsel im selben Archiv ohne Cache-Löschung | +| `ArchiveBackgroundRefresh` | Nur mit dauerhaft freigegebener Verbindung planen; begrenzter Refresh oder sauberer Abbruch, kein Login-Dialog | +| `GitHubClient` / `ShareViewModel` | Zusammengehörige Konfiguration und Credentials laden; Wechsel während Bildabruf/Upload erkennen | +| `ArchiveIntents` | Lokale Geräteauthentifizierung und Cache-Zugriff erhalten; keine interaktive Anmeldung oder Repository-Erstellung aus Kurzbefehlen | + +Der vorhandene `ArchiveRefreshCoordinator` schützt Cache-Schreibvorgänge im +App-Prozess. Er ersetzt keine prozessübergreifende Token-Sperre mit der Share +Extension. Das neue Credential-Modell muss beide Koordinationsebenen verbinden, +ohne die vorhandenen Abbruch- und Löschschutzmechanismen zu entfernen. + +GitHub dokumentiert standardmäßig acht Stunden für User Access Tokens und +sechs Monate für Refresh Tokens; bei erfolgreichem Refresh wird das alte Paar +ungültig. Die Implementierung verwendet die gelieferten Ablaufwerte und prüft +die tatsächlich aktivierte App-Konfiguration. +[GitHub: Refresh](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/refreshing-user-access-tokens) + +App und Share Extension dürfen nicht gleichzeitig dasselbe Refresh-Token +verbrauchen. Benötigt werden eine prozessübergreifende Koordination, erneutes +Lesen nach Sperrerwerb und atomisches Ersetzen des Credential-Satzes. Nach einem +Timeout beim Refresh gibt es keine blinde Wiederholungsschleife; bei verlorenem +neuem Tokenpaar ist eine erneute Anmeldung ein vorgesehener Wiederherstellungsweg. +Die Extension hat einen kurzen Zeitrahmen: nötigenfalls verständlich auf +„SourceBraid öffnen und Verbindung erneuern“ verweisen. Ein fehlgeschlagener +Upload darf nie als gespeichert gelten. + +Das bestehende Keychain-Item ist nicht synchronisierbar und gerätegebunden. +Ein neu eingerichtetes iPad übernimmt deshalb kein iPhone-Token. Der Standard +bleibt: auf dem zweiten Gerät anmelden und dasselbe Archiv auswählen. +Authentifizierungsgeheimnisse gehören weder in `SceneStorage` noch in +Konfigurationsdateien. Dort beziehungsweise in der Wiederherstellung des +Assistenten werden ausschließlich nicht geheime Schritte und Eingaben gespeichert. +Dauerhafte Zugangsdaten und Broker-Sitzungsnachweise bleiben in der Keychain; +nicht gesicherte Anmeldeversuche beginnen nach Prozessverlust neu. +[Apple: Gerätegebundene Keychain](https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlockthisdeviceonly) + +Abmeldung und Widerruf invalidieren ausstehende Login-/Refresh-Ergebnisse. +Ein danach eintreffendes Token darf die Verbindung nicht wieder aktivieren. +Ein dauerhaft gespeicherter Zustand `signedOut` unterbindet Hintergrundjobs +auch nach App-Neustart. „Offline mit gültiger lokaler Verbindung“ und +„abgemeldet“ bleiben unterschiedliche Zustände; lesbarer Cache darf nicht +bei jedem Netzfehler verschwinden. + +Insbesondere bleibt die lokale Verbindung zum Offline-Lesen und Suchen nutzbar, +wenn lediglich das kurzlebige Access Token abgelaufen und der Auth-Dienst nicht +erreichbar ist. Netzwerk-Token-Gültigkeit und lokale Zugriffsfreigabe sind +getrennte Bedingungen. Explizites Abmelden oder Entfernen lokaler Daten behält +seine bisherige Wirkung; ein fehlgeschlagener Refresh löscht keinen Cache. + +## 7. Archiv-Erstellung und Berechtigungen + +### A. Kleinster veröffentlichbarer Weg + +Der Assistent öffnet ein vorbereitetes GitHub-Formular für ein privates +Repository. Danach führt er zur Installation der GitHub App mit Auswahl des +Archivs. Er prüft anschließend API-seitig Repository-ID, Owner, Privatstatus +und tatsächlichen Zugriff; Formularwerte und Rückkehrparameter sind keine +vertrauenswürdige Bestätigung. + +Beispiel für den dokumentierten Formularweg: +[Privates Archiv bei GitHub vorbereiten](https://github.com/new?name=sourcebraid-archive&visibility=private&owner=%40me). +[GitHub: Repository-Formularparameter](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository#creating-a-new-repository-from-a-url-query) + +### B. Bevorzugtes Komfortziel: Erstellung direkt aus der App + +Die aktuelle GitHub-Dokumentation nennt **Repository creation** als engeres +Recht als Administration und beschreibt automatischen Zugriff auf selbst +erstellte Repositories. Damit ist die direkte Erstellung grundsätzlich planbar. +[GitHub: Berechtigungswahl](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app) + +Nach Installation/Autorisierung ruft iOS `POST /user/repos` auf, ausdrücklich +mit `private: true`. Die Antwort liefert die zu prüfende Repository-ID. Danach +wird der Zugriff erneut über die Installation festgestellt und das Archiv +initialisiert. Das öffentliche API-Default darf nie versehentlich verwendet +werden. +[GitHub: Repository erstellen](https://docs.github.com/en/rest/repos/repos?apiVersion=2026-03-10#create-a-repository-for-the-authenticated-user) + +**Früher Nachweis O00:** frisches Konto ohne Repository, tatsächliche +Verfügbarkeit des Creation-Rechts, Installation mit null/ausgewählten +Repositories, automatischer Zugriff, bestehende Installation mit abgelehntem +Rechte-Upgrade, Namenskollision und Timeout direkt nach Erstellung. Diese +Choreografie wurde für diesen Plan nicht praktisch ausgeführt. Scheitert der +Nachweis, wird zunächst Weg A umgesetzt. Kein pauschales Ausweichen auf +Administrationsrechte oder alle Repositories. + +### Berechtigungsmodell als Umsetzungsziel + +| Funktion | Geplanter Zugriff | Abgrenzung | +| --- | --- | --- | +| Lesen | Metadata und Contents lesen | Eigenständiger Lesemodus | +| Capture | Contents schreiben im ausgewählten Archiv | Keine allgemeinen Repository-Administrationsrechte | +| Neues Archiv | Separat begründetes Repository-creation-Recht für Weg B | Weg A benötigt keine Erstellungsvollmacht | +| PDF einrichten | Contents plus Workflows schreiben für die Support-Dateien | Separater Einrichtungsschritt, kein Capture-Standardrecht | +| PDF-Status | Nur falls nötig Actions lesen | Keine automatische Änderung von Organisationsrichtlinien | +| Gehostetes ChatGPT | Eigene, auf Lesen begrenzte GitHub-Verbindung | Kein Weiterreichen des iOS-Schreib-Tokens | + +Die konkreten API-Endpunkte und Rechte werden in O00 mit minimalen Testkonten +verifiziert; insbesondere der +[Contents-Schreibzugriff](https://docs.github.com/en/rest/repos/contents#create-or-update-file-contents) +für normale Dateien und Workflow-Dateien. GitHub-App-Rechte sind an die Registrierung gebunden +und keine frei wechselnden OAuth-Scopes. Ein optionaler PDF-Schalter macht ein +global angefordertes Workflow-Recht nicht nachträglich optional. Für die erste +Version: PDF-Setup über einen getrennten, eng begrenzten Einrichtungsweg; später +gegebenenfalls eigene Setup-App. Jede zusätzliche App erhöht die Bedienkomplexität +und muss einen nachweisbaren Nutzen haben. + +Auch „Nur lesen“ in der Oberfläche reduziert ein bereits erteiltes GitHub-Recht +nicht. Bestehende Contents-Read-PATs bleiben für diesen Modus unterstützt. Die +Capture-GitHub-App fordert Contents-Schreibrechte; eine garantiert nur lesende +OAuth-Anmeldung braucht eine entsprechend getrennte Rechte-Registrierung und +wird nicht als bereits gelöste Eigenschaft des Capture-Logins versprochen. + +Vorhandene Archive werden nur nach Auswahl verbunden. Persönliche Konten sind +der erste voll unterstützte Einrichtungsweg; Organisationen, SSO und ausstehende +Admin-Freigaben erhalten eigene Zustände. Regeln oder Branch-Schutz werden nicht +abgeschaltet, um einen Capture durchzusetzen. + +## 8. Initialisierung und PDF-Konvertierung + +### Erster Capture im veröffentlichten Reader + +1. Neues Archiv mit initialem Commit und `.gitkeep` herstellen; leeren Zustand + ausdrücklich anzeigen. Ein 404 wegen fehlender Freigabe bleibt ein Fehler. +2. Den Nutzer einen öffentlichen Beispielinhalt seiner Wahl oder eigene + freigegebene Inhalte über die echte Share Extension speichern lassen. +3. Den erfolgreichen Capture mit Archividentität, Pfad und vorhandenem + Commit-/Dateinachweis verbinden; kein zusätzliches Token im Rückkehrhinweis. +4. Bei Rückkehr den gespeicherten Pfad gezielt lesen oder begrenzt den + Archivstand aktualisieren. Der aktuelle reine Cache-Start genügt hier nicht. +5. Den Beitrag im vorhandenen Reader öffnen und „Speichern geprüft“ setzen. + Ein fehlgeschlagener Abruf lässt den Schritt offen, ohne den Upload sofort + zu wiederholen und doppelte Captures zu erzeugen. + +Der Assistent wartet weder auf einen vollständigen Suchindex noch auf einen +vom Betriebssystem gestarteten Hintergrundjob. Die Grenze von sieben ersten +Artikeltexten bleibt erhalten; ein gezielter Capture-Nachweis wird separat +begrenzt. Für vorhandene große Archive darf die Einrichtung keinen Vollabruf +auslösen. „Archiv verbunden“, „Speichern geprüft“ und „Suchabdeckung“ sind +unabhängige Anzeigen. + +### Wiederholbarer Bootstrap + +Der Bootstrap bekommt eine explizite Dateiliste und wird wiederholbar: + +- Neues Archiv mit initialem Commit und `/.gitkeep` vorbereiten. + Git speichert keine leeren Verzeichnisse. Ein Archiv ohne Captures ist danach + ein gültiger Zustand; auch ein noch nicht angelegter Archivordner muss beim + ersten Einrichten verständlich behandelt werden. +- Nur geplante Dateien anlegen. Bestehende Dateien vergleichen und erhalten; + Konflikte anzeigen. Kein Überschreiben aus Bequemlichkeit. +- Nach unklarer Netzwerkantwort zuerst remote prüfen, ob der Schritt schon + ausgeführt wurde. Kein zweites Repository oder doppelter Test-Capture. +- Einrichtungsschritte und verbleibende Arbeit ohne Geheimnisse dokumentieren. + Abbruch löscht kein bereits angelegtes Repository. +- Format von Capture-Pfaden, YAML-Frontmatter und URL-Hash-Index-Shards erhalten. + Ein neues Konfigurationsschema benötigt explizite Versionierung und Migration. + +PDF bleibt ein eigener Schritt „PDFs automatisch in Markdown umwandeln“. +Wiederverwendet wird die bestehende Allowlist: + +```text +.github/workflows/convert-pdfs.yml +requirements-docling.txt +scripts/convert_pdfs.py +scripts/push_with_retry.py +``` + +Die Dateien stammen aus den geprüften öffentlichen Projektquellen und werden +versionsgebunden gebündelt. Weder `website/` noch ein privates `web-clips/`, +lokale Konfiguration oder Git-Historie werden kopiert. Es wird auch nicht das +private Autoren-Repository geforkt oder exportiert. + +Der PDF-Schritt erläutert Verarbeitung durch GitHub Actions und mögliche +Kontingente/Kosten, ohne einen unbegrenzt kostenlosen Betrieb zu versprechen. +Ein synthetisches PDF prüft den tatsächlichen Durchlauf. „PDF hochgeladen“, +„Konvertierung ausstehend“ und „Markdown verfügbar“ bleiben getrennt. Bei +deaktivierten Actions bleiben normales Speichern, Lesen und Suche nutzbar; +ausstehende PDF-Konvertierung wird sichtbar, statt als abgeschlossen zu gelten. + +## 9. Chrome, ChatGPT und Codex ergänzend verbinden + +### Chrome + +Der vorhandene Teilen-Button im Reader exportiert Markdown-Inhalt. Ein Export +von Repository-Einstellungen aus iOS ist weiterhin eine geplante neue Funktion. + +Nach dem iOS-Grundsetup kann die App eine Konfiguration ohne Zugangsdaten +bereitstellen. Die existierenden Schlüssel bleiben kompatibel: + +```json +{ + "owner": "example-user", + "repo": "sourcebraid-archive", + "branch": "main", + "root_folder": "web-clips" +} +``` + +Das ist ein synthetisches Beispiel, keine reale Konfiguration. Chrome braucht +zunächst einen Importdialog mit Vorschau, strikter Pfadprüfung und eigener +GitHub-Anmeldung. Importierte Werte verleihen keinen Zugriff. OAuth-Callbacks, +Manifest-V3-Service-Worker-Neustarts und eventuell nötige neue Browserrechte +werden gezielt geprüft. Es werden keine iOS-Tokens, Cookies oder Refresh-Tokens +in die Erweiterung übertragen. + +Eine spätere Kopplung per QR-Code darf nur Konfiguration oder einen kurzlebigen, +einmaligen, sitzungsgebundenen Kopplungsnachweis enthalten. Repository-Namen sind +ebenfalls privat zu behandeln. Installation und Freigabe der Chrome-Erweiterung +erfolgen im Browser; die iOS-App kann sie anleiten, aber nicht still installieren. + +### Gehostetes ChatGPT + +Die [vorbereitete MCP-Anbindung](CHATGPT_MCP_DEPLOYMENT.md) hat bereits +nutzerbezogene OAuth-Grants. Ihr OAuth verbindet jedoch **ChatGPT mit +SourceBraid**. Auf der Zustimmungsseite wird weiterhin ein separater GitHub-PAT +verlangt. Diese Anmeldung löst das GitHub-Tokenproblem noch nicht. + +Der Ausbau benötigt eine eigenständige, lesende GitHub-Anbindung, vorzugsweise +eine separate GitHub-App-Registrierung ohne Schreibrechte. Zustimmung und +Zuordnung zu Nutzer, Installation und genau einem Archiv werden serverseitig +geprüft. Der vorhandene Code akzeptiert explizit `github_pat_` und ermittelt die +Identität über `/user`; Installationstokens sind daher kein austauschbarer +Ersatz. Credential-Typ, Autorisierung und Erneuerung müssen gezielt angepasst +und mit zwei getrennten Testkonten geprüft werden. + +Zwischen ChatGPT und MCP bleiben eigener OAuth-Flow mit PKCE, Ressourcenbindung +und Tokenprüfung nötig. Ein GitHub-Token ist kein MCP-Zugangstoken. +[OpenAI: Plugin-Authentifizierung](https://developers.openai.com/plugins/build/auth) + +Dieser gehostete Weg verarbeitet angeforderte Inhalte bei SourceBraid/Cloudflare +und OpenAI. Er erhält eine eigene Einwilligung und Datenschutzbeschreibung. +Das iPhone wird nicht zum dauerhaft erreichbaren MCP-Server. Die Verbindung +muss unabhängig vom laufenden iPhone funktionieren. Öffentliche Verfügbarkeit +wird erst nach Deployment, Tests und Freigabe angekündigt; die App zeigt zuvor +keinen scheinbar funktionierenden Verbindungsbutton. + +### Lokales Codex-Plugin + +Der vorhandene Weg mit lokaler Python-Laufzeit und GitHub-Zugang bleibt bestehen. +Die Konfiguration aus iOS kann ihn später vereinfachen. Die Standardbibliothek +bleibt ausreichend; für diesen Plan ist kein neues Python-Paket nötig. Lesen +und bestätigte Schreib-/Löschaktionen behalten getrennte Anforderungen. Kein +ChatGPT- oder Codex-Konto ist für die iOS-Grundfunktionen erforderlich. + +## 10. Zustände, Fehler und Wiederherstellung + +Der Assistent speichert einen Entwurf getrennt von der aktiven Konfiguration. +Ein möglicher Ablauf ist: + +```text +unconfigured → authenticating → choosingArchive → authorizingArchive +→ provisioning → readReady → captureVerified → completed +``` + +`cancelled`, `offline`, `authorizationPending`, `reauthRequired` und +`repairRequired` sind wiederaufnehmbare Zustände. PDF und Zusatzintegrationen +sind separate Fähigkeiten und keine Pflichtzustände dieser Kette. + +`readReady` ist ein eigenständiger nutzbarer Endpunkt für Lesebetrieb. +`captureVerified` verlangt einen tatsächlichen Schreib-/Rücklese-Nachweis. +„Einrichtung erledigt“ wird daher immer zusammen mit dem gewählten Lese- oder +Capture-Modus gespeichert; eine unvollständige Suche blockiert beide nicht. + +### Migration von 1.1.0 + +1. Legacy-Konfiguration und vorhandenes PAT lesen, ohne sie umzuschreiben. + Funktionierende Installationen starten weiterhin im Reader. +2. Unter „Verbindungen“ freiwillig „GitHub-Anmeldung einrichten“ anbieten; + Owner, Repository, Branch und Root aus der bestehenden Verbindung übernehmen. +3. Neue Anmeldung und Archivzugriff im getrennten Entwurf prüfen. Abbruch + erhält das alte PAT und den vollständigen bisherigen Verbindungszustand. +4. Erfolgreiche Verbindung einmalig und wiederherstellbar aktivieren; alle + Verbraucher auf dieselbe Revision umstellen. Im selben Archiv Cache und + `cacheKey:path`-Identitäten der Kurzbefehle beibehalten. +5. Altes PAT erst nach gesicherter Übernahme lokal entfernen. GitHub-Widerruf + separat erläutern, da das PAT möglicherweise noch in Chrome verwendet wird. +6. Bei späteren Fehlern zunächst die neue Verbindung reparieren. Kein stiller + Rückfall auf ein schon widerrufenes PAT und keine neue Archiv-Erstellung. + +| Situation | Nutzerführung / gefordertes Verhalten | +| --- | --- | +| Anmeldung abgebrochen | Zur vorherigen Seite; bestehende Verbindung bleibt aktiv | +| Falsches GitHub-Konto | Konto sichtbar anzeigen und gezielten Kontowechsel anbieten | +| 401 / Token widerrufen | Einmal sinnvoll erneuern oder neu anmelden; keine Endlosschleife | +| 403 / fehlendes Recht | Fehlende Freigabe benennen; Organisationsfreigabe und Rate Limit unterscheiden | +| 404 | Fehlende Sichtbarkeit, falsches Repository, fehlenden Branch und leeres Archiv unterscheiden | +| Repository öffentlich | Im Anfängerweg private Alternative verlangen; bestehendes Repository nicht automatisch umstellen | +| Name belegt | Vorhandenes Archiv prüfen oder anderen Namen vorschlagen; nichts überschreiben | +| Geschützter Branch | Blockade erläutern; erlaubten Branch explizit wählen, Schutz erhalten | +| Abbruch nach Erstellung | Vorhandenes Repository finden und fortsetzen; keine automatische Löschung | +| Offline / Rate Limit | Fortschritt erhalten, Wiederholung mit Backoff, vorhandenen Cache nutzbar halten | +| Gerät neu / App neu installiert | Erneut anmelden und vorhandenes Archiv wählen; keine doppelte Erstellung | +| Share Extension ohne gültigen Zugriff | Klare Fehlermeldung und Reparatur in der Haupt-App | + +„Auf diesem Gerät abmelden“, „GitHub-Freigabe widerrufen“, „lokale Daten entfernen“ +und „Repository löschen“ sind unterschiedliche Aktionen. Der Assistent löscht +keine Archive. Ein Widerruf der GitHub-App-Autorisierung kann weitere Geräte +betreffen; unabhängiger Gerätewiderruf wird ohne Nachweis nicht versprochen. +[GitHub: Autorisierung widerrufen](https://docs.github.com/en/apps/using-github-apps/reviewing-and-revoking-authorization-of-github-apps) + +## 11. Umsetzungspakete und Reihenfolge + +Die Schätzungen sind Personentage für Entwicklung und Tests, keine Zusagen. +App-Store-Prüfung, Betreiberfreigaben und Wartezeiten sind nicht enthalten. + +| Paket | Arbeit und betroffene Bereiche | Ergebnis / Abnahme | Grobe Größe | +| --- | --- | --- | --- | +| O00 – Machbarkeit nach 1.1.0 | Test-GitHub-App, Web-Flow/PKCE/Refresh, Creation-Recht, Callback ab OS 17 und iPad-Scene-Konfiguration prüfen | Schriftlicher API-/Rechte-Nachweis, Entscheidung zwischen Archivweg A und B sowie Hosting-Vertrag | 2–3 Tage | +| O01 – Verbindung und Migration | Unveränderlicher Entwurf, revisionierte Aktivierung, CredentialProvider für alle Verbraucher, Legacy-PAT-Migration, dauerhafter Abmeldestatus | Alte Verbindung bleibt bei Fehlern bestehen; verspätete Ergebnisse und Hintergrundjobs aktivieren sie nicht erneut | 4–6 Tage | +| O02 – Anmeldung | Auth-Dienst, AuthenticationServices, fenstergebundene Callbacks, prozessübergreifender Refresh | Login/Abbruch/Refresh/Widerruf einschließlich Neustart und negativer Sicherheitstests | 5–8 Tage | +| O03 – Archiv | Repository-Auswahl/Erstellung, Branch-Erkennung, idempotenter Bootstrap, leerer Reader | Frisches privates Archiv und vorhandenes Archiv erfolgreich geprüft | 3–5 Tage | +| O04 – Assistent auf iPhone und iPad | Neueinstieg, Update-/Zweitgeräteweg, schmale/breite Fenster, echte Share Extension, gezieltes Capture-Rücklesen | iPhone und iPad erreichen unabhängig den ersten Capture; bestehender Reader bleibt erhalten | 4–6 Tage | +| O05 – Release-Abnahme | DE/EN, Accessibility, Migration, Datenschutztexte, Betrieb und Geräteprüfung | Freigabefähige erste Version ohne verpflichtende Zusatzintegrationen | 3–5 Tage | +| O06 – PDF | Separater Rechte-/Setup-Weg und Konvertierungsstatus | Vollständige synthetische PDF-Konvertierung; normales Archiv unabhängig nutzbar | Danach separat schätzen | +| O07 – Chrome | Konfigurationsimport, eigene Anmeldung, später optional Kopplung | Dasselbe Archiv nutzbar, keine Token-Weitergabe | Danach separat schätzen | +| O08 – ChatGPT/Codex | Lesende GitHub-Anbindung des MCP-Dienstes, lokaler Import | Getrennte Rechte und funktionierende Kontentrennung | Danach separat schätzen | + +Für O00–O05 ergibt das grob **21–33 Personentage**. Die gegenüber dem alten +Entwurf höhere Bandbreite berücksichtigt die Migration veröffentlichter +Installationen sowie Hintergrundverbraucher, iPad-Fenster und gezieltes +Capture-Rücklesen. O00 kann diese Schätzung ändern. + +O00 und die lokal testbaren Teile von O01 können parallel vorbereitet werden. +O02 baut auf deren Verbindungsvertrag auf, O03 auf der bestätigten +Authentifizierung. O04 verbindet diese Bausteine; O05 nimmt die erste +vollständige Version ab. PDF und Zusatzintegrationen folgen unabhängig. + +Ein geführter PAT-Weg bleibt verfügbar, ist aber kein verpflichtendes neues +Zwischenrelease. Eine kleine vorgezogene Korrektur von „Verbindung testen“ +wäre separat veröffentlichbar. Sie erfüllt das Ziel „keine manuelle +Token-Erstellung“ noch nicht und ersetzt den Installer nicht. + +Vorgeschlagene neue iOS-Bausteine: `OnboardingCoordinator`, +`GitHubAuthorizationClient`, `GitHubRepositorySetupClient` und +`CredentialProvider`. Diese Namen sind Entwürfe, keine bereits vorhandenen +Dateien. Capture-Code bleibt auf sein bestehendes Format konzentriert. + +| Bestehender Bereich | Geplante Änderung | +| --- | --- | +| `ContentView.swift`, `SettingsView.swift` | Neueinstieg und Verbindungen, vorhandene Reader-/Tab-Navigation erhalten | +| `SettingsViewModel.swift`, `SourceBraidConfiguration.swift` | Entwurf, Legacy-Decode, unveränderliche Testanfrage und revisionierte Übernahme | +| `KeychainStore.swift`, App Group | Versionierte Credentials und prozessübergreifende Koordination, Identitäten erhalten | +| `GitHubClient.swift`, `ArchiveReadClient.swift` | CredentialProvider und gezielte Verbindungs-/Capability-Prüfungen | +| `ArchiveLibrary.swift`, `ArchiveBackgroundRefresh.swift`, `ArchiveIntents.swift` | Bestehende Cache-/Abbruchmechanik erhalten, Lifecycle und Readiness integrieren | +| `ShareViewModel.swift` | Konsistente Verbindung, Refresh-Limit und Capture-Nachweis | +| `Info.plist`, Entitlements, Projekt, lokalisierte Texte | Callback-/Domain-Konfiguration, Verfügbarkeit, neue Screens und DE/EN | +| Neuer Auth-Dienst | Eigene Quellen, Tests und öffentliche Release-Allowlist; keine private Website exportieren | + +## 12. Tests und verbindliche Abnahmekriterien + +### Erneut ausgeführte Prüfungen am 15. Oktober 2026 + +| Prüfung | Ergebnis dieser Überprüfung | +| --- | --- | +| `python3 scripts/validate_ios_release.py` | Bestanden, 1.1.0/Build 11 | +| Generischer Simulator-Build, App und Extension, ohne Signierung | Bestanden mit Xcode 27.1 (27A9275) | +| Validator auf erzeugtem Simulator-App-Bundle | Bestanden, 1.1.0/Build 11 | +| Bestehende Unit-/Integrationstests auf iPad Pro 13-inch (M5), Runtime 27.0 | **47 bestanden, 0 Fehler** | +| Bestehende UI-Suite auf demselben iPad-Simulator | **7 bestanden, 0 Fehler** | + +Build und Tests verwendeten die iOS-Quellen des Tags, eigene Derived Data +unter `/private/tmp/sourcebraid-installer-review-20261015-derived` und serielle +Simulator-Ausführung. Testnachweis: +`/private/tmp/sourcebraid-installer-review-20261015-ipad.xcresult`, dazu +`sourcebraid-installer-review-20261015-ipad.log` und +`sourcebraid-installer-review-20261015-build.log` im selben temporären Verzeichnis. +Diese Artefakte bleiben außerhalb des Repositorys. Xcodes Zugriff auf Simulator +und Paketcache war erst außerhalb der Sandbox möglich; der erste blockierte +Versuch ist kein Produktfehler. + +Die UI-Suite nutzt synthetische Debug-Daten. Sie beweist keine echte GitHub- +Anmeldung, Share-Extension-Ersteinrichtung, schmale iPad-Fenster, mehrere +aktive Fenster oder Mindest-OS-Funktion. Physische Geräte und ein echter +App-Store-Binärvergleich wurden in dieser Überprüfung nicht ausgeführt. +Node-/Python-Kernsuiten und MCP-Tests wurden für diese reine Planänderung nicht +erneut gestartet. Die bisherigen Release-Nachweise bleiben separat bestehen. + +### Automatisiert + +- Zustandsübergänge, Entwurf/Übernahme, Abbruch und Migration alter Einstellungen. +- PKCE, falsches `state`, fremde Callback-URL, Replay, abgelaufene Sitzung, + verlorene Token-Antwort und paralleler Refresh aus App/Extension. +- Nutzer-/Repository-/Installationsbindung, fremde Repository-ID und zwei + getrennte Konten; kein Zugriff durch manipulierte Konfigurationsimporte. +- Neue/fehlende Branches, leeres Archiv, bereits vorhandene Dateien, + Namenskollision, Timeout nach Commit und wiederholter Bootstrap. +- Reiner Lesezugriff, verweigerte Writes, Branch-Schutz, PDF ohne Workflow. +- Keine Geheimnisse in Logs, Diagnoseexporten, URLs, Paketen oder Screenshots. +- Eingaben während einer laufenden Verbindungsprüfung ändern: Ergebnis und + Übernahme müssen am geprüften Entwurf bleiben; veraltete Ergebnisse verwerfen. +- Prozessabbruch zwischen Credential-Vorbereitung und Aktivierung; nach Neustart + entweder vollständige alte oder vollständige neue Verbindung, niemals eine Mischung. +- Abmelden → App beenden → neu starten: keine neuen Hintergrundabrufe ohne + aktive Verbindung. Späte Refresh-Antworten dürfen die Abmeldung nicht aufheben. +- PAT → GitHub App im selben Archiv: Cache, geladene Beiträge und Kurzbefehls- + Identitäten bleiben erhalten; keine erneute Vollindexierung als Setup-Pflicht. +- Access Token abgelaufen und Gerät offline: geladene Beiträge und lokale Suche + bleiben ohne Netz-Refresh nutzbar; neue Netzwerkaktionen zeigen den nötigen + Wiederverbindungszustand. Explizite Abmeldung bleibt davon getrennt. +- Leeres initialisiertes Archiv → Capture → Rückkehr: Beitrag erscheint durch + gezielten Abruf auch ohne Hintergrundlauf und ohne manuellen Refresh-Trick. + +### Auf physischen Geräten und mit GitHub-Testkonten + +- Frisches Konto ohne Repository bis zum ersten Capture; vorhandenes Archiv + ohne Überschreiben verbinden. +- Bestehende PAT-Nutzer migrieren nur nach erfolgreicher neuer Anmeldung. +- Login mit Zwei-Faktor-Anmeldung, Abbruch und falschem Konto. +- App-Hintergrundwechsel, unterbrochene Verbindung, Tokenablauf, Widerruf, + Neuinstallation und Keychain-Zugriff vor dem ersten Entsperren. +- Safari-Teilen, andere Share-Quellen, iPhone/iPad, VoiceOver, große Schrift, + Deutsch und Englisch sowie die älteste unterstützte iOS-Version. +- iPad im Vollbild und in schmalen/breiten Fenstern, Größenwechsel während + Anmeldung, Hardware-Tastatur und Fokus, Browser-Rückkehr zum auslösenden Fenster. +- Falls mehrere App-Fenster freigegeben werden: zweiter Anmeldeversuch, Fenster + schließen, veralteten Assistenten bestätigen und Abmeldung während Refresh. + Ohne Mehrfensterfreigabe diese Fähigkeit nicht als getestet bewerben. +- Callback getrennt auf OS 17.0–17.3 und 17.4+ prüfen. Ein Test unter Runtime + 27.0 ersetzt diese Verfügbarkeitsprüfung nicht. +- iPhone zuerst / iPad danach und iPad zuerst / iPhone danach: dasselbe Archiv, + unabhängige Anmeldung und keine versehentliche zweite Repository-Erstellung. +- Fünf Personen ohne GitHub-Erfahrung testen den Ablauf. Zielwert: mindestens + vier schaffen den ersten Capture ohne persönliche Hilfe; Setup-Zeit und + Abbruchstellen werden mit Einwilligung beobachtet. Das ist ein vorgeschlagenes + Produktziel, kein gemessener Wert und keine neue Telemetrie-Anforderung. + +### Repository-Prüfungen bei der späteren Implementierung + +Bei Änderungen an gemeinsamem Capture, Skripten oder Plugin beide Kernsuiten +aus dem Repository-Root ausführen: + +```bash +node --test tests/capture-utils.test.js +python3 -m unittest discover -s tests -p "test_*.py" +``` + +iOS aus `ios/` ohne Signierung bauen und die neuen Unit-/UI-Tests zusätzlich +auf einem passenden Simulator ausführen: + +```bash +xcodebuild \ + -project SourceBraid.xcodeproj \ + -scheme SourceBraid \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO \ + build +``` + +Bei Änderungen am gehosteten Dienst zusätzlich dessen `npm ci`, `npm test` +und `npm run build` unter `chatgpt-mcp/`. Für einen neuen Auth-Dienst eigene +Tests/Build-Prüfungen festlegen. Fehlende Plattformen oder Abhängigkeiten +offen dokumentieren; Builds ersetzen weder Live-OAuth- noch Geräteabnahme. + +**Fertig ist die erste Version**, wenn der normale iOS-Weg ohne PAT-Erstellung, +Terminal und Zusatzplugin funktioniert, ein privates Archiv nachweislich im +Nutzerkonto liegt, ein Capture erfolgreich zurückgelesen wird und Abbruch, +Erneuerung sowie bestehende Archive zuverlässig behandelt werden. PDF und +Zusatzintegrationen besitzen eigene Abnahmen. + +Diese Kriterien gelten auch für ein allein eingerichtetes iPad. Ein Upgrade von +1.1.0 darf weder erneute Pflichteinrichtung noch Cache-Verlust erzwingen. Der +reine Lesemodus hat eine eigene Abnahme ohne Schreibvorgang. Mindest-OS, +Accessibility und physische Share-Extension-Prüfungen müssen für den neuen +Installer nachgewiesen werden; die Veröffentlichung von 1.1.0 schließt die +in der Gerätecheckliste dokumentierten offenen Fälle nicht automatisch. + +## 13. Vor Veröffentlichung zu klären + +1. **Betrieb:** Wer verantwortet Auth-Dienst, Domain, Secrets, Monitoring ohne + Inhalts-/Tokenlogs, Ausfälle, Missbrauchsschutz und Wiederherstellung? +2. **Rechte:** Welche tatsächlich getestete GitHub-App-Registrierung liefert + den kleinsten notwendigen Zugriff? Wird direkte Erstellung sofort freigegeben? +3. **Datenschutz:** Neue Auth-Verarbeitung, Betreiber, Aufbewahrung und Widerruf + in [PRIVACY.md](../PRIVACY.md), iOS-Hinweisen und Store-Angaben aktualisieren; + rechtliche Texte nicht ohne Maintainer-Review abschwächen. +4. **Distribution:** Die iOS-App bleibt Haupteinstieg. Nur echte veröffentlichte + Store-/Plugin-Links anzeigen; Entwicklungspakete klar unterscheiden. +5. **Migration:** PAT-Fallback erhalten, bestehende Archive nicht umbenennen, + Schlüsselbund und Formate kompatibel halten. +6. **Release:** Nur öffentliche Allowlist-Dateien übernehmen, keine privaten + Quellen oder Historie; erforderliche Checks und DCO-Sign-off beachten. + +Der nächste konkrete Umsetzungsschritt ist **O00**, parallel zu den lokalen +Verbindungsgrundlagen aus **O01**: Anmeldung, Refresh und private Archiv- +Erstellung mit minimalen Rechten nachweisen, gleichzeitig die 1.1.0-Verbindung +gegen fehlgeschlagene oder veraltete Einstellungsversuche absichern. Danach +werden Authentifizierung und Assistent auf iPhone und iPad gegen denselben +geprüften Vertrag implementiert. diff --git a/docs/IOS_ONBOARDING_INSTALLER_PLAN.md b/docs/IOS_ONBOARDING_INSTALLER_PLAN.md new file mode 100644 index 0000000..31b9964 --- /dev/null +++ b/docs/IOS_ONBOARDING_INSTALLER_PLAN.md @@ -0,0 +1,854 @@ +# SourceBraid: Installer plan for iOS and iPadOS after version 1.1.0 + +**SourceBraid — Weave the web into Markdown.** + +As of: **October 15, 2026 · Revision 2** · Baseline: **iOS/iPadOS 1.1.0, build 11** +· Status: **New implementation plan; installer not yet implemented** +· [Deutsche Version](IOS_ONBOARDING_INSTALLER_PLAN.de.md) + +This revision replaces the October 12 draft. The maintainer has confirmed +the App Store release of 1.1.0. The reviewed source baseline is tag +`ios-v1.1.0`, commit `e55f1fc17483096c4c05286713d6725b951d4d06`, compared +with `bb7c77a`. This plan does not preselect a version number for the next +installer release. + +## 1. Recommendation and goal + +**The released iPhone/iPad app becomes the main setup route.** The +“installer” is primarily a guided setup assistant inside the app. It connects +GitHub, creates a private archive or connects an existing one, checks that it +can be used, and guides the user to their first saved clip. Chrome and +ChatGPT/Codex are then offered as optional additions. + +The goal for new users is: **Install the app → sign in to GitHub → confirm a +private archive → save the first item.** Nobody needs to understand Git, +branches, Personal Access Tokens, or a terminal to do this. A GitHub account, +sign-in, and explicit authorizations remain necessary; SourceBraid does not +automate account verification or two-factor authentication. + +Recommended technical direction: + +- A **GitHub App** registered by the maintainer, which users authorize for + their archive. Users do not create their own developer app. +- Browser sign-in from iOS with PKCE and a small SourceBraid service for + securely exchanging and renewing credentials. +- Direct capture transfers between iOS and GitHub. Markdown and Git history + remain authoritative; local databases are rebuildable caches. +- Automatic creation of a private archive after an early technical proof of + feasibility; a guided GitHub form remains a simpler first stage. +- Existing PAT configurations continue to work. Manual entry moves to + “Advanced setup.” + +The reader, search, chat interface, and refresh functionality from 1.1.0 are +reused. The installer adds their entry flow and connection management. An +update must not force existing users through mandatory setup again. + +This document plans the work. It does not register a GitHub App, operate a new +service, or change existing privacy commitments. The +[release notes](IOS_RELEASE_1.1.0.md), +[reader plan](IOS_READER_INTELLIGENCE_DUO_PLAN.md), and +[outstanding device checks](IOS_DEVICE_ACCEPTANCE.md) remain references for +the existing feature set. + +## 2. Current repository state + +### Release boundaries and changes since the previous plan + +The iOS runtime sources match `ios-v1.1.0`. The release contains 58 changed +files compared with its predecessor. The new foundation available for reuse +includes: + +- “Latest” with seven clips, the Markdown reader, and local full-text search. +- Startup from the cache without downloading all text again; an empty cache + initially loads at most seven article bodies. Search coverage may explicitly + be partial. +- Bounded background refresh plus cancellation and cache coordination. +- Adaptive iPhone/iPad navigation, optional local chat, and read-only Shortcuts. + Apple Intelligence remains independent of setup. + +The Share Extension already existed. GitHub sign-in, repository creation, +and token renewal were **not** added in 1.1.0. + +App Store publication: confirmed by the maintainer. Source version: checked +through the Git tag, version fields, and a local build. This does not establish +an independent check of the binary distributed through the store. In the iOS +tag, Chrome, Codex, and MCP are still at 1.0.1. The follow-up work committed +separately during the review in `467542a` prepares these components for 1.1.0; +the iOS runtime sources remain unchanged from the iOS tag. This additional +commit does not demonstrate public plugin availability. This plan does not +change that follow-up work. + +| Area | Available today | Missing for simple setup | +| --- | --- | --- | +| [iOS settings](../ios/SourceBraid/App/SettingsView.swift), [ViewModel](../ios/SourceBraid/App/SettingsViewModel.swift) | Form for owner, repository, branch, archive folder, and token | Guided sign-in, archive selection, creation, understandable states | +| [GitHubClient](../ios/SourceBraid/Shared/GitHubClient.swift) | Repository query, capture writes | Complete checks for branch, privacy, write capability, and PDF support | +| [Configuration](../ios/SourceBraid/Shared/SourceBraidConfiguration.swift), [Keychain](../ios/SourceBraid/Shared/KeychainStore.swift) | Shared settings and one token for app/Share Extension | Authentication type, expiry, refresh, account binding, and safe migration | +| [Reader](../ios/SourceBraid/Archive/ArchiveLibrary.swift) | Released reading, search, cache startup, and latest seven | Handle a new empty archive correctly; explicitly read back the first capture | +| [Background refresh](../ios/SourceBraid/Archive/ArchiveBackgroundRefresh.swift) | Bounded requests and coordinated cache writes | Respect connection state across restarts; integrate renewable credentials | +| [Shortcuts](../ios/SourceBraid/Archive/ArchiveIntents.swift) | Search and open already downloaded entries | Adopt the new credential model while preserving local identities and offline behavior | +| [Setup script](../scripts/setup_github.py), [package builder](../scripts/build_setup_package.py) | Private repository, support files, and configuration without a token | Python and an authenticated `gh` are required; manual PAT creation remains the final step | +| [Chrome extension](../chrome-extension/sourcebraid/popup.js) | Manual settings, capture, and plugin configuration export without a token | Sign-in and import of a configuration prepared by iOS | +| [Hosted ChatGPT](../chatgpt-mcp/src/auth.mjs) | Prepared OAuth service with per-user grants | GitHub sign-in instead of a separate PAT; deployment and public release approval | +| [Local Codex plugin](../codex-plugin/sourcebraid/scripts/sourcebraid.py) | Python standard library, local search, existing GitHub credentials | Optional simpler configuration import; not part of basic iOS setup | + +### Specific implementation findings + +1. “Test connection” currently saves the new configuration before checking the + network. A failed attempt can therefore switch the existing archive and + remove its local cache. The assistant needs a draft that becomes active + only after successful validation. +2. The current connection check reads only repository metadata. This proves + neither that a branch exists nor that a capture succeeds. “Connected,” + “Reading available,” and “Saving verified” must become separate states. +3. Fields and Save remain usable during the check. After the network call, + the success message uses the inputs that are current at that time. It can + therefore name a different repository from the one tested. An immutable + draft with a revision check must keep request, result, and activation together. +4. A missing archive folder still produces a 404 error. The new empty list + does not repair this. Bootstrap and the true “no clips yet” state must be + distinguished from missing access or an incorrect branch. +5. Returning to the app after a capture restores an existing cache without + automatically loading a new GitHub snapshot. The assistant must explicitly + fetch the first capture; waiting for background refresh is not a reliable + way to complete setup. +6. The block on background requests after sign-out currently exists only in + memory. Saved repository fields remain; after a restart, scheduling can + resume even though the token was removed. Persistent connection state must + constrain scheduling and execution. + +These findings are in `SettingsViewModel.testConnection`, +`GitHubClient.testConnection`, `ArchiveReadClient.snapshot`, +`ArchiveLibrary.configure`, and `ArchiveBackgroundRefresh.schedule`. +They are findings of this plan’s review and were not fixed in product code here. + +### Evidence from this review + +- Source validator: **passed for 1.1.0/build 11**. +- Unsigned generic simulator build with **Xcode 27.1 / 27A9275 passed**; + the generated app bundle also passes the validator. +- The current iPad test run and its limitations are recorded in section 12. +- Earlier figures in the [release record](IOS_RELEASE_1.1.0.md) remain + historical evidence. They are not presented as tests rerun for this review. + +The setup script is a good reference for allowed files and repeatability. +However, its blanket enabling of GitHub Actions with `allowed_actions: all` +should **not** be carried into the new assistant. + +## 3. What the installer should do + +### Required scope of the first user-friendly version + +- Get started without a terminal, Python, or GitHub CLI. +- Explain in everyday language: “Your archive is a private folder on GitHub. + The files belong to you and remain usable independently of SourceBraid.” +- GitHub sign-in, understandable repository authorization, and account display. +- Create a new private archive or connect an existing archive. +- Detect technical fields automatically; show them under “Advanced” only when + needed. +- Initialize, resume after interruption, and validate with specific recovery + actions when errors occur. +- Introduce iOS sharing and guide the first real capture, including reading it + back. +- Provide a “Connections” area that remains accessible for renewal and repair. +- Complete German and English text, VoiceOver, and Dynamic Type. +- Take existing users directly to the reader; offer PAT migration as optional. +- Allow complete setup on an iPad without prior iPhone setup. +- Connect the existing archive on a second device instead of creating a + duplicate. Each installation signs in to GitHub independently. + +### Deliberately deferred + +PDF conversion, Chrome connection, hosted ChatGPT, and local Codex setup must +not block a successful basic start. Apple Intelligence is not required either. +A separate desktop installer is initially a lower priority; the existing +script remains available for technical users and as an independent route +without an iPhone. Android remains on the roadmap. + +## 4. Sign-in: options and decision + +| Option | Benefit | Effort and limitation | Role | +| --- | --- | --- | --- | +| Guided fine-grained PAT | Small change; existing clients remain compatible | The token still needs to be created, copied, and later replaced | Transition and expert route | +| GitHub App, browser flow + PKCE + auth service | Understandable sign-in; targeted repository authorization; automatic renewal | Operating a small service and new credential handling | Recommended product goal | +| GitHub App, device flow | No app secret in the client; no dedicated token exchange service required | Code entry, polling, and increased phishing exposure | Evaluate as a justified alternative, not the iOS default | +| OAuth App | Familiar sign-in route | Private repositories typically require broad `repo` access | Lower priority than a GitHub App | +| Desktop script with `gh auth login` | Much is already available | Additional installation and terminal knowledge | Addition for advanced users | + +For comparison of the breadth of OAuth App access: +[GitHub: OAuth scopes](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps). + +GitHub documents PKCE in the web flow but still requires a `client_secret` +during code exchange. The plan therefore does not promise a purely native web +flow without a secret. The auth service is our architecture decision for +keeping that secret on the server. +[GitHub: User access token flow](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-web-application-flow-to-generate-a-user-access-token) + +GitHub prefers PKCE over device flow and recommends the latter only where a +justified need exists in constrained environments. A client secret embedded +in a native app would not be confidential either. Device flow therefore does +not become the default solely to save server work. +[GitHub: Best practices](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/best-practices-for-creating-a-github-app#client-secrets) + +For the existing PAT route, a GitHub link can prefill the name, owner, expiry, +and Contents permission. The user must check that the single archive repository +is selected; the link does not create a token automatically. Tokens belong +only in the protected input field, never in a chat, QR code, support ticket, +or configuration export. +[GitHub: PAT templates](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#pre-filling-fine-grained-personal-access-token-details-using-url-parameters) + +## 5. The user journey + +| Step | Display and action | What SourceBraid handles | +| --- | --- | --- | +| 1. Welcome | “Your own knowledge archive”; “Set up” | Brief explanation of GitHub and private ownership; existing users skip this step | +| 2. Connect GitHub | “Connect to GitHub”; alternatively “Create a GitHub account” | Browser sign-in, return, and verification of the signed-in account | +| 3. Choose archive | “New private archive” or “Existing archive” | Suggest `sourcebraid-archive`, check for name collisions, and offer existing choices | +| 4. Confirm access | GitHub displays SourceBraid and the repositories to authorize | Guide selection of exactly the archive; verify the actual installation afterward | +| 5. Prepare archive | “Preparing archive” with named substeps | Determine the branch, initialize the archive folder with `.gitkeep`, preserve existing files | +| 6. First save | Short guide for Safari → Share → SourceBraid | Save an actual capture, explicitly fetch it, and open it in the existing reader | +| 7. Done | “Your archive is ready” | Optionally connect PDF, Chrome, or ChatGPT; all can be skipped | + +The order of archive creation and installation authorization depends on the +chosen bootstrap method; the interface shows only the next step needed at +that point. Signing in to GitHub and installing the GitHub App are separate +authorizations. Successful sign-in alone does not establish archive access. + +Defaults apply only to new archives. Existing names, folders, and branches are +not renamed. In particular, the actual default branch is detected instead of +assuming `main` everywhere. The previous name `sourcebraid-private` is no proof +of privacy and must not be confused with the maintainer’s private authoring +repository. + +No test silently creates an apparently real article. An optional synthetic +setup file must be announced as such. A capture initiated by the user with +correct provenance is the strongest evidence for successful completion. +Read-only access remains a usable mode with the message “Authorization to +save is missing.” + +### Entry points for new and existing installations + +| Starting point | Entry flow | Success | +| --- | --- | --- | +| New iPhone or iPad without configuration | Assistant replaces “Connect your archive → Settings” and the empty reader placeholder | Private archive connected; first capture retrievable | +| Update of a working 1.1.0 installation | Directly to “Recent,” with an optional notice in “Connections” | Existing archive and cache remain usable | +| Second device / reinstallation | Prominently offer “Connect existing archive” | Select the same repository and receive separate credentials | +| Expired or revoked credential | Repair the connection instead of creating a new archive | Confirm identity and renew access | +| Read-only permissions | Reader and search with clear labeling | Reading usable independently of capture | + +The existing synthetic demo is a debug/test mode, not a released customer +feature. A public preview would be separate work and is not required for +this installer. + +### iPhone and iPadOS + +A shared `OnboardingCoordinator` controls the same functional flow. On iPhone +and in narrow iPad windows, one step is displayed at a time; a wide iPad +window can show progress and explanation alongside the current form. +Navigation state, scroll position, and inputs survive rotation, resizing, +and return from the browser. No fixed screen widths or fullscreen requirement. + +Keyboard input, Tab navigation, visible focus, VoiceOver, large text, and +portrait/landscape orientations are part of acceptance. Existing adaptive +navigation is reused; an iPad is not tested merely as an enlarged iPhone. + +`WindowGroup` alone does not prove support for multiple simultaneously usable +app windows, and this is not an implicit new release promise. O00 checks scene +configuration and actual window support. The connection coordinator permits +at most one active sign-in/activation across the app. Other supported windows +show the operation instead of starting another. Late results from a closed +or superseded assistant must not overwrite a newer connection. + +## 6. Technical architecture for iOS and iPadOS + +Apple provides `ASWebAuthenticationSession` as a system mechanism for web +sign-in. We use it instead of a custom password form or the capture WebView. +Implementation preserves the existing iOS 17 support and checks callback API +availability. +[Apple: ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) + +The presentation anchor comes from the window that initiated sign-in. The +session and presentation delegate are retained until completion; do not use +a global “first window” from all scenes. A startup failure or cancellation +returns to the existing draft. +[Apple: Web sign-in](https://developer.apple.com/documentation/authenticationservices/authenticating-a-user-through-a-web-service) + +The newer HTTPS callback requires iOS/iPadOS 17.4 and an associated domain. +For the still-supported OS versions 17.0–17.3, a tested older custom-scheme +callback with PKCE and state binding is required. Callback APIs and +Associated Domains are new configuration, not part of 1.1.0. Raising the +minimum version is not planned here. +[Apple: Callback API](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/callback) + +```mermaid +flowchart LR + I["iOS assistant"] --> W["System browser: GitHub sign-in"] + W --> A["SourceBraid auth service"] + I -->|"Code exchange / refresh"| A + A -->|"OAuth"| G["GitHub"] + A -->|"Credentials over HTTPS"| I + I -->|"Store locally"| K["Shared iOS Keychain"] + K --> I + K --> S["Share Extension"] + I -->|"Read / prepare archive"| R["Private GitHub archive"] + S -->|"Save capture directly"| R +``` + +### Responsibilities of the auth service + +The service keeps the client secret in a protected server environment and +supports sign-in and refresh. It does not proxy archive files and, in the +basic model, does not need a GitHub App private key to mint installation +tokens. iOS uses GitHub App user access tokens. + +Planned contract: + +1. iOS starts a short-lived sign-in with random `state` and PKCE S256. +2. The service binds the transaction to the initiating app session and an + exactly allowlisted callback address; no arbitrary redirect destinations + supplied by callers. +3. GitHub authenticates the user. On return, state, expiry, and the binding + to the original request are checked. +4. A one-time return code is redeemed from the app over HTTPS. The verifier + and code exchange must belong to the same PKCE transaction. GitHub tokens + must not enter callback URLs or browser histories. +5. The service exchanges the GitHub code using the server-side secret and + delivers the tokens only to the initiating app. The app checks GitHub + identity and archive access before activation. +6. Token renewal uses a protected endpoint bound to the session. It must not + be an open token proxy or arbitrary HTTP proxy. + +The broker session proof must remain verifiable after the app restarts and +the GitHub access token expires. O00 defines its secure storage, binding to +the authorized connection, lifetime, renewal, and revocation. An ordinary +refresh should not require another browser sign-in; an expired or revoked +broker session may require one. + +This is an interface draft to be reviewed, not a newly invented OAuth +protocol. A maintained OAuth implementation and a security review must cover +replay, login CSRF, app impersonation, and failed handoffs. + +**Privacy impact:** The service processes tokens at least transiently and +therefore cannot be advertised as having “no access to credentials.” The +proposal calls for no persistent token storage for iOS, no content database, +and no logging of tokens, codes, or request bodies. Short-lived session data, +operational metadata, retention periods, hosting, and secret rotation must be +defined before release. The existing Cloudflare infrastructure is a possible +hosting route, not an already available native sign-in solution. + +### A consistent connection instead of a single token string + +The new connection receives a stable local ID and a revision. A +`CredentialProvider` supplies credentials together with their associated +GitHub/archive identity. Settings, reader, and extension must not combine +a new owner and an old token read at different times. + +Keychain and App Group defaults do not form a shared database transaction. +A new credential version is therefore first prepared and checked separately, +then activated through a coordinated update of an active reference. Partially +written states must be detected after process termination; the last valid +connection remains active until successful activation. A concrete +commit/recovery contract belongs in O01. + +The same archive with renewed credentials retains its cache and Shortcut IDs. +An explicit archive switch uses the existing invalidation and cache cleanup +only after successful activation. All running operations check their +connection revision before applying a result. During a switch, a capture +already in progress is completed or cancelled in a controlled way, never +retried against the newly selected repository. + +### Lifecycle of all credential consumers + +The shared Keychain receives a versioned credential model with authentication +type, stable GitHub user ID, app/installation association, access and refresh +tokens, and expiry times. Nonsecret archive configuration remains separate. +Existing Keychain and App Group identities are preserved. + +| Consumer in 1.1.0 | Adaptation for renewable credentials | +| --- | --- | +| `SettingsViewModel` and assistant | Validate an immutable draft; activate only the same revision | +| `ArchiveLibrary` / `ArchiveReadClient` | Obtain credentials before requests; switch tokens within the same archive without deleting the cache | +| `ArchiveBackgroundRefresh` | Schedule only with a persistently authorized connection; bounded refresh or clean cancellation, no login dialog | +| `GitHubClient` / `ShareViewModel` | Load matching configuration and credentials; detect switches during image fetching/upload | +| `ArchiveIntents` | Preserve local device authentication and cache access; no interactive sign-in or repository creation from Shortcuts | + +The existing `ArchiveRefreshCoordinator` protects cache writes within the app +process. It does not replace a cross-process token lock shared with the Share +Extension. The new credential model must connect both coordination layers +without removing existing cancellation and deletion safeguards. + +GitHub documents defaults of eight hours for user access tokens and six +months for refresh tokens; a successful refresh invalidates the old pair. +The implementation uses the returned expiry values and checks the app +configuration that is actually enabled. +[GitHub: Refresh](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/refreshing-user-access-tokens) + +The app and Share Extension must not consume the same refresh token +concurrently. Cross-process coordination, rereading after acquiring the lock, +and atomic replacement of the credential set are required. A refresh timeout +must not trigger a blind retry loop; if the new token pair is lost, signing +in again is an intended recovery route. The extension has a short execution +window: when necessary, clearly direct users to “Open SourceBraid and renew +the connection.” A failed upload must never be reported as saved. + +The existing Keychain item is not synchronizable and is bound to the device. +A newly configured iPad therefore does not inherit an iPhone token. The +default remains: sign in on the second device and select the same archive. +Authentication secrets belong neither in `SceneStorage` nor in configuration +files. Only nonsecret steps and inputs are stored there or in assistant state +restoration. Persistent credentials and broker session proofs remain in the +Keychain; sign-in attempts that have not been securely saved restart after +process loss. +[Apple: Device-bound Keychain](https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlockthisdeviceonly) + +Sign-out and revocation invalidate pending login/refresh results. A token +arriving afterward must not reactivate the connection. A persistently stored +`signedOut` state blocks background jobs even after app restart. “Offline +with a valid local connection” and “signed out” remain different states; +readable cache must not disappear on every network error. + +In particular, the local connection remains usable for offline reading and +search when only the short-lived access token has expired and the auth service +is unreachable. Network token validity and local access authorization are +separate conditions. Explicit sign-out or removal of local data retains its +existing effect; a failed refresh does not delete the cache. + +## 7. Archive creation and permissions + +### A. Smallest releasable route + +The assistant opens a prepared GitHub form for a private repository. It then +guides installation of the GitHub App with the archive selected. Afterward, +it uses the API to check repository ID, owner, private status, and actual +access; form values and return parameters are not trustworthy confirmation. + +Example of the documented form route: +[Prepare a private archive on GitHub](https://github.com/new?name=sourcebraid-archive&visibility=private&owner=%40me). +[GitHub: Repository form parameters](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository#creating-a-new-repository-from-a-url-query) + +### B. Preferred convenience goal: creation directly from the app + +The current GitHub documentation lists **Repository creation** as a narrower +permission than Administration and describes automatic access to repositories +the app creates. This makes direct creation feasible in principle. +[GitHub: Choosing permissions](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app) + +After installation/authorization, iOS calls `POST /user/repos`, explicitly +with `private: true`. The response provides the repository ID to verify. +Access is then checked again through the installation, and the archive is +initialized. The public API default must never be used accidentally. +[GitHub: Create a repository](https://docs.github.com/en/rest/repos/repos?apiVersion=2026-03-10#create-a-repository-for-the-authenticated-user) + +**Early proof O00:** a fresh account without a repository, actual availability +of the creation permission, installation with zero/selected repositories, +automatic access, an existing installation with a declined permission +upgrade, name collision, and a timeout immediately after creation. This +sequence has not been exercised in practice for this plan. If the proof +fails, implement route A first. Do not fall back broadly to administration +permissions or all repositories. + +### Target permission model + +| Function | Planned access | Boundary | +| --- | --- | --- | +| Reading | Read Metadata and Contents | Independent read-only mode | +| Capture | Write Contents in the selected archive | No general repository administration permissions | +| New archive | Separately justified Repository creation permission for route B | Route A needs no authority to create repositories | +| PDF setup | Write Contents and Workflows for the support files | Separate setup step, not a default capture permission | +| PDF status | Read Actions only if necessary | No automatic changes to organization policies | +| Hosted ChatGPT | Its own GitHub connection limited to reading | No forwarding of the iOS write token | + +The specific API endpoints and permissions will be verified in O00 with +minimal test accounts, particularly +[Contents write access](https://docs.github.com/en/rest/repos/contents#create-or-update-file-contents) +for normal files and workflow files. GitHub App permissions are tied to the +registration and are not freely switchable OAuth scopes. An optional PDF +toggle does not retroactively make a globally requested workflow permission +optional. For the first version, PDF setup uses a separate, narrowly scoped +setup route; a separate setup app may follow later. Each additional app +increases usability complexity and must provide a demonstrable benefit. + +Likewise, “Read only” in the interface does not reduce an already granted +GitHub permission. Existing Contents-read PATs remain supported for this mode. +The capture GitHub App requests Contents write permission; guaranteed +read-only OAuth sign-in needs a correspondingly separate permission +registration and is not promised as an already solved property of capture +sign-in. + +Existing archives are connected only after selection. Personal accounts are +the first fully supported setup route; organizations, SSO, and pending admin +approvals receive their own states. Rules and branch protection are not +disabled to force a capture through. + +## 8. Initialization and PDF conversion + +### First capture in the released reader + +1. Create the new archive with an initial commit and `.gitkeep`; explicitly + display the empty state. A 404 caused by missing authorization remains an error. +2. Have the user save a public example of their choice or their own authorized + content through the actual Share Extension. +3. Associate the successful capture with archive identity, path, and available + commit/file evidence; no additional token in the return notification. +4. On return, explicitly read the saved path or perform a bounded refresh of + the archive snapshot. Current cache-only startup is insufficient here. +5. Open the clip in the existing reader and set “Saving verified.” A failed + read leaves the step incomplete without immediately repeating the upload + and creating duplicate captures. + +The assistant waits neither for a complete search index nor for an +OS-scheduled background job. The seven-body initial download limit remains; +targeted capture verification has a separate limit. For large existing +archives, setup must not trigger a full download. “Archive connected,” +“Saving verified,” and “Search coverage” are independent indicators. + +### Repeatable bootstrap + +Bootstrap receives an explicit file list and becomes repeatable: + +- Prepare a new archive with an initial commit and `/.gitkeep`. + Git does not store empty directories. An archive without captures then + becomes a valid state; an archive folder that has not yet been created + must also be handled clearly during first setup. +- Create only planned files. Compare and preserve existing files; show + conflicts. Do not overwrite for convenience. +- After an ambiguous network response, check remotely first whether the step + has already completed. No second repository or duplicate test capture. +- Document setup steps and remaining work without secrets. Cancellation does + not delete a repository that has already been created. +- Preserve capture paths, YAML frontmatter, and URL-hash index shard formats. + A new configuration schema requires explicit versioning and migration. + +PDF remains a separate step, “Automatically convert PDFs to Markdown.” +Reuse the existing allowlist: + +```text +.github/workflows/convert-pdfs.yml +requirements-docling.txt +scripts/convert_pdfs.py +scripts/push_with_retry.py +``` + +The files come from the verified public project sources and are bundled with +a pinned version. Neither `website/` nor a private `web-clips/`, local +configuration, or Git history is copied. The private authoring repository is +not forked or exported either. + +The PDF step explains processing through GitHub Actions and possible +quotas/costs, without promising unlimited free operation. A synthetic PDF +checks the actual end-to-end run. “PDF uploaded,” “Conversion pending,” and +“Markdown available” remain separate. When Actions are disabled, normal +saving, reading, and search remain usable; pending PDF conversion is visible +rather than reported as complete. + +## 9. Connect Chrome, ChatGPT, and Codex as additions + +### Chrome + +The existing share button in the reader exports Markdown content. Exporting +repository settings from iOS remains a planned new feature. + +After basic iOS setup, the app can provide a configuration without credentials. +Existing keys remain compatible: + +```json +{ + "owner": "example-user", + "repo": "sourcebraid-archive", + "branch": "main", + "root_folder": "web-clips" +} +``` + +This is a synthetic example, not a real configuration. Chrome first needs +an import dialog with a preview, strict path validation, and its own GitHub +sign-in. Imported values do not grant access. OAuth callbacks, Manifest V3 +service worker restarts, and any necessary new browser permissions receive +targeted checks. No iOS tokens, cookies, or refresh tokens are transferred to +the extension. + +Later QR code pairing may contain only configuration or a short-lived, +one-time pairing proof bound to the session. Repository names must also be +treated as private. Installation and authorization of the Chrome extension +take place in the browser; the iOS app can guide them but cannot silently +install the extension. + +### Hosted ChatGPT + +The [prepared MCP integration](CHATGPT_MCP_DEPLOYMENT.md) already has per-user +OAuth grants. However, its OAuth connects **ChatGPT to SourceBraid**. The +consent page still requests a separate GitHub PAT. This sign-in does not yet +solve the GitHub token problem. + +Expansion requires an independent read-only GitHub connection, preferably a +separate GitHub App registration without write permissions. Consent and the +association with a user, installation, and exactly one archive are checked +on the server. The existing code explicitly accepts `github_pat_` and +determines identity through `/user`; installation tokens are therefore not +an interchangeable substitute. Credential type, authorization, and renewal +must be deliberately adapted and tested with two separate test accounts. + +Between ChatGPT and MCP, a dedicated OAuth flow with PKCE, resource binding, +and token validation remains necessary. A GitHub token is not an MCP access +token. +[OpenAI: Plugin authentication](https://developers.openai.com/plugins/build/auth) + +This hosted route processes requested content at SourceBraid/Cloudflare and +OpenAI. It receives its own consent and privacy description. The iPhone does +not become a permanently reachable MCP server. The connection must work +independently of a running iPhone. Public availability is announced only +after deployment, testing, and approval; before that, the app does not show +a connection button that appears to work. + +### Local Codex plugin + +The existing route with a local Python runtime and GitHub access remains +available. Configuration from iOS can simplify it later. The standard library +remains sufficient; this plan requires no new Python package. Reading and +confirmed write/delete actions retain separate requirements. No ChatGPT or +Codex account is required for basic iOS features. + +## 10. States, errors, and recovery + +The assistant stores a draft separately from the active configuration. +One possible sequence is: + +```text +unconfigured → authenticating → choosingArchive → authorizingArchive +→ provisioning → readReady → captureVerified → completed +``` + +`cancelled`, `offline`, `authorizationPending`, `reauthRequired`, and +`repairRequired` are resumable states. PDF and additional integrations are +separate capabilities, not required states in this sequence. + +`readReady` is an independently usable endpoint for read-only operation. +`captureVerified` requires actual evidence of writing and reading back. +“Setup complete” is therefore always stored together with the selected +read-only or capture mode; incomplete search coverage blocks neither. + +### Migration from 1.1.0 + +1. Read the legacy configuration and existing PAT without rewriting them. + Working installations continue to open in the reader. +2. In “Connections,” optionally offer “Set up GitHub sign-in”; carry over + owner, repository, branch, and root from the existing connection. +3. Check new sign-in and archive access in the separate draft. Cancellation + preserves the old PAT and the complete previous connection state. +4. Activate the successful connection once, with recovery support; switch all + consumers to the same revision. Within the same archive, preserve the cache + and `cacheKey:path` Shortcut identities. +5. Remove the old PAT locally only after activation is secure. Explain GitHub + revocation separately, as the PAT may still be used in Chrome. +6. If errors occur later, repair the new connection first. No silent fallback + to an already revoked PAT and no creation of a new archive. + +| Situation | User guidance / required behavior | +| --- | --- | +| Sign-in cancelled | Return to the previous page; existing connection remains active | +| Wrong GitHub account | Display the account clearly and offer an explicit account switch | +| 401 / revoked token | Renew once where appropriate or sign in again; no infinite loop | +| 403 / missing permission | Identify the missing authorization; distinguish organization approval from rate limits | +| 404 | Distinguish missing visibility, wrong repository, missing branch, and empty archive | +| Public repository | Require a private alternative in the beginner route; do not automatically change an existing repository | +| Name taken | Check an existing archive or suggest another name; overwrite nothing | +| Protected branch | Explain the block; explicitly choose an allowed branch while preserving protection | +| Interruption after creation | Find the existing repository and continue; no automatic deletion | +| Offline / rate limit | Preserve progress, retry with backoff, keep existing cache usable | +| New device / reinstalled app | Sign in again and select the existing archive; no duplicate creation | +| Share Extension without valid access | Clear error message and repair in the main app | + +“Sign out on this device,” “Revoke GitHub authorization,” “Remove local data,” +and “Delete repository” are different actions. The assistant does not delete +archives. Revoking GitHub App authorization can affect other devices; +independent revocation per device is not promised without verification. +[GitHub: Revoke authorization](https://docs.github.com/en/apps/using-github-apps/reviewing-and-revoking-authorization-of-github-apps) + +## 11. Implementation packages and order + +Estimates are person-days for development and testing, not commitments. +App Store review, operator approvals, and waiting times are excluded. + +| Package | Work and affected areas | Outcome / acceptance | Rough size | +| --- | --- | --- | --- | +| O00 – Feasibility after 1.1.0 | Test GitHub App, web flow/PKCE/refresh, creation permission, callbacks from OS 17 onward, and iPad scene configuration | Written API/permission evidence, decision between archive routes A and B, and hosting contract | 2–3 days | +| O01 – Connection and migration | Immutable draft, revision-based activation, CredentialProvider for all consumers, legacy PAT migration, persistent signed-out state | Old connection survives errors; late results and background jobs do not reactivate it | 4–6 days | +| O02 – Sign-in | Auth service, AuthenticationServices, window-bound callbacks, cross-process refresh | Login/cancellation/refresh/revocation, including restart and negative security tests | 5–8 days | +| O03 – Archive | Repository selection/creation, branch detection, idempotent bootstrap, empty reader | Fresh private archive and existing archive successfully verified | 3–5 days | +| O04 – Assistant on iPhone and iPad | First-run entry, update/second-device route, narrow/wide windows, actual Share Extension, targeted capture readback | iPhone and iPad independently reach their first capture; existing reader is preserved | 4–6 days | +| O05 – Release acceptance | DE/EN, accessibility, migration, privacy text, operations, and device checks | First version ready for approval without mandatory additional integrations | 3–5 days | +| O06 – PDF | Separate permission/setup route and conversion status | Complete synthetic PDF conversion; normal archive usable independently | Estimate separately afterward | +| O07 – Chrome | Configuration import, own sign-in, optional pairing later | Same archive usable, no token forwarding | Estimate separately afterward | +| O08 – ChatGPT/Codex | Read-only GitHub connection for the MCP service, local import | Separate permissions and working account isolation | Estimate separately afterward | + +O00–O05 total approximately **21–33 person-days**. The higher range compared +with the previous draft accounts for migration of released installations, +background consumers, iPad windows, and targeted capture readback. O00 may +change this estimate. + +O00 and the locally testable parts of O01 can be prepared in parallel. O02 +builds on their connection contract, and O03 on verified authentication. +O04 connects these components; O05 accepts the first complete version. PDF +and additional integrations follow independently. + +A guided PAT route remains available but is not a mandatory new intermediate +release. A small early fix for “Test connection” could be released separately. +It does not yet meet the goal of “no manual token creation” and does not +replace the installer. + +Proposed new iOS components: `OnboardingCoordinator`, +`GitHubAuthorizationClient`, `GitHubRepositorySetupClient`, and +`CredentialProvider`. These names are drafts, not existing files. +Capture code remains focused on its existing format. + +| Existing area | Planned change | +| --- | --- | +| `ContentView.swift`, `SettingsView.swift` | First-run entry and connections; preserve existing reader/tab navigation | +| `SettingsViewModel.swift`, `SourceBraidConfiguration.swift` | Draft, legacy decoding, immutable test request, and revision-based activation | +| `KeychainStore.swift`, App Group | Versioned credentials and cross-process coordination; preserve identities | +| `GitHubClient.swift`, `ArchiveReadClient.swift` | CredentialProvider and targeted connection/capability checks | +| `ArchiveLibrary.swift`, `ArchiveBackgroundRefresh.swift`, `ArchiveIntents.swift` | Preserve existing cache/cancellation mechanisms; integrate lifecycle and readiness | +| `ShareViewModel.swift` | Consistent connection, refresh limit, and capture evidence | +| `Info.plist`, entitlements, project, localized text | Callback/domain configuration, availability, new screens, and DE/EN | +| New auth service | Its own sources, tests, and public release allowlist; do not export the private website | + +## 12. Tests and mandatory acceptance criteria + +### Checks rerun on October 15, 2026 + +| Check | Result of this review | +| --- | --- | +| `python3 scripts/validate_ios_release.py` | Passed, 1.1.0/build 11 | +| Generic simulator build, app and extension, without signing | Passed with Xcode 27.1 (27A9275) | +| Validator on the generated simulator app bundle | Passed, 1.1.0/build 11 | +| Existing unit/integration tests on iPad Pro 13-inch (M5), runtime 27.0 | **47 passed, 0 failures** | +| Existing UI suite on the same iPad simulator | **7 passed, 0 failures** | + +Build and tests used the tag’s iOS sources, dedicated Derived Data under +`/private/tmp/sourcebraid-installer-review-20261015-derived`, and serial +simulator execution. Test evidence: +`/private/tmp/sourcebraid-installer-review-20261015-ipad.xcresult`, plus +`sourcebraid-installer-review-20261015-ipad.log` and +`sourcebraid-installer-review-20261015-build.log` in the same temporary directory. +These artifacts remain outside the repository. Xcode could access the simulator +and package cache only outside the sandbox; the first blocked attempt is not +a product failure. + +The UI suite uses synthetic debug data. It does not prove real GitHub sign-in, +first-time Share Extension setup, narrow iPad windows, multiple active windows, +or minimum-OS functionality. Physical devices and an actual App Store binary +comparison were not tested in this review. Node/Python core suites and MCP +tests were not rerun for this documentation-only change. Previous release +evidence remains separate. + +### Automated + +- State transitions, draft/activation, cancellation, and migration of old settings. +- PKCE, incorrect `state`, foreign callback URL, replay, expired session, + lost token response, and concurrent refresh from app/extension. +- User/repository/installation binding, foreign repository ID, and two + separate accounts; no access through manipulated configuration imports. +- New/missing branches, empty archive, existing files, name collision, + timeout after commit, and repeated bootstrap. +- Read-only access, denied writes, branch protection, PDF without a workflow. +- No secrets in logs, diagnostic exports, URLs, packages, or screenshots. +- Change inputs during a running connection check: result and activation must + remain tied to the tested draft; discard outdated results. +- Process termination between credential preparation and activation: after + restart, either the complete old or complete new connection, never a mixture. +- Sign out → quit app → restart: no new background requests without an active + connection. Late refresh responses must not undo sign-out. +- PAT → GitHub App within the same archive: preserve cache, downloaded clips, + and Shortcut identities; no full reindexing required for setup. +- Access token expired and device offline: downloaded clips and local search + remain usable without a network refresh; new network actions show the required + reconnection state. Explicit sign-out remains separate. +- Empty initialized archive → capture → return: the clip appears through a + targeted fetch even without a background run or manual refresh workaround. + +### On physical devices and with GitHub test accounts + +- Fresh account without a repository through to the first capture; connect + an existing archive without overwriting it. +- Existing PAT users migrate only after successful new sign-in. +- Sign-in with two-factor authentication, cancellation, and the wrong account. +- App backgrounding, interrupted connection, token expiry, revocation, + reinstallation, and Keychain access before the first unlock. +- Safari sharing, other share sources, iPhone/iPad, VoiceOver, large text, + German and English, and the oldest supported iOS version. +- iPad fullscreen and in narrow/wide windows, resizing during sign-in, + hardware keyboard and focus, browser return to the initiating window. +- If multiple app windows are approved: second sign-in attempt, closing a + window, confirming an outdated assistant, and sign-out during refresh. + Without multiwindow approval, do not advertise this capability as tested. +- Check callbacks separately on OS 17.0–17.3 and 17.4+. Testing on runtime + 27.0 does not replace this availability check. +- iPhone first / iPad second and iPad first / iPhone second: same archive, + independent sign-in, and no accidental creation of a second repository. +- Five people without GitHub experience test the flow. Target: at least four + complete their first capture without personal assistance; setup time and + abandonment points are observed with consent. This is a proposed product + goal, not a measured result or a new telemetry requirement. + +### Repository checks during later implementation + +For shared capture, script, or plugin changes, run both core suites from the +repository root: + +```bash +node --test tests/capture-utils.test.js +python3 -m unittest discover -s tests -p "test_*.py" +``` + +Build iOS from `ios/` without signing and additionally run the new unit/UI +tests on a suitable simulator: + +```bash +xcodebuild \ + -project SourceBraid.xcodeproj \ + -scheme SourceBraid \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO \ + build +``` + +For changes to the hosted service, also run its `npm ci`, `npm test`, and +`npm run build` under `chatgpt-mcp/`. Define dedicated tests/build checks +for a new auth service. Document missing platforms or dependencies openly; +builds replace neither live OAuth nor device acceptance. + +**The first version is complete** when the normal iOS route works without +PAT creation, a terminal, or an additional plugin; a private archive is +verified to belong to the user’s account; a capture is successfully read +back; and cancellation, renewal, and existing archives are handled reliably. +PDF and additional integrations have their own acceptance criteria. + +These criteria also apply to an independently configured iPad. Upgrading from +1.1.0 must not force mandatory setup again or cache loss. Read-only mode has +its own acceptance without a write operation. Minimum-OS, accessibility, and +physical Share Extension checks must be demonstrated for the new installer; +the release of 1.1.0 does not automatically close outstanding cases documented +in the device checklist. + +## 13. Questions to resolve before release + +1. **Operations:** Who owns the auth service, domain, secrets, monitoring + without content/token logs, outages, abuse protection, and recovery? +2. **Permissions:** Which actually tested GitHub App registration provides + the minimum necessary access? Will direct creation be approved immediately? +3. **Privacy:** Update new authentication processing, operators, retention, + and revocation in [PRIVACY.md](../PRIVACY.md), iOS notices, and store + disclosures; do not weaken legal text without maintainer review. +4. **Distribution:** The iOS app remains the main entry point. Show only + real published store/plugin links; clearly distinguish development packages. +5. **Migration:** Preserve the PAT fallback, do not rename existing archives, + and keep Keychain and formats compatible. +6. **Release:** Include only public allowlisted files, no private sources or + history; observe required checks and DCO sign-off. + +The next concrete implementation step is **O00**, in parallel with the local +connection foundations from **O01**: verify sign-in, refresh, and private +archive creation with minimal permissions while protecting the 1.1.0 +connection against failed or outdated settings attempts. Authentication and +the assistant on iPhone and iPad are then implemented against the same +verified contract. diff --git a/docs/IOS_RELEASE_1.1.0.de.md b/docs/IOS_RELEASE_1.1.0.de.md index 7450b2b..2ee83bd 100644 --- a/docs/IOS_RELEASE_1.1.0.de.md +++ b/docs/IOS_RELEASE_1.1.0.de.md @@ -2,6 +2,21 @@ 15. Oktober 2026 · [English](IOS_RELEASE_1.1.0.md) +## Veröffentlichungsnachtrag — 15. Oktober 2026 + +Der Maintainer hat bestätigt, dass iOS **1.1.0** im App Store veröffentlicht ist. +Chrome, das lokale Codex-Plugin und der gehostete ChatGPT-MCP-Dienst werden jetzt +als Release-Pakete **1.1.0** vorbereitet. Ihre Store-/Verzeichnisprüfung und +Veröffentlichung bleiben getrennte Schritte; für den gehosteten MCP-Dienst +stehen Bereitstellung und Live-Tests noch aus. + +Das folgende Vorprüfungsprotokoll bewahrt die damals tatsächlich geprüften +Versionen und Ergebnisse, einschließlich des separaten Submission-Bundles +**1.0.1**. Die Veröffentlichung schließt die offenen Geräte- und +Modellqualitätsprüfungen nicht ab. + +## Stand vor der Veröffentlichung + Der Maintainer hat diesen Release-Kandidaten nach eigenem Funktionstest zur Veröffentlichung freigegeben. App und Share Extension verwenden **1.1.0/Build 11**. Der geplante GitHub-Tag **`ios-v1.1.0`** gilt nur für iOS. diff --git a/docs/IOS_RELEASE_1.1.0.md b/docs/IOS_RELEASE_1.1.0.md index 942e10b..36cc3d2 100644 --- a/docs/IOS_RELEASE_1.1.0.md +++ b/docs/IOS_RELEASE_1.1.0.md @@ -2,6 +2,19 @@ October 15, 2026 · [Deutsch](IOS_RELEASE_1.1.0.de.md) +## Publication update — October 15, 2026 + +The maintainer has confirmed that iOS **1.1.0** is published in the App Store. +Chrome, the local Codex plugin, and hosted ChatGPT MCP are now being prepared +as **1.1.0** release packages. Their store/directory review and publication remain +separate; the hosted MCP service still requires deployment and live testing. + +The pre-publication record below preserves the versions and results actually +checked at that time, including the separate **1.0.1** submission bundle. +Publication does not close the outstanding device and model-quality checks. + +## Pre-publication status + The maintainer approved this release candidate for publication after a hands-on functional check. App and Share Extension use **1.1.0/build 11**. The planned GitHub tag is **`ios-v1.1.0`**, scoped to iOS. Chrome, the Codex diff --git a/ios/README.md b/ios/README.md index 2383a22..4425b8a 100644 --- a/ios/README.md +++ b/ios/README.md @@ -1,10 +1,9 @@ # SourceBraid for iOS The iOS project contains a SwiftUI archive reader, configuration screens, and a -native Share Extension. The maintainer approved the **1.1.0** release candidate -for publication on October 15, 2026 after a hands-on functional check. Xcode -Cloud distribution and App Store availability are tracked separately in the -[release notes](../docs/IOS_RELEASE_1.1.0.md). The Share +native Share Extension. The maintainer confirmed on October 15, 2026 that +**1.1.0** is published in the App Store. The [release notes](../docs/IOS_RELEASE_1.1.0.md) +preserve the earlier preflight results and record the publication update. The Share Extension saves shared URLs, readable Safari content and its relevant image assets, selected text, PDFs, and other files into the same GitHub repository and URL-hash-sharded `web-clips/index/*.jsonl` format as the Chrome extension. @@ -32,13 +31,14 @@ Reader-only use needs `Contents: Read` for the selected GitHub repository. The S ## TestFlight The project uses automatic distribution signing for Apple Developer team -`H76K2HQUFB`. The approved release candidate is version `1.1.0` (build `11`) for -both the app and Share Extension. Its planned iOS-only GitHub tag is -`ios-v1.1.0`; Chrome, the Codex plugin, and hosted MCP remain at `1.0.1`. +`H76K2HQUFB`. The source version is `1.1.0` (build `11`) for both the app and +Share Extension; the maintainer has confirmed App Store publication of `1.1.0`. +The iOS-only tag name is `ios-v1.1.0`. Chrome, the local Codex plugin, and hosted +MCP now target `1.1.0` under the separate `v1.1.0` submission-bundle release. Before every later upload, increment `CURRENT_PROJECT_VERSION` for both targets. The maintainer reports that the Xcode Cloud workflow uses Xcode 27.1 RC. -Cloud archive, distribution, and App Store processing must each be verified -from their actual results; none is implied by local validation. +For future uploads, cloud archive, distribution, and App Store processing must +each be verified from their actual results; none is implied by local validation. Run the release preflight from the repository root before archiving: @@ -133,26 +133,29 @@ xcodebuild \ ## Local acceptance tests -Use Xcode 27.1 for the complete candidate, including the Duo API. Select an +Use Xcode 27.1 for the complete iOS implementation, including the Duo API. Select an installed simulator and run **Product → Test**; the shared scheme includes unit/integration tests and synthetic UI tests. No test needs a real archive or GitHub token. For CLI commands, synthetic demo setup, and real-device checks, see [English](../docs/IOS_DEVICE_ACCEPTANCE.md) / [Deutsch](../docs/IOS_DEVICE_ACCEPTANCE.de.md). -The maintainer has approved publication of version 1.1.0/build 11. The remaining +The maintainer has confirmed App Store publication of version 1.1.0. The remaining minimum-OS, physical Duo transition, dark-appearance, accessibility, and real model-quality checks remain documented in the acceptance checklist; this -approval does not mark the full device matrix as passed. Historical test +publication does not mark the full device matrix as passed. Historical test entries retain the version and build that were actually tested. -## Deutsch: Release-Kandidat 1.1.0 - -Der Maintainer hat Version **1.1.0/Build 11** am 15. Oktober 2026 nach eigenem -Funktionstest zur Veröffentlichung freigegeben. Der geplante iOS-GitHub-Tag -lautet `ios-v1.1.0`; Chrome, Codex-Plugin und gehosteter MCP bleiben auf `1.0.1`. -Der Xcode-Cloud-Workflow verwendet laut Maintainer Xcode 27.1 RC. Cloud-Build, -Distribution und App-Store-Verfügbarkeit werden anhand ihrer tatsächlichen -Ergebnisse getrennt geprüft. [Release-Hinweise](../docs/IOS_RELEASE_1.1.0.de.md). +## Deutsch: Veröffentlichung 1.1.0 + +Der Maintainer hat am 15. Oktober 2026 die App-Store-Veröffentlichung von +**1.1.0** bestätigt. App und Share Extension stehen im Quellcode auf +**1.1.0/Build 11**. Der iOS-spezifische Tagname lautet `ios-v1.1.0`; Chrome, +lokales Codex-Plugin und gehosteter MCP zielen jetzt auf `1.1.0` im separaten +Submission-Bundle unter `v1.1.0`. Deren öffentliche Freigabe folgt unabhängig. +Der Xcode-Cloud-Workflow verwendet laut Maintainer Xcode 27.1 RC. Für spätere +Uploads werden Cloud-Build, Distribution und App-Store-Verarbeitung anhand +ihrer tatsächlichen Ergebnisse geprüft. Die früheren Testnachweise bleiben +in den [Release-Hinweisen](../docs/IOS_RELEASE_1.1.0.de.md) erhalten. Die App startet mit **Neueste** und den neuesten sieben Beiträgen; Antippen öffnet den Beitrag direkt. Native Bereiche für **Suche** und **Chat** halten diff --git a/marketing/COMMUNITY_POSTS.md b/marketing/COMMUNITY_POSTS.md index 25b042d..b2c05bb 100644 --- a/marketing/COMMUNITY_POSTS.md +++ b/marketing/COMMUNITY_POSTS.md @@ -9,7 +9,7 @@ day or reuse an identical title and body. Suggested title: -> I built a GitHub-backed web-to-Markdown archive with no central content server +> I built a web-to-Markdown archive with user-controlled GitHub storage Draft: @@ -17,11 +17,13 @@ Draft: > papers, wikis, Gists, and PDFs as Markdown in a private GitHub repository the > user controls. > -> I want to be precise about the "self-hosted" angle: SourceBraid is not a server -> you deploy, and it depends on GitHub as the durable remote store. There is no -> central SourceBraid content service. The Markdown files, assets, metadata -> shards, and Git history live in the selected repository; the SQLite full-text -> index is a disposable local cache. +> I want to be precise about the "self-hosted" angle: capture and local Codex +> retrieval depend on GitHub as the durable remote store. The Markdown files, +> assets, metadata shards, and Git history live in the selected repository; the +> Codex SQLite full-text index is a disposable local cache. A separate read-only +> ChatGPT service is prepared for deployment on Cloudflare: requested sources +> would pass through its Worker, with OAuth connection records in Workers KV. +> It is not a self-hosted or wholly local ChatGPT workflow. > > Capture is available through the public clients listed here: {{PUBLIC_URL}}. > PDF conversion can run in the user's repository through GitHub Actions and @@ -47,7 +49,9 @@ Draft: > SourceBraid captures articles, research papers, wikis, Gists, and PDFs into a > private GitHub repository. It adds YAML frontmatter, keeps relevant images as > relative assets, records notes and tags, and uses Git history for inspectable -> changes. A ChatGPT/Codex plugin can search and fetch the resulting archive. +> changes. A local Codex plugin can search and fetch the resulting archive. A +> separate hosted, read-only ChatGPT integration is prepared for publication; +> its requested sources pass through Cloudflare before reaching ChatGPT. > > Demo: {{DEMO_URL}} > Source: {{REPOSITORY_URL}} @@ -69,12 +73,17 @@ Draft: > people who want their web research as portable Markdown. > > The capture clients save pages, papers, wikis, Gists, and PDFs into a private -> GitHub repository selected by the user. The project has no central content -> server. Markdown and Git history are authoritative, while the local SQLite -> search index can be deleted and rebuilt. The code is MIT-licensed, with a -> Manifest V3 Chrome extension, a native iOS app/Share Extension, and a -> dependency-free Python plugin for ChatGPT and Codex. Remove any client from -> this sentence that is not public for `{{VERSION}}`. +> GitHub repository selected by the user. Markdown and Git history are +> authoritative, while the local Codex SQLite search index can be deleted and +> rebuilt. The code is MIT-licensed, with a Manifest V3 Chrome extension, a +> native iOS app/Share Extension, and a standard-library Python plugin for local +> Codex runtimes. Remove any client from this sentence that is not public for +> `{{VERSION}}`. +> +> A separate read-only ChatGPT service is prepared for deployment and review. +> It processes requested sources through Cloudflare Workers and stores OAuth +> connection records in Workers KV. Local Codex indexing and confirmed archive +> deletion are not capabilities of that hosted service. > > Repository: {{REPOSITORY_URL}} > Release: {{RELEASE_URL}} diff --git a/marketing/DEMO_SCRIPT.md b/marketing/DEMO_SCRIPT.md index 151b42c..cce2f3d 100644 --- a/marketing/DEMO_SCRIPT.md +++ b/marketing/DEMO_SCRIPT.md @@ -52,16 +52,21 @@ Voiceover: ### 34-50 seconds: retrieve -Visual: In ChatGPT or Codex, use the public plugin path and ask: +Visual: In Codex, use the local plugin and ask: > Search my SourceBraid archive for the demo source and show its provenance. +For a ChatGPT version of the demo, use the separate hosted read-only connection +only after deployment, live verification, and public availability. Label the +client and connection shown; do not imply that the local plugin ZIP connects +ordinary ChatGPT. + Show the matching result and source path. Do not show a deletion operation in the primary demo; it distracts from the capture-to-retrieval story. Voiceover: -> The SourceBraid plugin searches the archive and returns the saved source with +> SourceBraid searches the archive and returns the saved source with > its repository path and provenance. ### 50-60 seconds: trust and action @@ -71,9 +76,15 @@ End on the product name and one public install URL. Voiceover: -> There is no central SourceBraid content server. Your archive lives in the -> GitHub repository you choose. SourceBraid is open source and available at -> sourcebraid.com. +> Your archive lives in the GitHub repository you choose, with Markdown and Git +> history as the source of truth. SourceBraid is open source. Find the available +> clients at sourcebraid.com. + +For the ChatGPT version, replace that voiceover with: + +> Your archive stays in your GitHub repository. This read-only ChatGPT connection +> processes requested sources through SourceBraid's Cloudflare service. Find +> the available clients at sourcebraid.com. On-screen text: diff --git a/marketing/GITHUB.md b/marketing/GITHUB.md index 33407cb..6d6f4bf 100644 --- a/marketing/GITHUB.md +++ b/marketing/GITHUB.md @@ -104,16 +104,17 @@ Suggested welcome text: SourceBraid saves web pages, papers, wikis, Gists, and PDFs as portable Markdown in a private GitHub repository controlled by the user. This coordinated release -includes the supported capture clients and ChatGPT/Codex archive tools listed -below. +includes the supported capture clients and archive integrations listed below. ## Install - Chrome: {{CHROME_STORE_URL}} - iPhone and iPad: {{APP_STORE_URL}} -- ChatGPT and Codex plugin: {{PLUGIN_URL}} +- Local Codex plugin: {{PLUGIN_URL}} Remove any installation line whose destination is not public for this release. +Add a separate ChatGPT install link only after the hosted read-only service is +deployed, verified live, and published. ## Highlights @@ -121,8 +122,12 @@ Remove any installation line whose destination is not public for this release. ## Privacy and permissions -SourceBraid has no central content server. Capture clients write to the GitHub -repository selected by the user. Review the permissions and data flow in the +Capture clients write directly to the GitHub repository selected by the user. +Local Codex tools use a disposable SQLite index and require a fresh preview and +explicit confirmation for deletion or metadata migration. The separate hosted +read-only ChatGPT service is prepared for deployment; it processes requested sources through +Cloudflare Workers and stores OAuth connection records in Workers KV. +Markdown and Git history remain authoritative. Review the data flow in the [privacy notice](https://github.com/patrickschiller/sourcebraid/blob/main/PRIVACY.md). ## Checks diff --git a/marketing/OPENAI_MCP_SUBMISSION.md b/marketing/OPENAI_MCP_SUBMISSION.md index a878293..580f425 100644 --- a/marketing/OPENAI_MCP_SUBMISSION.md +++ b/marketing/OPENAI_MCP_SUBMISSION.md @@ -1,22 +1,38 @@ -# OpenAI submission kit — SourceBraid read-only MCP 1.0.1 +# OpenAI submission kit — SourceBraid read-only MCP 1.1.0 This is the **primary ChatGPT release route**. Code and submission material are prepared; deployment, live acceptance, identity verification, submission and approval are separate outstanding gates. Do not enter an undeployed URL as a working service. Follow [the deployment runbook](../docs/CHATGPT_MCP_DEPLOYMENT.md). +Portal workflow checked against the [official submission guide](https://developers.openai.com/plugins/deploy/submission) +on **October 15, 2026**: start with a plugin ZIP that already declares the remote +MCP server. Adding MCP later to an existing skills-only plugin is not supported. + +## Upload package and deployment source + +- Upload **`sourcebraid-chatgpt-plugin-v1.1.0.zip`** from the verified release + bundle. It contains root `plugin.json`, root `mcp.json`, and the logo. The + manifest supplies listing metadata; `mcp.json` declares the stable HTTPS + endpoint and OAuth settings. Keep credentials outside this ZIP. +- Deploy the service from **`sourcebraid-chatgpt-mcp-v1.1.0.zip`** following the + runbook. This is Worker source code, not the plugin upload or a live endpoint. +- The optional **`sourcebraid-plugin-skills-v1.1.0.zip`** is a separate local + Python package. Do not upload it for this hosted read-only submission. + ## Copy-ready listing | Field | Value | | --- | --- | | Name | SourceBraid | | Developer | Patrick Schiller, only if identical to the selected verified identity | -| Submission | With MCP; Universal URL; remote MCP only | +| Submission | Upload plugin ZIP, then connect the declared remote server under MCPs / With MCP | +| Upload | `sourcebraid-chatgpt-plugin-v1.1.0.zip` | | Target MCP URL | `https://mcp.sourcebraid.com/mcp` — after deployment and live tests | | Authentication | OAuth 2.1 / PKCE S256; scope `archive:read` | | Category | Productivity | | Capability | Read | -| Short description | Search and read your private Markdown knowledge archive. | +| Short description | Search your Markdown archive. | | Website | `https://sourcebraid.com` | | Repository | `https://github.com/patrickschiller/sourcebraid` | | Privacy | `https://github.com/patrickschiller/sourcebraid/blob/main/PRIVACY.md` | @@ -39,7 +55,7 @@ Long description: German short description: -> Durchsuche und lies dein privates Markdown-Wissensarchiv. +> Durchsuche dein Markdownarchiv German long description: @@ -64,10 +80,11 @@ No token or GitHub identity ID is returned by a tool. The consent page has its own restrictive CSP; there is no embedded ChatGPT component to configure. This release does not support email-domain-restricted workspace enrollment. -Do not use the Codex search screenshot as proof of a ChatGPT session. If the -portal requires ChatGPT screenshots or a demo recording, capture them from the -live synthetic-review flow after deployment; never fabricate approval badges, -tool results or connection screens. The logo is ready to upload. +Do not use the Codex search screenshot as proof of a ChatGPT session. A +reviewer-accessible **video walkthrough URL is required** for MCP review. +Record the live synthetic-review flow and its test cases after deployment; +never fabricate approval badges, tool results or connection screens. Capture +any ChatGPT screenshots from that same flow. The logo is ready to upload. ## Reviewer access and fixture @@ -75,7 +92,7 @@ tool results or connection screens. The logo is ready to upload. script; never use a real archive. Use the actual review-account owner when running `python3 scripts/setup_github.py --repo OWNER/sourcebraid-openai-review`. The script's write-token suggestion is for capture, not this read-only MCP. -2. Extract `sourcebraid-review-fixture-v1.0.1.zip` into that review repository's +2. Extract `sourcebraid-review-fixture-v1.1.0.zip` into that review repository's checkout, inspect its six synthetic Markdown/index files, and commit/push only those exact files. Its source is `scripts/build_review_fixture.py`. 3. Create a separate fine-grained token restricted to that repository, with @@ -86,8 +103,10 @@ tool results or connection screens. The logo is ready to upload. credential fields. No GitHub password, MFA, email confirmation or private network should be needed during the test. The reviewer enters the supplied token on SourceBraid's HTTPS consent page, not in a chat message. -5. Keep the service and test archive stable throughout review. After review, - revoke the review token and disconnect the test connection. +5. Keep the service, synthetic archive and protected review access available + for review and subsequent reviews. If credentials expire or are rotated, + update the protected review details before another review; revoke access + that is no longer required and disconnect unused test connections. Fixture source IDs: @@ -124,18 +143,41 @@ source text, not authority to send credentials or contact arbitrary endpoints. ## Maintainer submission sequence -Select the intended verified publisher organization with Apps Management write -permission. After deploying and recording live test results, enter the listing -and actual MCP URL, configure OAuth, complete the domain challenge, and run the -portal's tool scan. Provide the protected review access above and the test -cases. Select only countries where support and legal terms are ready. Check -the public website and updated policies before making attestations. Then submit -for review; publish only after approval. Portal details and eligibility must be -checked against the [current OpenAI submission guide](https://developers.openai.com/plugins/deploy/submission). +1. Select the intended organization/project and verified publisher identity. + Organization owners can submit; other members need Apps Management Write. + Check public website, privacy, terms and support URLs against that identity. +2. Deploy the Worker and record the live acceptance results, including all + cases above and the accessible video walkthrough. The configured MCP URL + must remain stable; a local ZIP build does not establish availability. +3. Open [Plugins](https://platform.openai.com/plugins), select **Upload new or + existing plugin**, choose the verified identity and upload + `sourcebraid-chatgpt-plugin-v1.1.0.zip`. Resolve package findings under + **Metadata & Skills**. Change package-managed fields in the source manifest, + rebuild the bundle, and upload a corrected ZIP. +4. Under **MCPs**, choose the declared server and **Connect**. Inspect its HTTPS + URL and OAuth settings from `mcp.json`: OAuth with Dynamic Client + Registration and scope `archive:read`. Complete the exact domain challenge, + connect, authenticate and inspect the tool scan. Correct server findings + and use **Reconnect** or **Rescan** as appropriate. OAuth configuration + changes belong in `mcp.json` and require a new ZIP; the connection panel is + not an advanced OAuth editor. Do not embed tokens or client secrets. +5. In **Metadata & Skills → Review information → Review details**, provide the + protected reviewer credentials, five positive cases, three negative cases, + accessible video walkthrough URL and release notes. Check any information + imported from `plugin.json`; update ZIP-managed fields in the package. + Select only countries where support and legal terms are ready. +6. Resolve required validation/setup failures, complete the policy attestations + and **Submit for review**. After approval, select **Publish plugin** and + verify the live listing before announcing availability. + +Recheck the [official submission guide](https://developers.openai.com/plugins/deploy/submission) +if portal details or eligibility differ. The package follows the +[portable plugin format](https://developers.openai.com/plugins/build/plugins) +with root `plugin.json` and `mcp.json`. Release notes: -> Initial SourceBraid read-only remote MCP submission. Search, fetch, archive +> Initial SourceBraid 1.1.0 read-only remote MCP submission. Search, fetch, archive > listing and status for a user-selected private GitHub Markdown archive. > OAuth consent, scoped access and provenance-preserving results. No archive > writes or deletion. Review access uses a dedicated synthetic repository. @@ -151,9 +193,25 @@ Zuerst Cloudflare-Konto und Domain freigeben und den Dienst nach der [Deployment-Anleitung](../docs/CHATGPT_MCP_DEPLOYMENT.md) bereitstellen. Danach das private synthetische Review-Archiv und einen eigenen Read-only-Token einrichten, alle obigen Fälle in ChatGPT live prüfen und Ergebnisse ohne -Geheimnisse dokumentieren. Im OpenAI-Portal **With MCP / Universal** wählen, -verifizierte Identität verwenden, Domain verifizieren und Tools scannen. Den -Review-Token ausschließlich in geschützten Reviewer-Feldern hinterlegen. +Geheimnisse dokumentieren. Ein für Reviewer erreichbarer Video-Rundgang ist +Pflicht; ihn erst mit dem funktionierenden Dienst aufnehmen. + +Im am 15. Oktober 2026 geprüften Portalablauf zuerst **Upload new or existing +plugin** wählen und `sourcebraid-chatgpt-plugin-v1.1.0.zip` mit verifizierter +Identität hochladen. Das ZIP enthält `plugin.json`, `mcp.json` und Logo. Der +MCP-Server muss bereits in diesem ersten Upload stehen; ein Skills-only-Plugin +lässt sich derzeit nicht nachträglich um MCP erweitern. Das separate +`sourcebraid-chatgpt-mcp-v1.1.0.zip` dient der Serverbereitstellung. + +Unter **MCPs → Connect** Endpunkt und die aus `mcp.json` gelesenen OAuth-DCR- +Einstellungen mit `archive:read` prüfen, Domain-Challenge erfüllen, anmelden +und Tool-Scan abschließen. Metadaten- und OAuth-Änderungen im Quellpaket +vornehmen und neu hochladen. Unter **Review details** fünf positive und drei +negative Fälle, Video-URL und Release Notes prüfen; den Review-Token +ausschließlich geschützt hinterlegen. Review-Zugang für Folgeprüfungen +verfügbar halten und bei Rotation die geschützten Angaben aktualisieren. + Länder, Website, Datenschutz und Nutzungsbedingungen prüfen, dann einreichen. -Die Freigabe durch OpenAI und die anschließende Veröffentlichung stehen noch -aus; ein lokaler Build oder Skills-ZIP ersetzt diese Schritte nicht. +Erst nach OpenAI-Freigabe **Publish plugin** wählen und das öffentliche Listing +prüfen. Diese Schritte stehen noch aus; ein lokaler Build oder Skills-ZIP +ersetzt sie nicht. diff --git a/marketing/OPENAI_PLUGIN_SUBMISSION.md b/marketing/OPENAI_PLUGIN_SUBMISSION.md index 7147996..6d4b6a3 100644 --- a/marketing/OPENAI_PLUGIN_SUBMISSION.md +++ b/marketing/OPENAI_PLUGIN_SUBMISSION.md @@ -1,4 +1,4 @@ -# Optional local skills kit — SourceBraid 1.0.1 +# Optional local skills kit — SourceBraid 1.1.0 The primary ChatGPT submission is the hosted read-only **With MCP** integration documented in [`OPENAI_MCP_SUBMISSION.md`](OPENAI_MCP_SUBMISSION.md), with @@ -16,25 +16,34 @@ Use the current [OpenAI submission guide](https://developers.openai.com/plugins/ and [plugin packaging guide](https://developers.openai.com/plugins/build/plugins) if the portal differs from this kit. +As checked on **October 15, 2026**, the public portal starts with a plugin ZIP +upload. A remote MCP server must be declared in that first ZIP; an existing +skills-only plugin cannot currently be extended with MCP. The main ChatGPT +submission therefore uses `sourcebraid-chatgpt-plugin-v1.1.0.zip`, with its own +root `plugin.json` and `mcp.json`, rather than this optional local package. For +desktop execution, check the [desktop-only submission route](https://developers.openai.com/plugins/deploy/desktop-only) +and its eligibility before submitting. Do not infer eligibility from a local +installation or successful ZIP build. + ## Release route | Field | Submission value | | --- | --- | | Product | SourceBraid | -| Version | `1.0.1` | +| Version | `1.1.0` | | Distribution type | Optional local-runtime skills package | | Runtime | Python 3 and locally supplied GitHub authentication | -| Bundle | `dist/sourcebraid-plugin-skills-v1.0.1.zip` | +| Bundle | `dist/sourcebraid-plugin-skills-v1.1.0.zip` | | Local source | `codex-plugin/sourcebraid/` | | Primary ChatGPT submission | **With MCP**, using `OPENAI_MCP_SUBMISSION.md` | -| App UI submission | Not part of `1.0.1` | +| App UI submission | Not part of `1.1.0` | Rebuild the ZIP from the final public release commit before distribution: ```bash python3 scripts/build_plugin_package.py -unzip -l dist/sourcebraid-plugin-skills-v1.0.1.zip -shasum -a 256 dist/sourcebraid-plugin-skills-v1.0.1.zip +unzip -l dist/sourcebraid-plugin-skills-v1.1.0.zip +shasum -a 256 dist/sourcebraid-plugin-skills-v1.1.0.zip ``` Record the generated digest in the release's `SHA256SUMS` and @@ -77,7 +86,7 @@ provide local GitHub credentials. Main ChatGPT publication follows the MCP kit. | Developer name | Patrick Schiller | | Category | Productivity | | Capabilities | Read, Interactive, Write | -| Short description | Search your private Markdown knowledge archive. | +| Short description | Search your Markdown archive. | | Website | `https://sourcebraid.com` | | Repository | `https://github.com/patrickschiller/sourcebraid` | | Privacy policy | `https://github.com/patrickschiller/sourcebraid/blob/main/PRIVACY.md` | @@ -151,8 +160,8 @@ archive: ```bash python3 scripts/build_review_fixture.py \ - --output dist/sourcebraid-review-fixture-v1.0.1.zip -unzip -l dist/sourcebraid-review-fixture-v1.0.1.zip + --output dist/sourcebraid-review-fixture-v1.1.0.zip +unzip -l dist/sourcebraid-review-fixture-v1.1.0.zip ``` The paths are stable so the following local workflows remain reproducible. @@ -223,7 +232,7 @@ review run if the review should exercise the initial index build. Paste this into the protected reviewer-instructions field: -> This optional SourceBraid 1.0.1 skills package runs a standard-library Python +> This optional SourceBraid 1.1.0 skills package runs a standard-library Python > CLI against the dedicated private synthetic GitHub repository. Configure the > four non-secret SOURCEBRAID variables supplied with this submission and expose > the restricted review credential as GITHUB_TOKEN. Run status before the first @@ -390,8 +399,9 @@ availability merely because the portal permits it. package, and prepared hosted ChatGPT service. - [ ] Any separate optional listing explicitly names the required local runtime; the main ChatGPT submission follows **With MCP** and its own kit. -- [ ] Listing copy, logo, screenshots, links, and capabilities entered. -- [ ] Three starter prompts entered exactly. +- [ ] Listing copy, logo, screenshots, links, and capabilities included in the + manifest and checked after ZIP upload under Metadata & Skills. +- [ ] Three starter prompts included in the manifest and checked after upload. - [ ] Dedicated repository confirmed private and seeded only with the synthetic fixture. - [ ] Fine-grained review token restricted to that repository and placed only in @@ -419,6 +429,15 @@ Die primäre ChatGPT-Einreichung erfolgt als **With MCP** nach Dienst `https://mcp.sourcebraid.com/mcp` ist noch nicht bereitgestellt. Er bietet ausschließlich Lesezugriff. Seine Review-Zugangsdaten müssen Contents-Leserechte für genau ein synthetisches privates Repository haben und dürfen nur geschützt -übermittelt werden. Die folgenden lokalen Schreibtests sind keine Fähigkeiten +übermittelt werden. Die oben aufgeführten lokalen Schreibtests sind keine Fähigkeiten des gehosteten Dienstes. Maßgeblich sind die frisch erzeugten Prüfsummen des finalen Release-Bundles, keine fest eingetragenen ZIP-Hashes. + +Nach dem am 15. Oktober 2026 geprüften Portalablauf beginnt die Einreichung mit +einem Plugin-ZIP. Für den gehosteten Dienst ist das separate +`sourcebraid-chatgpt-plugin-v1.1.0.zip` mit `plugin.json` und `mcp.json` vorgesehen. +Ein MCP-Server kann derzeit nicht nachträglich zu einem Skills-only-Plugin +hinzugefügt werden. Für dieses lokale Paket gelten die gesonderten +[Desktop-Vorgaben](https://developers.openai.com/plugins/deploy/desktop-only); +ein erfolgreicher lokaler Build belegt keine Freigabe für den öffentlichen +Katalog. diff --git a/marketing/PRODUCT_HUNT.md b/marketing/PRODUCT_HUNT.md index 5ba31c7..26f63c3 100644 --- a/marketing/PRODUCT_HUNT.md +++ b/marketing/PRODUCT_HUNT.md @@ -2,7 +2,8 @@ Publish only when the product is immediately installable through at least one public destination. Remove unavailable platform links rather than promising -them in the listing. +them in the listing. Include the prepared hosted ChatGPT integration only after +deployment, live verification, and publication. ## Main listing @@ -17,7 +18,7 @@ Tagline: Description: > Save web pages, papers, wikis, Gists, and PDFs as durable Markdown in a private -> GitHub repository you control, then search the archive from ChatGPT and Codex. +> GitHub repository you control, then search the archive with local Codex tools. Primary URL: @@ -28,7 +29,7 @@ Additional links: - Repository: {{REPOSITORY_URL}} - Chrome: {{CHROME_STORE_URL}} - iPhone and iPad: {{APP_STORE_URL}} -- ChatGPT and Codex: {{PLUGIN_URL}} +- Local Codex plugin: {{PLUGIN_URL}} Choose no more than three current Product Hunt categories that reflect the shipped product. Prefer the closest equivalents of productivity, knowledge @@ -39,8 +40,9 @@ management, and open source; verify the live taxonomy during submission. 1. 45-60 second capture-to-search demo; 2. Chrome capture with tags and notes; 3. readable Markdown and provenance in the private synthetic repository; -4. ChatGPT or Codex search result; -5. simple data-flow graphic explaining that there is no central content server. +4. local Codex search result, or a verified live ChatGPT result after publication; +5. data-flow graphic showing direct capture to GitHub and, if shown, the hosted + ChatGPT service's processing of requested sources on Cloudflare. ## Maker comment @@ -50,14 +52,17 @@ management, and open source; verify the live taxonomy during submission. > > SourceBraid captures web pages, papers, wikis, Gists, and PDFs as Markdown in > a private GitHub repository selected by the user. It preserves provenance, -> stores relevant assets next to the document, and uses normal Git commits. A -> ChatGPT/Codex plugin adds full-text search, fetching, and guarded archive -> management without making a SourceBraid database the source of truth. +> stores relevant assets next to the document, and uses normal Git commits. The +> local Codex plugin adds full-text search, fetching, and archive management +> with a fresh preview and explicit confirmation for deletion or metadata +> migration. The separate hosted ChatGPT integration is prepared for read-only +> search and retrieval. > -> The privacy boundary shaped the architecture: SourceBraid has no central -> content server. The capture clients communicate with source websites and the -> GitHub repository the user configured. The Markdown files and Git history are -> authoritative; the local SQLite search index is disposable and rebuildable. +> Capture clients write directly to the GitHub repository the user configured. +> The prepared ChatGPT service processes requested sources through a Cloudflare +> Worker and stores OAuth connection records separately in Workers KV; it does +> not maintain an archive-content database. The Markdown files and Git history +> are authoritative; the local Codex SQLite index is disposable and rebuildable. > > The project is MIT-licensed. I would especially value feedback on first-run > GitHub setup, the permission explanation, and which research workflows deserve diff --git a/marketing/PUBLISHING_CHECKLIST.de.md b/marketing/PUBLISHING_CHECKLIST.de.md new file mode 100644 index 0000000..2a19824 --- /dev/null +++ b/marketing/PUBLISHING_CHECKLIST.de.md @@ -0,0 +1,186 @@ +# SourceBraid 1.0.1 – Veröffentlichungsplan + +[English](PUBLISHING_CHECKLIST.md). Prüfung: 28. September 2026, öffentlicher +Commit `bb7c77ab18b3908d324a4a8e43056d574e60fbde`. +Diese Arbeitsliste ergänzt [RELEASING.md](../RELEASING.md). +Häkchen dokumentieren tatsächlich ausgeführte Prüfungen; offene Punkte sind +keine Bestätigung, dass Konten oder Store-Einträge noch nicht existieren. + +Empfohlene Reihenfolge: lokal installieren und testen, Chrome einreichen, +während dessen Prüfung den ChatGPT-Dienst bereitstellen und abnehmen. +Jeden Kanal nach seiner Freigabe veröffentlichen; die gemeinsame Ankündigung +erst starten, wenn alle darin genannten Installationslinks funktionieren. +iOS benötigt für diesen Plan keine neue Veröffentlichung. + +## Geprüfter Stand + +- [x] `git pull --ff-only origin main`: bereits aktuell, kein neuer Commit. +- [x] Letzten Commit mit seinem Vorgänger verglichen: 60 Dateien geändert. + Chrome steigt von 0.7.1 auf 1.0.1, ergänzt DE/EN-Metadaten, ausdrückliche + Datenzustimmung und tokenfreien Plugin-Konfigurationsexport. `downloads` + entfällt; Hostrechte sind ausdrücklich HTTP, HTTPS und lokale Dateien. + Archivpfade und lokaler Index werden stärker abgegrenzt. Neu sind der + lesende Cloudflare-MCP-Dienst, Marketplace-Eintrag und Release-/Review-Kits. +- [x] 23 Capture-, 63 Python- und 33 Remote-MCP-Tests bestanden. +- [x] Worker-Build als Dry Run bestanden; vorhandene npm-Abhängigkeiten benutzt. +- [x] Release-Bundle reproduzierbar gebaut, Inhalte geprüft, 24 Prüfsummen gültig. + `RELEASE_MANIFEST.json` nennt obigen Commit und unveränderte Release-Eingaben. +- [x] [GitHub Release checks](https://github.com/patrickschiller/sourcebraid/actions/runs/36350019080) + für diesen Commit erfolgreich. +- [x] Chrome-Bilder vorhanden: zwei Screenshots 1280×800, Promo 440×280, + Icon 128×128. Texte und Reviewer-Schritte liegen vor. +- [x] Lokale CLI und Plugin-Suche geprüft: SourceBraid wird derzeit nicht angeboten; + der Repository-Marketplace muss für die lokale Installation hinzugefügt werden. +- [x] Homepage sowie öffentliche Privacy-/Terms-Links antworten mit HTTP 200. +- [ ] **HTTPS-Blocker beheben:** `https://mcp.sourcebraid.com/health` scheitert + derzeit an einem Zertifikat, das nicht zum Hostnamen passt. Mit Python und + curl bestätigt. Kein erfolgreicher Health-/OAuth-Test; TLS-Prüfung beibehalten. +- [ ] **Außendarstellung korrigieren:** `PRODUCT_HUNT.md`, `COMMUNITY_POSTS.md`, + `DEMO_SCRIPT.md` und `GITHUB.md` behaupten teilweise noch, es gebe keinen + zentralen Inhaltsdienst. Gehostete ChatGPT-Anfragen durchlaufen jedoch + SourceBraid/Cloudflare. Lokale Volltextsuche und Löschfunktionen nicht als + Funktionen der gehosteten ChatGPT-Verbindung bewerben. +- [ ] Finalen Release-Commit/Tag und GitHub-Release veröffentlichen. + Die Abfragen lieferten aktuell weder Tags noch Releases. + +Die vorhandene lokale `.gitignore`-Änderung blieb erhalten. Der Builder prüft +saubere **Release-Eingaben**, nicht den gesamten Arbeitsbaum. Vor einer finalen +Veröffentlichung einen sauberen öffentlichen Checkout nach `RELEASING.md` nutzen. +Kein iOS-Build, echter Chrome-Durchlauf oder Live-ChatGPT-Test wurde hier ausgeführt. +Automatische Paketprüfungen ersetzen keine Sichtprüfung der Screenshots. + +## 1. Auf diesem Rechner starten – gemeinsam + +- [ ] Lokales Codex-Plugin aus dem geprüften Checkout installieren: + + ```bash + codex plugin marketplace add . + codex plugin add sourcebraid@sourcebraid + ``` + + Beide Befehle sind in der installierten CLI verfügbar und werden im + Repository-Hauptverzeichnis ausgeführt. Danach einen neuen Chat öffnen; + bei Bedarf die Desktop-App neu starten. Ein Repo-Marketplace verteilt das + Plugin lokal und ist getrennt vom öffentlichen Verzeichnis. + [OpenAI-Paketdokumentation](https://developers.openai.com/plugins/build/plugins). + +- [ ] Ein dediziertes privates Testarchiv auswählen, das geprüfte + `sourcebraid-review-fixture-v1.0.1.zip` dort entpacken und ausschließlich seine + sechs synthetischen Dateien committen/pushen. Damit ist „portable weave“ als + Suchbegriff reproduzierbar. `OWNER` und `TEST_REPO` im folgenden Befehl ersetzen; + Zugang über bestehendes + `gh auth login` oder die dokumentierten Token-Umgebungsvariablen einrichten: + + ```bash + python3 codex-plugin/sourcebraid/scripts/sourcebraid.py config \ + --repo-slug OWNER/TEST_REPO --branch main --root-folder web-clips + python3 codex-plugin/sourcebraid/scripts/sourcebraid.py index build + python3 codex-plugin/sourcebraid/scripts/sourcebraid.py search "portable weave" + ``` + +- [ ] In einem neuen Chat Suche und Abruf einer synthetischen Quelle testen. + Das vollständige lokale Plugin verwendet Python, lokalen Index und GitHub-Zugang. + Anleitung: [ChatGPT und Codex](../docs/CHATGPT_PLUGIN.md). +- [ ] In Chrome `chrome://extensions` öffnen, Entwicklermodus aktivieren, + „Entpackte Erweiterung laden“ und `chrome-extension/sourcebraid` auswählen. +- [ ] Im Testarchiv Einwilligung, HTML-Aufnahme mit Bild, Tags/Notizen, + Markdown-Fallback und tokenfreien Konfigurationsexport prüfen. +- [ ] Öffentliche und lokale synthetische PDF testen, für lokale Dateien den + Datei-URL-Zugriff aktivieren; GitHub-Actions-Konvertierung bis zum fertigen + Markdown und zu den Bildern prüfen. Datum und Ergebnis protokollieren. + +## 2. Google Chrome Web Store + +- [ ] **Patrick:** Publisherkonto festlegen, Registrierung und einmalige Gebühr + erledigen, Kontaktmail bestätigen und 2FA aktivieren. + [Registrierung](https://developer.chrome.com/docs/webstore/register), + [Kontoeinrichtung](https://developer.chrome.com/docs/webstore/set-up-account), + [2FA](https://developer.chrome.com/docs/webstore/program-policies/two-step-verification). +- [ ] **Patrick:** Trader-/Non-Trader-Angabe anhand der tatsächlichen Situation + ausfüllen; erforderliche Identitäts-/Kontaktnachweise abschließen. + [Google-Anforderungen](https://developer.chrome.com/docs/webstore/program-policies/trader-verification-faq). +- [ ] **Gemeinsam:** Eigenes privates Chrome-Review-Archiv vorbereiten. Sein PAT + benötigt `Contents: Read and write`; Workflow-Schreibrechte nur, wenn damit + die PDF-Unterstützung installiert wird. Zugang ausschließlich im vorgesehenen + Reviewer-Feld hinterlegen. [Test instructions](https://developer.chrome.com/docs/webstore/cws-dashboard-test-instructions). +- [ ] **Codex + Patrick:** Das geprüfte Chrome-ZIP im + [Developer Dashboard](https://chrome.google.com/webstore/devconsole) hochladen. + DE/EN-Texte aus [STORE_LISTING.md](../chrome-extension/STORE_LISTING.md), + vorhandene Bilder und öffentliche Support-/Privacy-Links übernehmen. + [Bildvorgaben](https://developer.chrome.com/docs/webstore/images). +- [ ] **Gemeinsam:** Single-purpose, Datenkategorien, Remote-Code-Antwort und + Begründung jeder Berechtigung am realen Verhalten prüfen. HTTP-/HTTPS-Zugriff + ist weiterhin breit; lokale Dateien sind für lokale PDFs vorgesehen. + [Privacy-Felder](https://developer.chrome.com/docs/webstore/cws-dashboard-privacy). +- [ ] **Patrick:** Länder und öffentliche Sichtbarkeit festlegen, Einreichung + abschließen. Bei verzögerter Veröffentlichung innerhalb von 30 Tagen nach + Freigabe publizieren. [Veröffentlichung](https://developer.chrome.com/docs/webstore/publish). +- [ ] **Gemeinsam:** Nach Freigabe aus dem Store frisch installieren und + Aufnahme/PDF nochmals abnehmen. Live-Store-URL dokumentieren. + +Google nennt wenige Tage bis mehrere Wochen für die Prüfung; breite Hostrechte +können sie verlängern. Keinen festen Launchtermin vor Freigabe versprechen. +[Review-Verfahren](https://developer.chrome.com/docs/webstore/review-process). + +## 3. ChatGPT und öffentliches OpenAI-Plugin-Verzeichnis + +Der vorhandene Primärpfad ist **With MCP / Universal**: ein gehosteter Dienst +mit `search`, `fetch`, `sourcebraid_list` und `sourcebraid_status`, ausschließlich +lesend. Das optionale Skills-ZIP allein verbindet ChatGPT nicht mit dem Archiv. + +- [ ] **Patrick:** OpenAI-Publisheridentität verifizieren und + `Apps Management: Write` prüfen. [Einreichung](https://developers.openai.com/plugins/deploy/submission). +- [ ] **Patrick:** Cloudflare-Konto, Domainkontrolle und Kostenrahmen festlegen. +- [ ] **Codex + Patrick:** Nach [Deployment-Runbook](../docs/CHATGPT_MCP_DEPLOYMENT.md) + Worker, KV und Custom Domain bereitstellen; das falsche TLS-Zertifikat beheben. + Kein GitHub-PAT gehört in globale Worker-Variablen. +- [ ] **Gemeinsam:** Öffentliches HTTPS, `/health`, OAuth-Metadaten und erwartetes + HTTP 401 ohne Anmeldung prüfen. Health allein genügt nicht. +- [ ] **Gemeinsam:** Separates privates OpenAI-Review-Archiv aus dem Fixture-ZIP + befüllen. Eigenen PAT mit ausschließlich `Contents: Read-only` für dieses + Repository erstellen. Nie den Chrome-Schreibtoken verwenden oder Tokens in + Chats, Screenshots und öffentliche Dateien eintragen. +- [ ] **Gemeinsam:** ChatGPT-Entwicklermodus und MCP-Verbindung einrichten; + Verfügbarkeit hängt vom Konto/Workspace ab. + [Offizieller Testablauf](https://developers.openai.com/plugins/deploy/connect-chatgpt). +- [ ] **Gemeinsam:** Alle fünf positiven und drei negativen Fälle im + [MCP-Einreichungskit](OPENAI_MCP_SUBMISSION.md) live bestehen: Status, + Liste, Suche, Abruf, Zusammenfassung sowie verweigertes Löschen, + fremde/unerlaubte Pfade und widerrufener Token. Zusätzlich Refresh, + Wiederverbinden und Trennung zweier Nutzerarchive prüfen. Bösartige + Anweisungen in einer synthetischen Quelle müssen als Quelldaten behandelt + werden und dürfen weder Tokenweitergabe noch fremde Netzwerkaufrufe auslösen. +- [ ] **Codex:** Ergebnisse mit Datum und Worker-/Commitversion festhalten; + bei Bedarf echte ChatGPT-Demo mit synthetischen Quellen aufnehmen. +- [ ] **Patrick + Codex:** Im OpenAI-Portal Domainnachweis, Tool-Scan, + Listing und geschützten Reviewzugang vervollständigen; Länder, Erklärungen + und Release Notes prüfen, zur Prüfung einreichen und nach Freigabe publizieren. + [Einreichungsablauf](https://developers.openai.com/plugins/deploy/submission). +- [ ] **Gemeinsam:** Installation aus dem öffentlichen Verzeichnis abnehmen, + Live-Link erfassen und zeitlich begrenzte Review-Zugänge anschließend widerrufen. + +## 4. Pakete und gemeinsamer Abschluss + +Die lokal geprüften Dateien liegen unter `dist/sourcebraid-release-v1.0.1/`: + +| Datei | Verwendung | +| --- | --- | +| `sourcebraid-chrome-v1.0.1.zip` | Google-Upload | +| `sourcebraid-chatgpt-mcp-v1.0.1.zip` | Quellpaket für Worker-Deployment | +| `sourcebraid-review-fixture-v1.0.1.zip` | Drei synthetische Quellen plus Index-Shards | +| `sourcebraid-plugin-skills-v1.0.1.zip` | Optionales lokales Skills-Paket; kein gehosteter MCP | +| `RELEASE_MANIFEST.json`, `SHA256SUMS` | Commitbezug und Integrität | + +- [ ] Finale Korrekturen in DE/EN abgleichen und mit DCO-Sign-off committen. + Anschließend relevante Tests und Pakete vom finalen sauberen Commit erneut + prüfen. Vorhandene lokale Änderungen nicht ungeprüft in den Release übernehmen. +- [ ] Geprüften Tag `v1.0.1` und GitHub-Release mit passenden Artefakten publizieren; + erst danach die taggebundene Plugin-Installation bewerben. +- [ ] Nur tatsächlich freigegebene Store-/Plugin-Links und Aussagen in den + Launchentwürfen verwenden; private Website-Quellen und reale Archive ausschließen. +- [ ] Reale Erstinstallation je veröffentlichtem Kanal bestehen und danach die + [Launch-Checkliste](LAUNCH_CHECKLIST.md) für die öffentliche Ankündigung nutzen. + +Nächster gemeinsamer Arbeitsschritt: lokales Plugin und Chrome einrichten und +einen synthetischen Capture-to-Search-Durchlauf abschließen. Kontoprüfungen bei +Google, OpenAI und Cloudflare können parallel vorbereitet werden. diff --git a/marketing/PUBLISHING_CHECKLIST.md b/marketing/PUBLISHING_CHECKLIST.md new file mode 100644 index 0000000..17dfa65 --- /dev/null +++ b/marketing/PUBLISHING_CHECKLIST.md @@ -0,0 +1,180 @@ +# SourceBraid 1.0.1 publishing plan + +[Deutsch](PUBLISHING_CHECKLIST.de.md). Audited September 28, 2026 at public +commit `bb7c77ab18b3908d324a4a8e43056d574e60fbde`. +This operational checklist supplements [RELEASING.md](../RELEASING.md). +Checked boxes represent completed checks; unchecked account/store items mean +unverified, not proof that those accounts or listings do not exist. + +Recommended sequence: install and test locally, submit Chrome, then deploy +and validate the ChatGPT service while Chrome is under review. Publish each +channel after approval; announce the combined launch once every advertised +installation link works. This plan does not require a new iOS release. + +## Verified baseline + +- [x] `git pull --ff-only origin main`: already current; no new commits. +- [x] Reviewed the latest commit against its parent: 60 changed files. Chrome + moves from 0.7.1 to 1.0.1, adds DE/EN metadata, explicit data consent and + token-free plugin configuration export. `downloads` permission is removed; + host access explicitly covers HTTP, HTTPS and local files. Archive paths and + local index boundaries are hardened. Read-only Cloudflare MCP, a repository + marketplace and release/review kits are new. +- [x] 23 capture, 63 Python and 33 remote-MCP tests passed. +- [x] Worker dry-run build passed using dependencies already installed locally. +- [x] Reproducible release bundle passed content checks and 24 checksums. + `RELEASE_MANIFEST.json` records the commit above and unchanged release inputs. +- [x] [GitHub Release checks](https://github.com/patrickschiller/sourcebraid/actions/runs/36350019080) + passed for this commit. +- [x] Chrome listing/reviewer copy and required image types exist: two + 1280×800 screenshots, a 440×280 promo image and a 128×128 icon. +- [x] Local CLI/catalog inspection found no SourceBraid entry; add the repository + marketplace before installing the local plugin. +- [x] Homepage and public privacy/terms links returned HTTP 200. +- [ ] **Fix HTTPS blocker:** `https://mcp.sourcebraid.com/health` currently fails + certificate hostname validation, confirmed using Python and curl. No live + health/OAuth success is established. Keep TLS verification enabled. +- [ ] **Correct launch claims:** `PRODUCT_HUNT.md`, `COMMUNITY_POSTS.md`, + `DEMO_SCRIPT.md` and `GITHUB.md` still contain claims about no central content + service. Hosted ChatGPT requests pass through SourceBraid/Cloudflare. Do not + attribute local full-text indexing or deletion to hosted ChatGPT. +- [ ] Publish the final release commit/tag and GitHub release. Remote queries + currently returned no tags or releases. + +The existing local `.gitignore` change was preserved. The builder's clean check +covers **release inputs**, not the entire worktree. Final publication requires +a clean public checkout under `RELEASING.md`. No iOS build, real Chrome workflow +or live ChatGPT test was performed here. Automated packaging checks do not +replace visual inspection of screenshots. + +## 1. Set up this computer — together + +- [ ] Install the complete local Codex plugin from the reviewed repository root: + + ```bash + codex plugin marketplace add . + codex plugin add sourcebraid@sourcebraid + ``` + + Both commands are supported by the installed CLI. Start a new chat afterward; + restart the desktop app if necessary. Repository marketplaces are separate + from the public directory. + [OpenAI packaging documentation](https://developers.openai.com/plugins/build/plugins). + +- [ ] Select a dedicated private test archive, extract the checked + `sourcebraid-review-fixture-v1.0.1.zip` there and commit/push only its six + synthetic files. This makes the “portable weave” search reproducible. + Replace `OWNER` and `TEST_REPO` below; authenticate using `gh auth login` or the documented token + environment variables: + + ```bash + python3 codex-plugin/sourcebraid/scripts/sourcebraid.py config \ + --repo-slug OWNER/TEST_REPO --branch main --root-folder web-clips + python3 codex-plugin/sourcebraid/scripts/sourcebraid.py index build + python3 codex-plugin/sourcebraid/scripts/sourcebraid.py search "portable weave" + ``` + +- [ ] Test search and fetch in a new chat. The complete local plugin needs + Python, its local index and GitHub authentication. + [Installation guide](../docs/CHATGPT_PLUGIN.md). +- [ ] Open `chrome://extensions`, enable Developer mode and load + `chrome-extension/sourcebraid` using “Load unpacked.” +- [ ] Test consent, HTML capture with images, notes/tags, Markdown fallback and + token-free configuration export against the synthetic archive. +- [ ] Test synthetic public and local PDFs, enable file-URL access for local + files, and verify GitHub Actions conversion through final Markdown/images. + Record the date and results. + +## 2. Google Chrome Web Store + +- [ ] **Patrick:** Select the publisher account, complete registration/payment, + confirm contact email and enable 2FA. + [Registration](https://developer.chrome.com/docs/webstore/register), + [account setup](https://developer.chrome.com/docs/webstore/set-up-account), + [2FA](https://developer.chrome.com/docs/webstore/program-policies/two-step-verification). +- [ ] **Patrick:** Complete trader/non-trader declarations and any identity or + contact verification based on the actual situation. + [Google requirements](https://developer.chrome.com/docs/webstore/program-policies/trader-verification-faq). +- [ ] **Together:** Prepare a dedicated private Chrome review repository. Its + PAT needs Contents read/write; workflow write access only when installing + PDF support. Supply access solely in the designated reviewer field. + [Test instructions](https://developer.chrome.com/docs/webstore/cws-dashboard-test-instructions). +- [ ] **Codex + Patrick:** Upload the checked Chrome ZIP in the + [developer dashboard](https://chrome.google.com/webstore/devconsole); use + [DE/EN listing copy](../chrome-extension/STORE_LISTING.md), the prepared + images and public support/privacy links. + [Image requirements](https://developer.chrome.com/docs/webstore/images). +- [ ] **Together:** Check single-purpose, data categories, remote-code answer + and each permission justification against actual behavior. HTTP/HTTPS access + remains broad; file access supports local PDFs. + [Privacy fields](https://developer.chrome.com/docs/webstore/cws-dashboard-privacy). +- [ ] **Patrick:** Select countries and public visibility, then submit. If + publication is deferred, publish within 30 days of approval. + [Publishing](https://developer.chrome.com/docs/webstore/publish). +- [ ] **Together:** After approval, verify a fresh store installation and the + capture/PDF workflow; record the live listing URL. + +Google reviews may take days to weeks; broad host permissions can extend review. +Avoid promising a launch date before approval. +[Review process](https://developer.chrome.com/docs/webstore/review-process). + +## 3. ChatGPT and the public OpenAI Plugins Directory + +The prepared primary route is **With MCP / Universal**: hosted read-only +`search`, `fetch`, `sourcebraid_list` and `sourcebraid_status`. The optional +skills ZIP alone does not connect ChatGPT to an archive. + +- [ ] **Patrick:** Verify publisher identity and `Apps Management: Write`. + [Submission](https://developers.openai.com/plugins/deploy/submission). +- [ ] **Patrick:** Select the Cloudflare account, domain and spending limits. +- [ ] **Codex + Patrick:** Follow the [deployment runbook](../docs/CHATGPT_MCP_DEPLOYMENT.md) + for Worker, KV and custom domain; fix the TLS certificate. Do not put a user's + GitHub PAT in global Worker variables. +- [ ] **Together:** Verify public HTTPS, health, OAuth metadata and the expected + unauthenticated 401 response. Health alone does not establish archive access. +- [ ] **Together:** Populate a separate private OpenAI review archive from the + fixture ZIP. Create a PAT with Contents read-only for that repository. Never + reuse Chrome's write token or place tokens in chats, screenshots or public files. +- [ ] **Together:** Enable ChatGPT developer mode and connect MCP; availability + depends on account/workspace policy. + [Official testing flow](https://developers.openai.com/plugins/deploy/connect-chatgpt). +- [ ] **Together:** Pass all five positive and three negative live cases in the + [MCP submission kit](OPENAI_MCP_SUBMISSION.md): status, list, search, fetch, + summary; denied deletion, foreign/outside-root paths and revoked credentials. + Also verify refresh, reconnect and isolation between two users' archives. + Malicious instructions embedded in a synthetic source must remain source + data and must not trigger credential disclosure or unrelated network calls. +- [ ] **Codex:** Record date, Worker/commit version and outcomes; capture any + required ChatGPT demo from the real synthetic workflow. +- [ ] **Patrick + Codex:** Complete domain verification, tool scan, listing, + protected reviewer access, countries, attestations and release notes in the + portal. Submit for review and publish after approval. + [Submission flow](https://developers.openai.com/plugins/deploy/submission). +- [ ] **Together:** Verify installation from the public directory, record the + live URL and revoke temporary review access after review. + +## 4. Artifacts and final launch + +Checked local artifacts are in `dist/sourcebraid-release-v1.0.1/`: + +| File | Use | +| --- | --- | +| `sourcebraid-chrome-v1.0.1.zip` | Google upload | +| `sourcebraid-chatgpt-mcp-v1.0.1.zip` | Worker source for deployment | +| `sourcebraid-review-fixture-v1.0.1.zip` | Three synthetic sources plus index shards | +| `sourcebraid-plugin-skills-v1.0.1.zip` | Optional local skills; no hosted MCP | +| `RELEASE_MANIFEST.json`, `SHA256SUMS` | Commit provenance and integrity | + +- [ ] Align final DE/EN changes and commit with DCO sign-off. Recheck relevant + tests/artifacts from the final clean commit. Keep unrelated local work out of + the release. +- [ ] Publish reviewed tag `v1.0.1` and the GitHub release with matching artifacts; + only then advertise tag-bound plugin installation. +- [ ] Replace draft links/claims with verified destinations and behavior. Keep + private website sources and real archives out of the public release. +- [ ] Pass fresh installations for each released channel, then use the + [launch checklist](LAUNCH_CHECKLIST.md) for public announcements. + +Next joint step: install the local plugin and Chrome extension and complete a +synthetic capture-to-search workflow. Account checks for Google, OpenAI and +Cloudflare can proceed in parallel. diff --git a/marketing/README.md b/marketing/README.md index 67b5a16..98dd46f 100644 --- a/marketing/README.md +++ b/marketing/README.md @@ -58,6 +58,9 @@ rg -n '\{\{[A-Z0-9_]+\}\}' marketing ## Files +- Publishing checklist / Veröffentlichungscheckliste: + [English](PUBLISHING_CHECKLIST.md) · [Deutsch](PUBLISHING_CHECKLIST.de.md), + with the dated 1.0.1 audit, local setup, owners, and outstanding store gates; - [`GITHUB.md`](GITHUB.md): repository metadata, topics, and community setup; - [`LAUNCH_CHECKLIST.md`](LAUNCH_CHECKLIST.md): release gates and launch order; - [`OPENAI_MCP_SUBMISSION.md`](OPENAI_MCP_SUBMISSION.md): primary ChatGPT diff --git a/scripts/build_chatgpt_plugin_package.py b/scripts/build_chatgpt_plugin_package.py new file mode 100644 index 0000000..8b00950 --- /dev/null +++ b/scripts/build_chatgpt_plugin_package.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Build the remote-MCP plugin ZIP uploaded to the OpenAI submission portal.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import sys +import zipfile +from pathlib import Path, PurePosixPath +from urllib.parse import urlsplit + +from build_mcp_package import MCPPackageError, package_source, validated_version + + +PLUGIN_FILES = ( + ("chatgpt-plugin/sourcebraid/plugin.json", "plugin.json"), + ("chatgpt-plugin/sourcebraid/mcp.json", "mcp.json"), + ("codex-plugin/sourcebraid/assets/sourcebraid-icon.png", "assets/sourcebraid-icon.png"), +) + + +class ChatGPTPluginPackageError(RuntimeError): + """The hosted plugin package could not be prepared safely.""" + + +def validate_metadata(manifest: dict, servers: dict, version: str) -> None: + """Check SourceBraid's submission contract, not the entire external schema.""" + try: + if manifest.get("name") != "sourcebraid" or manifest.get("version") != version: + raise ChatGPTPluginPackageError("hosted plugin version must match the MCP service") + if manifest.get("$schema") != "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json": + raise ChatGPTPluginPackageError("hosted plugin must use the portable plugin schema") + interface = manifest["extensions"]["com.openai"]["interface"] + for key, limit in (("displayName", 30), ("shortDescription", 30), ("longDescription", 4000), ("developerName", 80)): + value = interface[key] + if not isinstance(value, str) or not value.strip() or len(value) > limit: + raise ChatGPTPluginPackageError(f"invalid hosted plugin listing field: {key}") + for key in ("websiteURL", "supportURL", "privacyPolicyURL", "termsOfServiceURL"): + value = interface[key] + url = urlsplit(value) + if url.scheme != "https" or not url.hostname or url.username or url.password or len(value) > 1024: + raise ChatGPTPluginPackageError(f"invalid hosted plugin URL: {key}") + if interface["capabilities"] != ["Read"]: + raise ChatGPTPluginPackageError("hosted plugin must advertise read-only access") + for key in ("logo", "composerIcon"): + if interface[key] != "./assets/sourcebraid-icon.png": + raise ChatGPTPluginPackageError("hosted plugin icon must be included in the allowlist") + expected_servers = { + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "sourcebraid": { + "type": "streamable-http", + "url": "https://mcp.sourcebraid.com/mcp", + "extensions": {"com.openai": {"auth": { + "type": "oauth", "client": {"mode": "dcr"}, + "baseScopes": ["archive:read"], + }}}, + }, + }, + } + if servers != expected_servers: + raise ChatGPTPluginPackageError("hosted MCP must use the production URL and credential-free read-only OAuth DCR") + except (KeyError, TypeError, ValueError, AttributeError) as error: + raise ChatGPTPluginPackageError("invalid hosted plugin metadata") from error + + +def build_package(root: Path, output: Path) -> tuple[str, str]: + version = validated_version(root) + entries = { + name: package_source(root, PurePosixPath(source)).read_bytes() + for source, name in PLUGIN_FILES + } + try: + validate_metadata(json.loads(entries["plugin.json"]), json.loads(entries["mcp.json"]), version) + except (ValueError, TypeError) as error: + raise ChatGPTPluginPackageError("could not read hosted plugin metadata") from error + output.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive: + for name, content in sorted(entries.items()): + info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) + info.compress_type = zipfile.ZIP_DEFLATED + info.external_attr = 0o100644 << 16 + archive.writestr(info, content) + return version, hashlib.sha256(output.read_bytes()).hexdigest() + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repository-root", type=Path, default=Path(__file__).resolve().parents[1]) + parser.add_argument("--output", type=Path) + args = parser.parse_args(argv) + root = args.repository_root.resolve() + try: + version = validated_version(root) + output = args.output or root / "dist" / f"sourcebraid-chatgpt-plugin-v{version}.zip" + version, checksum = build_package(root, output) + except (ChatGPTPluginPackageError, MCPPackageError, OSError) as error: + print(f"error: {error}", file=sys.stderr) + return 1 + print(f"package: {output}\nversion: {version}\nsha256: {checksum}\nfiles: {len(PLUGIN_FILES)} (explicit allowlist)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/build_release.py b/scripts/build_release.py index df8f94b..309c4af 100644 --- a/scripts/build_release.py +++ b/scripts/build_release.py @@ -14,6 +14,7 @@ from pathlib import Path, PurePosixPath import build_chrome_package as chrome +import build_chatgpt_plugin_package as chatgpt import build_mcp_package as mcp import build_plugin_package as plugin import build_review_fixture as review @@ -79,6 +80,7 @@ def artifact_names(version: str) -> dict[str, str]: "chrome": f"sourcebraid-chrome-v{version}.zip", "openai": f"sourcebraid-plugin-skills-v{version}.zip", "mcp": f"sourcebraid-chatgpt-mcp-v{version}.zip", + "chatgpt": f"sourcebraid-chatgpt-plugin-v{version}.zip", "setup": f"sourcebraid-github-setup-v{version}.py", "fixture": f"sourcebraid-review-fixture-v{version}.zip", } @@ -91,11 +93,13 @@ def source_inputs() -> tuple[str, ...]: "codex-plugin/sourcebraid/.codex-plugin/plugin.json", *(f"codex-plugin/sourcebraid/{name}" for name in plugin.PLUGIN_FILES), *(name for name, _ in mcp.MCP_FILES), + *(name for name, _ in chatgpt.PLUGIN_FILES), str(setup.SETUP_SCRIPT), *setup.SUPPORT_FILES, "scripts/build_chrome_package.py", "scripts/build_plugin_package.py", "scripts/build_setup_package.py", "scripts/build_review_fixture.py", "scripts/build_release.py", "scripts/build_mcp_package.py", + "scripts/build_chatgpt_plugin_package.py", )))) @@ -129,9 +133,12 @@ def start_here(version: str) -> bytes: - Chrome Web Store upload: `{names['chrome']}`. Listing and separate graphics: `chrome-extension/STORE_LISTING.md` and `chrome-extension/store-assets/`. -- ChatGPT connector source: `{names['mcp']}`. Deploy the service following +- ChatGPT portal upload: `{names['chatgpt']}` contains the portable plugin + manifest, remote MCP/OAuth configuration and logo. Complete live service + acceptance before submission; upload does not prove availability or approval. +- ChatGPT service source: `{names['mcp']}`. Deploy the service following `docs/CHATGPT_MCP_DEPLOYMENT.md`, then follow `marketing/OPENAI_MCP_SUBMISSION.md` - to submit its HTTPS endpoint. This source ZIP is not the connector endpoint. + to upload the plugin ZIP and connect/scan its HTTPS endpoint. - Local/Codex skills package: `{names['openai']}`. Follow `marketing/OPENAI_PLUGIN_SUBMISSION.md`, including its runtime requirements. Separate listing graphics are in `codex-plugin/sourcebraid/assets/`. @@ -141,8 +148,9 @@ def start_here(version: str) -> bytes: It is test data, not an end-user archive or a plugin upload. Deutsch: Das Chrome-ZIP wird im Chrome Web Store eingereicht. Das MCP-ZIP enthält -den Server zum Bereitstellen; danach wird dessen HTTPS-Endpunkt bei OpenAI -eingereicht. Das GitHub-Setup-Skript ist eine Download-Datei für den GitHub Release. +den Server zum Bereitstellen. Das separate ChatGPT-Plugin-ZIP wird bei OpenAI +hochgeladen; dort wird sein HTTPS-Endpunkt verbunden und geprüft. +Das GitHub-Setup-Skript ist eine Download-Datei für den GitHub Release. Die Bau- und Prüfschritte veröffentlichen noch nichts. `RELEASE_MANIFEST.json` records the source commit, whether allowlisted inputs @@ -211,6 +219,7 @@ def verify_bundle(directory: Path) -> dict[str, object]: verify_zip(directory / names["chrome"], chrome.PACKAGE_FILES) verify_zip(directory / names["openai"], (".codex-plugin/plugin.json", *plugin.PLUGIN_FILES)) verify_zip(directory / names["mcp"], tuple(name for _, name in mcp.MCP_FILES)) + verify_zip(directory / names["chatgpt"], tuple(name for _, name in chatgpt.PLUGIN_FILES)) verify_zip(directory / names["fixture"], tuple(review.fixture_files()), fixture=True) with zipfile.ZipFile(directory / names["openai"]) as archive: packaged_manifest = json.loads(archive.read(".codex-plugin/plugin.json")) @@ -226,6 +235,8 @@ def verify_bundle(directory: Path) -> dict[str, object]: lock = json.loads(archive.read("package-lock.json")) if package.get("version") != version or lock.get("version") != version or lock.get("packages", {}).get("", {}).get("version") != version: raise ReleaseError("ChatGPT MCP package version does not match the release") + with zipfile.ZipFile(directory / names["chatgpt"]) as archive: + chatgpt.validate_metadata(json.loads(archive.read("plugin.json")), json.loads(archive.read("mcp.json")), version) return manifest except (OSError, ValueError, KeyError, TypeError, zipfile.BadZipFile) as error: raise ReleaseError(f"could not verify release bundle: {error}") from error @@ -248,6 +259,7 @@ def assemble(root: Path, destination: Path) -> dict[str, object]: setup.build_package(root, destination / names["setup"]) review.build_package(destination / names["fixture"]) mcp.build_package(root, destination / names["mcp"]) + chatgpt.build_package(root, destination / names["chatgpt"]) for name in KIT_FILES: path = destination / name path.parent.mkdir(parents=True, exist_ok=True) @@ -333,7 +345,7 @@ def main(argv: list[str] | None = None) -> int: raise ReleaseError("release version does not match --expected-version") if args.require_clean and manifest["source"]["release_inputs_modified"] is not False: raise ReleaseError("release inputs were not committed and clean") - except (ReleaseError, chrome.PackageError, plugin.PluginPackageError, mcp.MCPPackageError, setup.SetupPackageError, OSError) as error: + except (ReleaseError, chrome.PackageError, plugin.PluginPackageError, mcp.MCPPackageError, chatgpt.ChatGPTPluginPackageError, setup.SetupPackageError, OSError) as error: print(f"error: {error}", file=sys.stderr) return 1 print(f"release: {output}\nversion: {manifest['version']}\nverified files: {len(manifest['files']) + 2}") diff --git a/tests/test_release_bundle.py b/tests/test_release_bundle.py index 121a8f4..fce50d5 100644 --- a/tests/test_release_bundle.py +++ b/tests/test_release_bundle.py @@ -134,6 +134,47 @@ def test_mcp_package_is_deterministic_and_excludes_runtime_secrets(self): release.mcp.build_package(root, root / "mismatched.zip") self.assertFalse((root / "mismatched.zip").exists()) + def test_hosted_plugin_upload_is_reproducible_and_excludes_local_runtime(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + for source, _ in release.chatgpt.PLUGIN_FILES: + path = root / source + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes((ROOT / source).read_bytes()) + package = json.loads((ROOT / "chatgpt-mcp/package.json").read_text()) + (root / "chatgpt-mcp").mkdir() + (root / "chatgpt-mcp/package.json").write_text(json.dumps(package)) + (root / "chatgpt-mcp/package-lock.json").write_text(json.dumps({ + "version": package["version"], "packages": {"": package}, + })) + (root / "chatgpt-plugin/sourcebraid/.env").write_text("private-fixture-credential") + first = root / "first.zip" + second = root / "second.zip" + self.assertEqual(release.chatgpt.build_package(root, first), release.chatgpt.build_package(root, second)) + with zipfile.ZipFile(first) as archive: + self.assertEqual(set(archive.namelist()), {"plugin.json", "mcp.json", "assets/sourcebraid-icon.png"}) + manifest = json.loads(archive.read("plugin.json")) + server = json.loads(archive.read("mcp.json"))["mcpServers"]["sourcebraid"] + self.assertEqual(manifest["version"], package["version"]) + self.assertEqual(manifest["extensions"]["com.openai"]["interface"]["capabilities"], ["Read"]) + self.assertEqual(server["type"], "streamable-http") + self.assertEqual(server["extensions"]["com.openai"]["auth"]["baseScopes"], ["archive:read"]) + for name in archive.namelist(): + self.assertNotIn(b"private-fixture-credential", archive.read(name)) + + def test_hosted_plugin_rejects_wrong_version_credentials_and_local_transport(self): + manifest = json.loads((ROOT / "chatgpt-plugin/sourcebraid/plugin.json").read_text()) + servers = json.loads((ROOT / "chatgpt-plugin/sourcebraid/mcp.json").read_text()) + version = manifest["version"] + with self.assertRaisesRegex(release.chatgpt.ChatGPTPluginPackageError, "version"): + release.chatgpt.validate_metadata(manifest, servers, "0.0.0") + for extra in ({"headers": {"Authorization": "Bearer synthetic-private"}}, {"type": "stdio", "command": "python3"}): + changed = json.loads(json.dumps(servers)) + changed["mcpServers"]["sourcebraid"].update(extra) + with self.assertRaisesRegex(release.chatgpt.ChatGPTPluginPackageError, "OAuth DCR"): + release.chatgpt.validate_metadata(manifest, changed, version) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_release_packages.py b/tests/test_release_packages.py index 99bbb27..95db2da 100644 --- a/tests/test_release_packages.py +++ b/tests/test_release_packages.py @@ -235,13 +235,21 @@ def test_package_versions_match_and_ios_release_is_independent(self): ios_versions = set(re.findall(r"MARKETING_VERSION = ([^;]+);", project)) mcp_version = re.search(r'^SERVER_VERSION = "([^"]+)"$', mcp, re.MULTILINE) - self.assertEqual(chrome["version"], "1.0.1") + self.assertEqual(chrome["version"], "1.1.0") self.assertEqual(plugin["version"], chrome["version"]) # iOS ships independently under ios-v* tags; v* tags version the # Chrome/Codex/MCP submission bundle together. self.assertEqual(ios_versions, {"1.1.0"}) self.assertIsNotNone(mcp_version) self.assertEqual(mcp_version.group(1), chrome["version"]) + hosted = json.loads((REPOSITORY_ROOT / "chatgpt-mcp/package.json").read_text()) + hosted_plugin = json.loads((REPOSITORY_ROOT / "chatgpt-plugin/sourcebraid/plugin.json").read_text()) + hosted_runtime = (REPOSITORY_ROOT / "chatgpt-mcp/src/auth.mjs").read_text() + hosted_version = re.search(r"^export const VERSION = '([^']+)';$", hosted_runtime, re.MULTILINE) + self.assertEqual(hosted["version"], chrome["version"]) + self.assertEqual(hosted_plugin["version"], chrome["version"]) + self.assertIsNotNone(hosted_version) + self.assertEqual(hosted_version.group(1), chrome["version"]) def test_repo_marketplace_exposes_the_versioned_plugin(self): marketplace_path = REPOSITORY_ROOT / ".agents" / "plugins" / "marketplace.json"