Renumber note (2026-06-11): previously filed as
ADR-030-ext-github-creds-in-pod, which collided withADR-030-agent-memory-pods. Renumbered to ADR-107 to enforce the one-number-one-decision uniqueness convention (see ADR-105 §Numbering and the ADR index inREADME.md).ADR-030now refers solely to agent-memory-pods.
Ratified
2026-04-19
- ADR-030 — Agent memory in Solid Pods (base ADR this extends)
- ADR-050 — Pod-backed
:KGNodeschema (sovereign-private-node model) - ADR-051 — Visibility transitions (publish/unpublish saga)
- ADR-028-ext — NIP-98 auth extension (used to read per-user Pod creds)
- ADR-052 — WAC default-private container policy (governs
./private/config/) - ADR-053 — solid-pod-rs sidecar
Current VisionClaw ingest uses a single shared GitHub token in the server's
.env file. Source of truth today:
GITHUB_OWNER=jjohare
GITHUB_REPO=logseq
GITHUB_BRANCH=main
GITHUB_BASE_PATH=mainKnowledgeGraph/pages # plus workingGraph/pages
This works for a single-user deployment but fails the multi-tenant sovereign model introduced in Wave 2:
- Each user's private graph lives in their own GitHub repo, under their own account. A shared server token cannot reach user A's repo and user B's repo without a sprawl of server-held tokens.
- Credentials must be sovereign (user-owned, stored in the user's Pod) rather than server-owned. The user grants the backend time-bounded read access; the user can revoke it by rotating the token in their Pod.
- The power user (jjohare) must be bootstrappable from the existing
.envvalues so the migration does not strand the operator.
ADR-030 established Pods as the sovereign store for agent memory. This ADR extends that pattern to GitHub credentials.
Store per-user GitHub credentials in the owner's Pod at ./private/config/github
as a JSON document with the following schema:
{
"owner": "jjohare",
"repo": "logseq",
"branch": "main",
"base_paths": [
"mainKnowledgeGraph/pages",
"workingGraph/pages"
],
"token": "ghp_xxx...",
"token_storage": "plain"
}owner,repo,branchmirror the current.envfields.base_pathsis a list (replaces the singleGITHUB_BASE_PATHenv var) so a user can source from multiple roots inside one repo, matching jjohare'smainKnowledgeGraph/pages+workingGraph/pagessetup.tokenis a GitHub PAT.token_storageis a versioning field."plain"is the v1 value."nip44"is reserved for v1.5 when we add NIP-44 at-rest encryption.
The schema is registered at docs/schemas/pod-github-config.json for
validation at both write time (admin CLI) and read time (backend ingest).
An admin CLI command performs a one-shot, auditable, idempotent migration from
the existing .env to the Pod:
claude-flow vc bootstrap-power-user --env .env
The command:
- Reads
GITHUB_OWNER,GITHUB_REPO,GITHUB_BRANCH, andGITHUB_BASE_PATH(splitting on commas for the list form). - Reads
GITHUB_TOKEN. - Writes the JSON document to
./private/config/githubin the operator's Pod via an authenticated-as-ownerPATCH. - Emits a kind-30301 audit event (distinct from the kind-30300 visibility events in ADR-051) recording the bootstrap.
- Exits non-zero if the Pod resource already exists and its contents differ; idempotent when contents match.
- WAC:
./private/config/is owner-only by default (per ADR-052). No other user and no anonymous read can touch the file. - Backend read: ingest reads the creds via authenticated-as-owner Pod access. On the first ingest request for a user, the user signs a NIP-98 authentication token (ADR-028-ext); the backend uses that token to fetch the creds file just like any other owner-authenticated Pod request.
- Token lifetime in memory: the backend holds the PAT in memory only for the duration of the ingest run; it is never written to server disk.
Plain text, inside the owner-ACL container. Sufficient for v1 because the Pod ACL closes external access and the backend handles the token only in memory.
Defence-in-depth encryption is deferred to v1.5 and v2:
- v1.5: NIP-44 encryption of the
tokenfield.token_storageflips to"nip44"; backend decrypts using the user's ephemeral session key. - v2: replace the PAT with a GitHub App OAuth flow. Removes the long-lived token entirely; token_storage becomes irrelevant.
GITHUB_CREDS_IN_POD=true|false:
true(new path): ingest reads per-user creds from the user's Pod. A user without creds in their Pod cannot ingest.false(legacy path): ingest falls back to the shared server.envtoken. Used during migration and for single-user deployments.
- Sovereign: GitHub auth is user-owned, not server-owned. Users rotate their own tokens; operators do not hold long-lived user credentials.
- Multi-tenant ingest: each user's graph is pulled from their own repo. The server reaches user A's repo and user B's repo using each user's own PAT, with no shared token.
- No shared server-side token sprawl: the operator does not accumulate a growing set of PATs for each onboarded user.
- Auditable bootstrap: the kind-30301 audit event records who bootstrapped whom, when.
- PAT leakage risk if Pod ACL is misconfigured. Mitigated by ADR-052 (default-private container policy) and integration tests that assert a different user cannot read the creds file.
- Token rotation requires user action. A stale PAT blocks ingest until the user updates it in their Pod. Resolved by the v2 GitHub App OAuth path.
- First-run latency: the initial NIP-98 signing handshake adds one round-trip before the first ingest can fetch creds.
- No change to the ingest pipeline's downstream behaviour; only the credential source changes.
base_pathsgeneralisation is backwards-compatible with the single-path.envform via the bootstrap CLI's comma-split.
- JSON schema defined at
docs/schemas/pod-github-config.json -
claude-flow vc bootstrap-power-user --env .envCLI reads.envand writes the Pod resource - Backend ingest reads Pod creds via authenticated-as-owner NIP-98 (no shared server-token code path when flag is
true) - Pod ACL enforcement test: a second user cannot read the first user's creds file
-
GITHUB_CREDS_IN_POD=falsepreserves existing shared-token behaviour - kind-30301 audit event emitted on bootstrap
- Backend holds the PAT in memory only (never written to server disk)
- Set
GITHUB_CREDS_IN_POD=false. Ingest falls back to the.env-supplied token; the Pod resource remains untouched and can be deleted at leisure from the operator's Pod. - The bootstrap CLI is idempotent and non-destructive; no cleanup required on the Pod side.
docs/schemas/pod-github-config.json— schema definitionsrc/cli/vc_bootstrap.rs— bootstrap CLI implementationsrc/ingest/github_creds.rs— Pod-backed cred readersrc/audit/nostr_events.rs— kind-30301 bootstrap event