[{"data":1,"prerenderedAt":299},["ShallowReactive",2],{"blog-de-den-zweiten-service-provider-habe-ich-nicht-entworfen":3,"header-blog-translations-/de/blog/den-zweiten-service-provider-habe-ich-nicht-entworfen":296},{"id":4,"title":5,"author":6,"body":7,"date":280,"description":281,"draft":282,"extension":283,"image":284,"meta":285,"navigation":192,"path":286,"seo":287,"stem":288,"tags":289,"translationKey":294,"__hash__":295},"blog_de/blog/de/den-zweiten-service-provider-habe-ich-nicht-entworfen.md","Den zweiten Service-Provider habe ich nicht entworfen","Patrick Hofmann",{"type":8,"value":9,"toc":273},"minimark",[10,23,35,40,43,54,57,61,64,82,97,101,104,107,111,114,124,147,206,221,224,228,231,238,244,247,269],[11,12,13,14,18,19,22],"p",{},"Meine Activity-Logs füttern die Timesheets pro Projekt und Firma. Bisher war das eine ",[15,16,17],"code",{},"jq","-Pipeline über JSONL — funktioniert, aber ich kann es nicht am Handy aufmachen und fragen, wie viele Stunden im Mai auf ein bestimmtes Projekt liefen. Also ",[15,20,21],{},"timetrack.openape.ai",".",[11,24,25,26,29,30,34],{},"Der ehrliche Teil: Ich habe mich nicht hingesetzt und einen Service entworfen. Ich habe ",[15,27,28],{},"openape-tasks"," kopiert und umbenannt. Der erste Commit im neuen Repo heißt wörtlich ",[31,32,33],"em",{},"\"mirror openape-tasks, rename to timetrack\"",". Das ist kein Tippfehler in der Commit-Message, das ist die Methode.",[36,37,39],"h2",{"id":38},"wie-der-erste-service-provider-war","Wie der erste Service-Provider war",[11,41,42],{},"Erfunden. Jeder Baustein war eine Entscheidung, die einmal getroffen wurde, schmerzhaft, mit Smoke-Tests, die beim ersten echten Dogfooding fünf verkettete Bugs auf einmal fanden.",[11,44,45,46,49,50,53],{},"DDISA-Discovery: ein SP registriert sich nicht im IdP, er publiziert seine Metadata über DNS. Token-Exchange: RFC 8693, mit dem delegation-grant-only Pfad für den Fall, dass der Caller nicht beide Tokens hat. Die zweistufige RBAC. Das ",[15,47,48],{},"ape-*","-CLI-Muster mit ",[15,51,52],{},"--json"," für agent-scriptbaren Output. Jedes davon war Arbeit, bei der ich nicht wusste, wie es ausgeht, bis es ausging.",[11,55,56],{},"Das ist der teure Teil. Den macht man einmal.",[36,58,60],{"id":59},"wie-timetrack-war","Wie timetrack war",[11,62,63],{},"Die eigentliche Arbeit war die Domäne: Firmen und Projekte, wer sieht welche Einträge, wie ein Report nach Projekt gruppiert. Die zweistufige RBAC musste auf das neue Datenmodell gelegt werden — eine Sichtbarkeitsfunktion plus die Tests, die die Matrix aus der Spec abklopfen.",[11,65,66,67,70,71,74,75,78,79,81],{},"Was ",[31,68,69],{},"kein"," Schritt war: die Identity-Schicht. Es gibt keinen Milestone \"Auth designen\". Nicht weil ich es vergessen hätte, sondern weil diese Schicht in jedem SP dieselbe ist. DDISA-Discovery, Token-Exchange, das Auth-Plus-Exchange-Route-Paar — kopiert, ",[15,72,73],{},"aud","/",[15,76,77],{},"iss"," auf ",[15,80,21],{}," gezogen, fertig. Der erste echte Agent-E2E gegen Prod war grün.",[11,83,84,85,88,89,92,93,96],{},"Die Reibung, die tatsächlich Zeit gekostet hat, lag nicht im SP. ",[15,86,87],{},"ape-timetrack companies use"," gegen localhost hat den ",[15,90,91],{},"activeEndpoint"," persistiert, ein lokaler State, der einen späteren Lauf gegen Prod als \"HTTP 0\" sterben ließ. Zwei Fehldiagnosen — erst ",[15,94,95],{},"cli-auth",", dann Node 25 — bevor der echte Grund auf dem Tisch lag: Test-Pollution des CLI-State, nichts am Protokoll. Das ist die Pointe an der Reibung: sie war im lokalen State, nicht in der Architektur. Die Architektur hat sich mechanisch verhalten.",[36,98,100],{"id":99},"was-weggefallen-ist","Was weggefallen ist",[11,102,103],{},"Das Erfinden. Es gibt keinen Punkt mehr, an dem ich überlege, wie ein SP sich gegenüber dem IdP authentifiziert. Diese Frage ist beantwortet, und die Antwort ist überall dieselbe.",[11,105,106],{},"Genau das macht einen neuen SP kopierbar statt erfindbar. Und genau da kippt es.",[36,108,110],{"id":109},"kopierbar-heißt-auch-annahmen-werden-mitkopiert","Kopierbar heißt auch: Annahmen werden mitkopiert",[11,112,113],{},"In derselben Woche landeten drei Commits mit identischen Messages in fünf Repos:",[115,116,121],"pre",{"className":117,"code":119,"language":120},[118],"language-text","fix: resolve IdP issuer from subject DDISA, drop hardcoded issuer\nfeat: declare scope catalog in /.well-known/openape.json\nfeat: enforce delegated scopes (chokepoint + subset@exchange + short TTL)\n","text",[15,122,119],{"__ignoreMap":123},"",[11,125,126,128,129,128,132,128,135,138,139,142,143,146],{},[15,127,28],{},", ",[15,130,131],{},"openape-plans",[15,133,134],{},"openape-preview",[15,136,137],{},"openape-timetrack",", der ",[15,140,141],{},"sp-starter"," — fünf Mal dasselbe, weil der Fehler fünf Mal derselbe war. Der Issuer war hardcoded auf ",[15,144,145],{},"https://id.openape.ai",". Konzeptuell:",[115,148,152],{"className":149,"code":150,"language":151,"meta":123,"style":123},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// vorher: in jedem SP gleich, weil aus dem ersten SP kopiert\nconst ISSUER = \"https://id.openape.ai\"\n\n// nachher: Issuer aus dem _ddisa-DNS-Record der Subject-Domain,\n// nicht mehr fest verdrahtet\n","ts",[15,153,154,163,187,194,200],{"__ignoreMap":123},[155,156,159],"span",{"class":157,"line":158},"line",1,[155,160,162],{"class":161},"sHwdD","// vorher: in jedem SP gleich, weil aus dem ersten SP kopiert\n",[155,164,166,170,174,178,181,184],{"class":157,"line":165},2,[155,167,169],{"class":168},"spNyl","const",[155,171,173],{"class":172},"sTEyZ"," ISSUER ",[155,175,177],{"class":176},"sMK4o","=",[155,179,180],{"class":176}," \"",[155,182,145],{"class":183},"sfazB",[155,185,186],{"class":176},"\"\n",[155,188,190],{"class":157,"line":189},3,[155,191,193],{"emptyLinePlaceholder":192},true,"\n",[155,195,197],{"class":157,"line":196},4,[155,198,199],{"class":161},"// nachher: Issuer aus dem _ddisa-DNS-Record der Subject-Domain,\n",[155,201,203],{"class":157,"line":202},5,[155,204,205],{"class":161},"// nicht mehr fest verdrahtet\n",[11,207,208,209,212,213,216,217,220],{},"Der erste SP hatte den hardcoded Issuer. Jede Kopie hat ihn mitgenommen. Niemand ist gestolpert, weil ",[15,210,211],{},"hofmann.eco"," per ",[15,214,215],{},"_ddisa","-DNS ohnehin auf ",[15,218,219],{},"id.openape.ai"," zeigt — die Verletzung war behavior-preserving, also unsichtbar. Aufgefallen ist sie erst, als ich das SP-Data-Access-Profil geschrieben und über alle fünf SPs hinweg auditiert habe.",[11,222,223],{},"Ein Audit der Doku hätte das nicht gefunden — der Code war ja überall \"korrekt\", weil überall gleich falsch. Gefunden hat es erst echtes Cross-SP-E2E.",[36,225,227],{"id":226},"der-schnitt","Der Schnitt",[11,229,230],{},"Das Erfinden ist einmalig und teuer. Das Kopieren ist billig und uniform — im Guten wie im Schlechten. Die uniforme richtige Entscheidung trägt jeden neuen SP fast mechanisch. Die uniforme falsche Annahme reproduziert sich genauso mechanisch, fünf Mal, und wird mit fünf Mal demselben Diff gefixt.",[11,232,233,234,237],{},"Dass die Architektur kopierbar ist, ist der Gewinn. Der Beweis, dass sie ",[31,235,236],{},"wirklich"," kopierbar ist, ist, dass der Bug es auch war.",[11,239,240,241,243],{},"Den ",[15,242,141],{}," habe ich danach öffentlich gemacht. Nicht weil ich geplant hatte, ein Template zu bauen. Ich wollte einen Timetracker für meine Stundenlisten — und habe dabei gemerkt, dass das Ding, von dem ich kopiere, jetzt ein Ding ist statt meiner Erinnerung an den letzten SP. Ein Template ist nur ehrlicher darüber, was sowieso passiert: der nächste SP wird nicht erfunden.",[245,246],"hr",{},[11,248,249],{},[31,250,251,252,259,260,263,264,22],{},"Code: ",[253,254,258],"a",{"href":255,"rel":256},"https://github.com/openape-ai/openape",[257],"nofollow","github.com/openape-ai/openape",", MIT-lizenziert. Das SP-Data-Access-Profil liegt in ",[15,261,262],{},"protocol",", der Kopier-Ausgangspunkt in ",[253,265,268],{"href":266,"rel":267},"https://github.com/openape-ai/sp-starter",[257],"github.com/openape-ai/sp-starter",[270,271,272],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":123,"searchDepth":165,"depth":165,"links":274},[275,276,277,278,279],{"id":38,"depth":165,"text":39},{"id":59,"depth":165,"text":60},{"id":99,"depth":165,"text":100},{"id":109,"depth":165,"text":110},{"id":226,"depth":165,"text":227},"2026-05-15","Ich wollte einen Timetracker für meine eigenen Stundenlisten. Herausgekommen ist ein Beweis, dass ein neuer protokoll-konformer Service-Provider keine Erfindung mehr ist, sondern eine Kopie — mit allem, was Kopieren mit sich bringt.",false,"md",null,{},"/blog/de/den-zweiten-service-provider-habe-ich-nicht-entworfen",{"title":5,"description":281},"blog/de/den-zweiten-service-provider-habe-ich-nicht-entworfen",[290,291,292,293],"OpenApe","Infrastructure","Architecture","Building in Public","second-sp-was-not-designed","p8UYkdRLFJJE5qK8wJeGWEg02hbTzjTfs7kBWyNPdmY",{"de":297,"en":298},"/de/blog/den-zweiten-service-provider-habe-ich-nicht-entworfen","/en/blog/second-sp-was-not-designed",1779001883940]