Skip to content

Make Tapper remote-only - #89

Merged
jlrickert merged 2 commits into
mainfrom
feat/immutable-flight-subflights
Aug 31, 2026
Merged

Make Tapper remote-only#89
jlrickert merged 2 commits into
mainfrom
feat/immutable-flight-subflights

Conversation

@jlrickert

Copy link
Copy Markdown
Owner

Tapper carried two storage worlds. A local keg resolved to a filesystem repository on disk; a remote keg resolved to the Hub's operation API. The two paths diverged constantly — locking, snapshots, dex maintenance, and schema validation each had to be implemented and tested twice — and the local path was the one that could not enforce authority, because there is no ACL on a directory.

This settles on the Hub. keg.Keg now has exactly one production shape: RemoteKeg for clients, LocalKeg over the Hub's server-side PgRepo for the Hub itself.

What changes

  • The filesystem repository is gone, along with its lock, snapshot, and event backends and the atomic-write and operation scaffolding that existed only to serve them. Tests move to a purpose-built memory repository under internal/testkegrepo, which is test-only by construction and cannot be reached from a released binary.
  • The pruned keg binary is retired. It existed to offer project-local resolution, so tap becomes the single entrypoint; the GoReleaser build, Homebrew formula, and install task go with it.
  • Configuration and flight manifests are Hub-resolved documents rather than files discovered on disk.
  • The graph visualization surface is removed — the Bun/TypeScript frontend, its embedded bundle, and the Keg and Tap graph operations. Dex links, backlinks, and RelatedNodes remain the supported relationship surface.
  • config is renamed to settings throughout, matching what the document has always been.

Consolidating onto one backend is what makes authority enforceable, so the behavior that depends on it lands here too:

  • Protected writes require read-derived precondition tokens. Settings, schemas, node updates, moves, and removals carry hashes with actionable conflict recovery instead of last-writer-wins.
  • Launch roots are immutable and pinned per connection, with per-call flight selection across the pinned root's recursive accessible graph. A subflight is an ordered list entry on its parent, not an assertion about the whole graph, so cycles are tolerated by deduplication rather than rejected by a write-time walk.
  • Keg settings and schema writes require admin. They steer every agent on the keg, so editor is not sufficient.
  • tap launch no longer requires a configured flight. Bootstrapping a first flight needs an agent session, and that session needed a flight that did not exist yet; a flightless launch runs under identity authority and warns which access is in play.
  • JSON schemas are embedded and materialized so editors can drive completion and validation against the running build.

Verification

Against a clean worktree at 6b911eb:

  • go build ./..., go vet ./... — pass
  • go test ./... — pass across internal/apidoc, pkg/cli, pkg/integrations, pkg/integrations/adapters, pkg/keg, pkg/mcp, pkg/parity, pkg/schemas, pkg/tapper
  • go test -race ./pkg/keg ./pkg/tapper — pass (matches CI)
  • task lint:docs — pass
  • task render-integrations is idempotent; regenerating produces no further diff

The implementation commit was verified in an isolated worktree before the documentation commit was added, so it stands on its own.

Coordination — do not merge alone

This is one half of a coordinated change. Tapper Hub is the server side of the same contract and must land in lockstep; merging this first leaves the Hub pinned to a Tapper that no longer matches its code. Delivery is gated until both sides are jointly verified and the Hub's dependency pin is updated as part of that.

Review notes

The history is deliberately two commits rather than a narrative sequence. pkg/keg/keg_iface.go, pkg/keg/keg_aggregate.go, pkg/tapper/tap.go, and pkg/cli/cmd_root.go are each touched by every theme in the change, so any thematic slice that needs one of them pulls in the others' edits and cannot compile until its dependencies land. integrations/content and integrations/rendered are both embedded via integrations/embed.go, making them build inputs that belong with the implementation. The result is that every commit here builds and tests clean.

Tapper carried two storage worlds. A local keg resolved to a filesystem
repository on disk; a remote keg resolved to the Hub's operation API. The
two paths diverged constantly -- locking, snapshots, dex maintenance, and
schema validation each had to be implemented and tested twice -- and the
local path was the one that could not enforce authority, because there is
no ACL on a directory.

Settle on the Hub. `keg.Keg` now has exactly one production shape:
`RemoteKeg` for clients, `LocalKeg` over the Hub's server-side `PgRepo`
for the Hub itself. The filesystem repository and its lock, snapshot, and
event backends are gone, along with the atomic-write and operation
scaffolding that existed only to serve them. Tests move to a purpose-built
memory repository under internal/testkegrepo, which is test-only by
construction and cannot be reached from a released binary.

With one backend, several surfaces collapse:

- The pruned `keg` binary loses its reason to exist -- it existed to offer
  project-local resolution -- so `tap` becomes the single entrypoint and
  the goreleaser build, Homebrew formula, and install task go with it.
- Configuration and flight manifests become Hub-resolved documents rather
  than files discovered on disk.
- The graph visualization surface is removed: the Bun/TypeScript frontend,
  its embedded bundle, and the Keg and Tap graph operations. Dex links,
  backlinks, and RelatedNodes remain the supported relationship surface.
- `config` is renamed to `settings` throughout, matching what the document
  has always been.

The same consolidation makes authority enforceable, so this commit also
lands the behavior that depends on it:

- Protected writes require read-derived precondition tokens. Settings,
  schemas, node updates, moves, and removals now carry hashes with
  actionable conflict recovery instead of last-writer-wins.
- Launch roots are immutable and pinned per connection, with per-call
  flight selection across the pinned root's recursive accessible graph. A
  subflight is an ordered list entry on its parent, not an assertion about
  the whole graph, so cycles are tolerated by deduplication rather than
  rejected by a write-time walk.
- Keg settings and schema writes require admin. They steer every agent on
  the keg, so editor is not sufficient.
- `tap launch` no longer requires a configured flight. Bootstrapping a
  first flight needs an agent session, and that session needed a flight
  that did not exist yet; a flightless launch runs under identity
  authority and warns which access is in play.
- JSON schemas are embedded and materialized so editors can drive
  completion and validation against the running build.
Bring the prose in line with the single-backend architecture: `tap` is
the only CLI, kegs resolve through configured hubs rather than disk
discovery, `LocalKeg` runs over the Hub's PgRepo, and the keg document is
called settings. Replaces the keg-config configuration page with
keg-settings and drops the graph visualization references.

CHANGELOG.md keeps its hand-written Unreleased notes for now; the release
workflow regenerates the file from Conventional Commit subjects, so this
prose is reference material rather than the published notes.
@jlrickert
jlrickert merged commit dcf626c into main Aug 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant