Skip to content

Pi Durable Platform v0.2.0

Choose a tag to compare

@github-actions github-actions released this 20 Oct 01:02
· 131 commits to main since this release
801a054

Images

The platform (the Go server with the web UI):

ghcr.io/ncecere/pi-durable-platform:v0.2.0
ghcr.io/ncecere/pi-durable-platform@sha256:3f988afa89eadec2b4f38b132cc34016300e10d11e7c08c0258a6546b195222d

The agent runtime (one agent per container; the platform starts it):

ghcr.io/ncecere/pi-durable-platform-runtime:v0.2.0
ghcr.io/ncecere/pi-durable-platform-runtime@sha256:11a098887c13377c985dae169d7dbbaedc854717b1d277e3cdf5aec45602ce8e

Deploy by digest. Verify the signatures with cosign v3 or newer (keyless, GitHub Actions OIDC):

cosign verify ghcr.io/ncecere/pi-durable-platform@sha256:3f988afa89eadec2b4f38b132cc34016300e10d11e7c08c0258a6546b195222d \
  --certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.2.0 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/ncecere/pi-durable-platform-runtime@sha256:11a098887c13377c985dae169d7dbbaedc854717b1d277e3cdf5aec45602ce8e \
  --certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.2.0 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Release assets

  • <image>-v0.2.0-<os>-<arch>.sbom.spdx.json: each image's SBOM (SPDX JSON) for each platform, as attached to the image.
  • <image>-v0.2.0.digest.txt: each image's reference by digest.
  • checksums.txt: SHA-256 of the files above (sha256sum -c checksums.txt).

The SBOM and build provenance are also attached to the images as attestations:
docker buildx imagetools inspect ghcr.io/ncecere/pi-durable-platform@sha256:3f988afa89eadec2b4f38b132cc34016300e10d11e7c08c0258a6546b195222d --format '{{ json .SBOM }}'.

Pi Durable Platform v0.2.0

"Run it anywhere, safely" (2026-10-19), on top of v0.1.2. The platform now ships deployment manifests, runs
as several replicas, exports metrics and traces, can re-encrypt its secrets, and closes the safety gaps the v0.1.0 notes
listed, plus the findings of an independent review of this release. It includes database migration 00017: read
Upgrading first.

What changed

