Back to blog
·by Patrick Hofmann

I Didn't Design the Second Service Provider

I wanted a time tracker for my own timesheets. What came out is proof that a new protocol-conformant service provider is no longer an invention, but a copy — with everything copying brings with it.

OpenApeInfrastructureArchitectureBuilding in Public

My activity logs feed the timesheets per project and company. So far that was a jq pipeline over JSONL — works, but I can't open it on my phone and ask how many hours ran on a specific project in May. So timetrack.openape.ai.

The honest part: I didn't sit down and design a service. I copied openape-tasks and renamed it. The first commit in the new repo literally reads "mirror openape-tasks, rename to timetrack". That's not a typo in the commit message, that's the method.

How the first service provider was

Invented. Every building block was a decision made once, painfully, with smoke tests that found five chained bugs at once in the first real dogfooding.

DDISA discovery: an SP doesn't register in the IdP, it publishes its metadata over DNS. Token exchange: RFC 8693, with the delegation-grant-only path for the case where the caller doesn't have both tokens. The two-tier RBAC. The ape-* CLI pattern with --json for agent-scriptable output. Each of these was work where I didn't know how it would turn out until it turned out.

That's the expensive part. You do it once.

How timetrack was

The actual work was the domain: companies and projects, who sees which entries, how a report groups by project. The two-tier RBAC had to be laid onto the new data model — a visibility function plus the tests that probe the matrix from the spec.

What was not a step: the identity layer. There's no milestone "design auth". Not because I forgot it, but because that layer is the same in every SP. DDISA discovery, token exchange, the auth-plus-exchange-route pair — copied, aud/iss pulled to timetrack.openape.ai, done. The first real agent E2E against prod was green.

The friction that actually cost time wasn't in the SP. ape-timetrack companies use against localhost persisted the activeEndpoint, a local state that let a later run against prod die as "HTTP 0". Two misdiagnoses — first cli-auth, then Node 25 — before the real reason was on the table: test pollution of the CLI state, nothing about the protocol. That's the point about the friction: it was in the local state, not in the architecture. The architecture behaved mechanically.

What fell away

The inventing. There's no longer a point where I think about how an SP authenticates to the IdP. That question is answered, and the answer is the same everywhere.

That's exactly what makes a new SP copyable instead of inventable. And that's exactly where it tips.

Copyable also means: assumptions get copied along

In the same week three commits with identical messages landed in five repos:

fix: resolve IdP issuer from subject DDISA, drop hardcoded issuer
feat: declare scope catalog in /.well-known/openape.json
feat: enforce delegated scopes (chokepoint + subset@exchange + short TTL)

openape-tasks, openape-plans, openape-preview, openape-timetrack, the sp-starter — five times the same, because the bug was the same five times. The issuer was hardcoded to https://id.openape.ai. Conceptually:

// before: the same in every SP, because copied from the first SP
const ISSUER = "https://id.openape.ai"

// after: issuer from the _ddisa DNS record of the subject domain,
// no longer hardwired

The first SP had the hardcoded issuer. Every copy carried it along. Nobody tripped, because hofmann.eco points to id.openape.ai via _ddisa DNS anyway — the violation was behavior-preserving, hence invisible. It only surfaced when I wrote the SP Data Access Profile and audited across all five SPs.

An audit of the docs wouldn't have found it — the code was "correct" everywhere, because it was equally wrong everywhere. Only real cross-SP E2E found it.

The cut

Inventing is one-time and expensive. Copying is cheap and uniform — for better and for worse. The uniform correct decision carries every new SP almost mechanically. The uniform wrong assumption reproduces just as mechanically, five times, and gets fixed with the same diff five times.

That the architecture is copyable is the win. The proof that it's really copyable is that the bug was too.

I made the sp-starter public afterwards. Not because I had planned to build a template. I wanted a time tracker for my timesheets — and noticed in the process that the thing I copy from is now a thing instead of my memory of the last SP. A template is just more honest about what happens anyway: the next SP won't be invented.


Code: github.com/openape-ai/openape, MIT-licensed. The SP Data Access Profile lives in protocol, the copy starting point in github.com/openape-ai/sp-starter.