Deploying

  • A Helm chart and a Kustomize base (deploy/) with RBAC, NetworkPolicies (and a Cilium flavour), Pod Security
    restricted, the Kubernetes and Agent Sandbox launchers, an Ingress, a PodDisruptionBudget and a ResourceQuota. The
    chart is the source of truth: the Kustomize base is generated from it, and CI checks that the two render the same
    objects, with Helm 3 and Helm 4. The chart is published, signed with cosign, to
    oci://ghcr.io/ncecere/charts/pi-durable-platform on release tags. CI installs it into a kind cluster with two
    replicas and checks the isolation (an agent reaches only its database, the gateway and DNS), using the images built for
    the commit under test (#1).
  • Several platform replicas (#2). Set replicaCount: 2 (the Kustomize component components/replicas; the chart
    adds rolling updates that leave none missing, spreading over nodes and zones and, if you turn it on, a
    PodDisruptionBudget). The runtime reports its open streams and its work, the reaper believes that report, only one
    replica reaps at a time and starting an agent is serialised across replicas, so no replica can hibernate an agent whose
    stream is open on another. Sessions, sign-in and OAuth state, rate limits and thread events were already shared through
    PostgreSQL. The design and the inventory of what is per replica are in docs/replicas.md.
  • Metrics and tracing (#3). /metrics on its own listener (METRICS_ADDR; the chart enables it on port 9090 and
    allows nobody to reach it until you say who may), with requests by route pattern, agent wakes (cold, warm pool or
    already running), hibernations, model and MCP proxies, OAuth, sign-ins, rate limits, background loops, group re-checks
    and database pools. Labels are values the platform defines; no id, email, team name or URL is ever a label. Optional
    traces over OTLP/HTTP (OTEL_EXPORTER_OTLP_ENDPOINT). A Grafana dashboard and example alerts are in deploy/grafana/,
    and the chart can create a ServiceMonitor, the NetworkPolicy for your scraper and the dashboard ConfigMap.

Operating

  • pi-durable-platform rekey (#4) re-seals every stored secret (agent credentials, model providers, MCP servers
    and the stored identity provider tokens) under the current ENCRYPTION_KEY; --dry-run shows what it would do and
    doctor counts the values still under the previous key. Once it reports none, ENCRYPTION_KEY_PREVIOUS can go. A
    test fails for any sealed column it doesn't cover.
  • A configuration reference (#23). .env.example lists every setting, and a test keeps it complete.
  • The agents database is harder to interfere with (#7). An agent's ownership is a row in its own schema, taken over
    only when the owning database session is gone, so another agent's role can't block it. New commands prune-roles and
    prune-agent-storage. doctor reports agent roles that belong to no agent, and warns when PUBLIC may connect to the
    platform database.
  • Vulnerability checks in CI: govulncheck and audits of the web app's and the runtime's production dependencies,
    weekly and whenever a dependency file changes.

For apps and people

  • Platform-chosen thread IDs (#5). POST /v1/agents/{id}/threads returns a new random thread ID. MCP servers are set
    only on a thread that exists or that the same PUT creates (the caller becomes its creator; send visibility on the
    PUT for a private thread). Use the POST and your threads can't be pre-seeded.
  • IdP group re-checks (#6). With OIDC_ALLOWED_GROUPS, the platform asks the identity provider every 15
    minutes (OIDC_GROUP_RECHECK_INTERVAL) whether each recently active person is still in an allowed group. When not,
    their sessions, API keys and connected apps stop working, open event streams close, and Admin › People shows "Group
    access ended"; signing in again restores access. If the provider can't answer, access lasts a grace period
    (OIDC_GROUP_RECHECK_GRACE, 24 hours). It needs the refresh token of the person's sign-in, so the offline_access
    scope. A person without a stored token is never re-checked: this is logged, counted and shown by doctor.

Fixed and hardened (the result of an independent review of this release; none was known to be exploited)

  • The reaper reads a runtime's /status through a 64 KiB limit; an agent could otherwise send a reply large enough to
    exhaust the platform's memory.
  • Agents whose database role created large objects or stray tables can be deleted; prune-roles removes such leftovers.
  • An agent's role can no longer block platform start-up by holding the agents database's prepare lock.
  • A client's traceparent no longer forces a request to be traced.
  • Open event streams re-check access every 30 seconds, so a person who is suspended, denied or removed from a team stops
    receiving events.
  • MCP servers set on a reserved thread ID don't reach another person's first message.
  • A stored identity provider token that can't be opened ends access after the grace period instead of exempting the
    person; a rotated refresh token survives a restart in the middle of a check; people in more than 200 groups are matched
    on the full list.
  • A status report from an agent's runtime that arrived after a thread was deleted brought the thread back, as a shared
    record carrying the deleted thread's title, even for a private thread (in v0.1.x too). A single report now only
    updates a thread the platform has; the runtime's list at start-up still adopts threads the platform doesn't know.
  • A reserved thread (no message yet) can be deleted; the runtime has no conversation for it, so the delete returned 404.
  • golang.org/x/net 0.60.0 fixes four HTTP/2 advisories (GO-2026-6603, -6611, -6612, -6617) that govulncheck reports
    as reachable. v0.1.2 and earlier are affected.

Upgrading from v0.1.2

  1. Back up the platform database (the migration is forward-only in practice: see rolling back).

  2. Replace both images (the platform and the runtime, by digest) and restart. The platform applies migration 00017
    when it starts.

    • It deletes MCP servers that were set on a thread nobody had started (earlier releases accepted them, and a later
      private thread with that ID would have inherited them) and makes a thread's servers belong to its record from then on.
    • It adds the tables for group re-checks. Nobody is re-checked until they sign in again, so nobody is locked out by the
      upgrade.
    • With several replicas, a rolling upgrade runs both versions for a while: until every replica is upgraded, a request
      to set MCP servers on an unrecorded thread can fail against the new reference. Apps that use
      POST /v1/agents/{id}/threads are unaffected.
  3. Agents upgrade as they wake. An agent that was already running keeps its old runtime until it hibernates; until
    then it doesn't report openStreams, so the reaper judges it by the streams of the replica it is on. An agent database
    from v0.1.x opens with the new runtime without any step.

  4. Keep agents' roles out of the platform database. Run pi-durable-platform doctor. If it warns that PUBLIC may
    connect, run on the server (as the platform database's owner):

    REVOKE CONNECT ON DATABASE <platform database> FROM PUBLIC;
    GRANT CONNECT ON DATABASE <platform database> TO <the platform's role>;

    and the same REVOKE for the other databases on that server that agents must not reach, or limit agent roles to the
    agents database in pg_hba.conf (a rule is in deploy/examples/cloudnative-pg/).
    The platform doesn't do this itself: other roles may connect to those databases.

  5. If you set OIDC_ALLOWED_GROUPS: the identity provider must issue refresh tokens for offline_access (the Helm
    chart adds the scope for you; otherwise add it to OIDC_SCOPES, and allow refresh tokens for the client in the
    provider). People sign in again to store a token. doctor says how many recently active people have none, and the
    pdp_group_recheck_people{state="without_token"} metric tracks it. Without OIDC_ALLOWED_GROUPS nothing changes.

  6. Metrics are off unless you set METRICS_ADDR (the chart sets it, on port 9090 and unreachable until
    networkPolicy.metrics.from names your scraper). Tracing is off unless OTEL_EXPORTER_OTLP_ENDPOINT is set.

  7. New settings, all optional: METRICS_ADDR, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME,
    OTEL_TRACES_SAMPLER_ARG, OIDC_GROUP_RECHECK_INTERVAL, OIDC_GROUP_RECHECK_GRACE.

Rolling back. Going back to v0.1.2's images works, but the schema stays at 00017: v0.1.2 ignores group denials
(people who lost access get their API keys back until you upgrade again) and a request that sets MCP servers on an
unrecorded thread fails against the new reference. goose down for 00017 exists and discards the group re-check
tokens and denials.

Known limitations

Unchanged from v0.1.0, except these, which this release resolves: one platform replica,
no re-encryption command, no deployment manifests, no metrics, tracing or dashboards, advisory locks in the agents
database (see step 4 above for what remains: a database server is a trust boundary you configure), and thread IDs chosen
by clients (use POST /v1/agents/{id}/threads; a client-chosen ID still works). New or remaining:

  • Per-replica caches. Each replica caches the identity provider's discovery document and apps' logos itself, so a
    consent page can show an app's initials instead of its logo on some requests with two replicas.
  • The local launcher is one process and can't be shared between replicas (the chart has no other launcher than
    kubernetes and agent-sandbox).
  • The agent limit can be exceeded by one under concurrent wakes (too_many_running).
  • Traces stop at the runtime: the platform passes traceparent to the runtime, but the runtime doesn't read it, so
    its own model and MCP calls start new traces.
  • Open streams and keys: a stream closes within 30 seconds when a person loses access. A revoked API key or app grant
    doesn't close a stream that is already open until then, and a service-account key isn't re-checked.
  • Group re-checks are fail-open for people without a stored token (so a provider that issues no refresh tokens
    can't lock everyone out); doctor and the metrics show it.
  • The roadmap plans the rest, each with a GitHub issue labelled roadmap.

Images and chart

Artifact What
ghcr.io/ncecere/pi-durable-platform The platform (distroless, UID 65532, read-only root filesystem)
ghcr.io/ncecere/pi-durable-platform-runtime The agent runtime (Node 22, UID 10001, read-only root filesystem with a writable /tmp)
oci://ghcr.io/ncecere/charts/pi-durable-platform The Helm chart, version 0.2.0

Both images are tagged v0.2.0 and latest, for linux/amd64 and linux/arm64. Deploy by digest. The digests are in the
GitHub Release, its <image>-v0.2.0.digest.txt assets and the workflow's job summary.

ghcr.io/ncecere/pi-durable-platform@sha256:3f988afa89eadec2b4f38b132cc34016300e10d11e7c08c0258a6546b195222d
ghcr.io/ncecere/pi-durable-platform-runtime@sha256:11a098887c13377c985dae169d7dbbaedc854717b1d277e3cdf5aec45602ce8e
ghcr.io/ncecere/charts/pi-durable-platform:0.2.0 (sha256:328a90a22c2d2a82a1e74a7b9667f3fa4bfe5d8ddd8a950967f7596b1a106cb8)

Verifying the images and the chart

Verify the signatures with cosign v3.0 or newer
(keyless, GitHub Actions OIDC):

cosign verify ghcr.io/ncecere/pi-durable-platform@sha256:<digest> \
  --certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.2.0 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/ncecere/pi-durable-platform-runtime@sha256:<digest> \
  --certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.2.0 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/ncecere/charts/pi-durable-platform:0.2.0 \
  --certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/chart.yml@refs/tags/v0.2.0 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

The SBOM (SPDX) and build provenance are attached to each image as attestations, and the Release carries each image's
SBOM per platform with checksums.txt. pi-durable-platform version prints pi-durable-platform 0.2.0 (<commit>); the
runtime image is labelled org.opencontainers.image.version=0.2.0.

Reporting problems

Open an issue at https://github.com/ncecere/pi-durable-platform/issues. Report security problems privately as
SECURITY.md describes.