~bigbes/sr-ht-dolt

93e699101e297244802590365e96da638404b579 — Eugene Blikh 3 days ago 3843eb5
storage: create databases empty so the first push needs no --force

Every automatic creation path wrote an "Initialize data repository"
commit through WriteEmptyRepo, and that commit is history. dolt decides
fast-forward on the client (actions.CanFastForward over the remotesapi),
so the server cannot forgive the collision: pushing a database that has a
root commit of its own — a beads tracker, anything grown locally — was
rejected as a non-fast-forward and could only land with --force. That is
the whole reason the companion-database recipe starts with a forced push.

push-to-create already provisioned an empty store for this exact reason.
Give the other two paths the same default: /internal/repos, whose caller
is git.sr.ht's post-update hook and therefore fires before its user has
ever pushed, now always provisions empty, and the web form does unless
its new "initialize with an empty commit" checkbox is ticked. The
checkbox buys what an empty store cannot offer — a database that can be
cloned before anything is pushed to it, since dolt refuses a store with
no commits as "contains no Dolt data".

Which is also why the overview of a database with no branches now teaches
push rather than clone: the clone box there quoted a command that could
not work. A store that fails to open is deliberately not treated as
empty — an unreadable database must not be advertised as a fresh one.
M cmd/doltsrht/main.go => cmd/doltsrht/main.go +4 -0
@@ 76,6 76,10 @@ func (m *storeManager) InitStore(ctx context.Context, absPath, ownerName, ownerE
	return storage.InitStore(ctx, absPath, ownerName, ownerEmail)
}

func (m *storeManager) InitEmptyStore(ctx context.Context, absPath string) error {
	return storage.InitEmptyStore(ctx, absPath)
}

func (m *storeManager) DeleteStore(ctx context.Context, root, absPath string) error {
	return storage.DeleteStore(ctx, root, absPath)
}

M cmd/doltsrht/main_test.go => cmd/doltsrht/main_test.go +4 -0
@@ 255,6 255,10 @@ func (noStores) InitStore(context.Context, string, string, string) error {
	panic("cmd/doltsrht: the boot tests must not touch the on-disk stores")
}

func (noStores) InitEmptyStore(context.Context, string) error {
	panic("cmd/doltsrht: the boot tests must not touch the on-disk stores")
}

func (noStores) DeleteStore(context.Context, string, string) error {
	panic("cmd/doltsrht: the boot tests must not touch the on-disk stores")
}

M docs/DESIGN.md => docs/DESIGN.md +10 -2
@@ 68,7 68,15 @@ Key verified facts that shape the design:
  = authorization), web UI owns the rest. `getScheme()` honors
  `x-forwarded-proto` — nginx must set it on grpc_pass.
- Empty-db init primitive: `doltdb.LoadDoltDB(ctx, types.Format_Default, fileURL, fs)`
  + `ddb.WriteEmptyRepo(ctx, "main", name, email)`.
  + `ddb.WriteEmptyRepo(ctx, "main", name, email)`. **This is the opt-in path, not
  the default one**: dolt decides fast-forward on the client, so the initial commit
  it writes makes the first push from any database with a history of its own a
  non-fast-forward the server cannot forgive. Every creation path (web form,
  `/internal/repos`, push-to-create) therefore defaults to `InitEmptyStore` — a bare
  NBS store with no commits and no branches — and only the create form's
  "initialize with an empty commit" checkbox reaches `WriteEmptyRepo`. The cost of an
  empty store is that it cannot be cloned (`ErrNoDataAtRemote`), which is why the
  overview of a database with no branches teaches push instead of clone.
- Bare stores **cannot** be opened by the sqle engine / embedded driver (they expect
  working sets). Browse UI uses low-level read-only APIs instead (see §Browse).
- dolt CLI v2.1.10 installed at `/opt/homebrew/bin/dolt` (integration tests).


@@ 88,7 96,7 @@ core/       # PURE domain: names.go (validate, ParseRepoPath), access.go (matrix
db/         # postgres: repos.go, access.go, keys.go (dolt_key CRUD)
authn/      # ctx.go (Caller), cookie.go (optional unified-login middleware),
            # token.go (Basic: PAT trio + 60s cache), jwt.go (Bearer: EdDSA verify)
storage/    # init.go (InitStore/DeleteStore via WriteEmptyRepo), dbcache.go (remotesrv.DBCache)
storage/    # init.go (InitEmptyStore default / InitStore opt-in / Delete/MoveStore), dbcache.go (remotesrv.DBCache)
remoteapi/  # server.go (remotesrv assembly), interceptors.go, credsvc.go (WhoAmI grpc server)
browse/     # open.go, log.go, tables.go, diff.go — read-only doltdb over bare stores
web/        # router.go, handlers_*.go, templates.go, templates/*.html

M storage/init.go => storage/init.go +7 -0
@@ 46,6 46,13 @@ func RepoDiskPath(root, owner, name string) string {
// into it with a single "main" branch and an initial commit authored by
// ownerName/ownerEmail.
//
// It is the OPT-IN half of database creation — the create form's "initialize
// with an empty commit" checkbox — and not what any automatic path uses. The
// initial commit it writes is history, so a client pushing a database that has
// its own history is pushing a non-fast-forward and must --force. What it buys
// in exchange is a database that can be cloned before anything is pushed to it,
// which InitEmptyStore's result cannot.
//
// absPath must be absolute. On any failure after the directory is created,
// InitStore removes absPath so a failed creation never leaves a partial store
// behind. Idempotence is NOT provided: calling InitStore on an existing store

M web/deps.go => web/deps.go +12 -0
@@ 83,7 83,19 @@ type Config struct {
type StoreManager interface {
	// InitStore creates a bare store at absPath and writes an empty repo authored
	// by ownerName/ownerEmail. On any failure it must leave no partial store.
	// That empty repo is an "Initialize data repository" commit, so it is the
	// opt-in half of creation: see InitEmptyStore for why it is not the default.
	InitStore(ctx context.Context, absPath, ownerName, ownerEmail string) error
	// InitEmptyStore creates a bare store at absPath with NO commits and NO
	// branches, so a client's first push lands as the initial history instead of
	// being rejected as a non-fast-forward. dolt decides fast-forward on the
	// client (actions.CanFastForward over the remotesapi), so an initial commit
	// on our side cannot be forgiven by the server — it can only be not written.
	// The cost is that a store with no commits cannot be dolt-cloned at all
	// ("remote at that url contains no Dolt data"), which is why the empty
	// overview page teaches push rather than clone. On any failure it must leave
	// no partial store.
	InitEmptyStore(ctx context.Context, absPath string) error
	// DeleteStore removes the store at absPath, refusing anything outside root.
	DeleteStore(ctx context.Context, root, absPath string) error
	// Evict closes and drops any memoized served handle for diskPath, so a

M web/handlers_internal.go => web/handlers_internal.go +10 -24
@@ 7,10 7,6 @@ import (
	"net/http"
	"strings"

	"sourcecraft.dev/bigbes/sr-ht-core/config"

	"sourcecraft.dev/bigbes/sr-ht-ecore/instconf"

	"sourcecraft.dev/bigbes/sr-ht-dolt/core"
	"sourcecraft.dev/bigbes/sr-ht-dolt/db"
)


@@ 134,26 130,16 @@ func (a *app) handleInternalCreate(w http.ResponseWriter, r *http.Request) {
		return
	}

	// The initial empty commit's author is cosmetic (real pushes overwrite
	// history); mirror handleCreate and author it as the instance owner,
	// falling back to the database owner.
	authorName, authorEmail := config.GetOwner(a.cfg.Conf)
	if authorName == "" {
		authorName = caller.Username
	}
	if authorEmail == "" {
		// The authority and not the bare host: this is the domain half of an
		// address that identifies *this* deployment, and two instances behind
		// one hostname on two ports are two of them. An origin that names no
		// host at all yields "", and then there is no domain to synthesize —
		// leave the address empty rather than emit "alice@".
		if authority := instconf.OriginAuthority(a.chrome.SelfOrigin()); authority != "" {
			authorEmail = caller.Username + "@" + authority
		}
	}
	if err := a.cfg.Stores.InitStore(ctx, diskPath, authorName, authorEmail); err != nil {
		// InitStore self-cleans its directory; undo the metadata row too so a
		// failed provision leaves nothing behind and a retry can start clean.
	// The companion is provisioned EMPTY — no branches, no "Initialize data
	// repository" commit. This endpoint fires before its user has ever pushed,
	// and whatever they push first (a beads tracker, a database built locally)
	// has a history of its own. dolt decides fast-forward on the client, so an
	// initial commit here would make every such first push a non-fast-forward
	// the server cannot forgive — the user's only way in would be --force. An
	// empty store lets that first push land as the database's initial history.
	if err := a.cfg.Stores.InitEmptyStore(ctx, diskPath); err != nil {
		// InitEmptyStore self-cleans its directory; undo the metadata row too so
		// a failed provision leaves nothing behind and a retry can start clean.
		_ = a.cfg.Repos.DeleteRepo(ctx, created.ID)
		http.Error(w, "initialize database store", http.StatusInternalServerError)
		return

M web/handlers_internal_test.go => web/handlers_internal_test.go +12 -6
@@ 73,10 73,15 @@ func TestInternalCreateProvisions(t *testing.T) {
	if got.Visibility != core.VisibilityPrivate {
		t.Fatalf("default visibility: got %q, want PRIVATE", got.Visibility)
	}
	// On-disk store initialized at the mapped path.
	if len(h.stores.initCalls) != 1 || !strings.HasSuffix(h.stores.initCalls[0], "/~alice/widgets") {
		t.Fatalf("expected InitStore at ~alice/widgets, got %v", h.stores.initCalls)
	}
	// On-disk store initialized at the mapped path — and initialized EMPTY. The
	// caller is git.sr.ht's post-update hook, which fires before its user has
	// ever pushed to the companion: an "Initialize data repository" commit here
	// would make that first push a non-fast-forward and force the user to
	// --force their own history in.
	if len(h.stores.initEmptyCalls) != 1 || !strings.HasSuffix(h.stores.initEmptyCalls[0], "/~alice/widgets") {
		t.Fatalf("expected InitEmptyStore at ~alice/widgets, got %v", h.stores.initEmptyCalls)
	}
	assert.Empty(t, h.stores.initCalls, "an auto-provisioned companion must carry no initial commit")
}

func TestInternalCreateIdempotent(t *testing.T) {


@@ 92,8 97,9 @@ func TestInternalCreateIdempotent(t *testing.T) {
		t.Fatalf("expected created:false, got %s", rec.Body.String())
	}
	// Must NOT touch disk when the row already exists.
	if len(h.stores.initCalls) != 0 {
		t.Fatalf("InitStore must not run for an existing companion, got %v", h.stores.initCalls)
	if len(h.stores.initCalls)+len(h.stores.initEmptyCalls) != 0 {
		t.Fatalf("no store init may run for an existing companion, got %v / %v",
			h.stores.initCalls, h.stores.initEmptyCalls)
	}
}


M web/handlers_repo.go => web/handlers_repo.go +45 -10
@@ 1,6 1,7 @@
package web

import (
	"context"
	"errors"
	"log/slog"
	"net/http"


@@ 56,6 57,12 @@ type createForm struct {
	Name        string
	Description string
	Visibility  string
	// Initialize asks for an "Initialize data repository" commit in the new
	// store. It defaults to OFF: an initial commit makes the first push from a
	// database with a history of its own a non-fast-forward (dolt decides that
	// on the client), so it would have to be forced. On costs the opposite —
	// the database is clonable immediately, which an empty store is not.
	Initialize bool
}

func (a *app) renderCreate(w http.ResponseWriter, r *http.Request, status int, form createForm, errMsg string) {


@@ 90,6 97,9 @@ func (a *app) handleCreate(w http.ResponseWriter, r *http.Request) {
		Name:        strings.TrimSpace(values.Get("name")),
		Description: strings.TrimSpace(values.Get("description")),
		Visibility:  values.Get("visibility"),
		// An unchecked checkbox is simply absent from the submission, so any
		// value at all means checked.
		Initialize: values.Get("initialize") != "",
	}

	visibility, ok := parseVisibility(form.Visibility)


@@ 103,14 113,6 @@ func (a *app) handleCreate(w http.ResponseWriter, r *http.Request) {
	}

	owner := ac.Username
	ownerName, ownerEmail := config.GetOwner(a.cfg.Conf)
	if ownerEmail == "" {
		ownerEmail = ac.Email
	}
	if ownerName == "" {
		ownerName = owner
	}

	diskPath := a.cfg.RepoDiskPath(owner, form.Name)
	repo := &core.Repo{
		Name:        form.Name,


@@ 135,8 137,13 @@ func (a *app) handleCreate(w http.ResponseWriter, r *http.Request) {
		return
	}

	if err := a.cfg.Stores.InitStore(r.Context(), diskPath, ownerName, ownerEmail); err != nil {
		// InitStore self-cleans its directory; undo the metadata row too.
	// Empty by default, so the first push from a database that already has a
	// history lands as this one's initial history rather than being rejected as
	// a non-fast-forward. The checkbox buys the opposite trade: an initial
	// commit, and with it a database that can be cloned before anything is
	// pushed to it.
	if err := a.initStore(r.Context(), diskPath, ac, form.Initialize); err != nil {
		// Both init paths self-clean their directory; undo the metadata row too.
		_ = a.cfg.Repos.DeleteRepo(r.Context(), created.ID)
		http.Error(w, "failed to initialize database store", http.StatusInternalServerError)
		return


@@ 145,6 152,26 @@ func (a *app) handleCreate(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/~"+owner+"/"+form.Name, http.StatusSeeOther)
}

// initStore materializes the on-disk store for a newly created database. With
// initialize false — the default — it writes a store with no commits at all, so
// the owner's first push is a fast-forward from empty. With initialize true it
// writes the "Initialize data repository" commit, whose author is cosmetic
// (real pushes overwrite it): the instance owner from the config, falling back
// to the creating user's own name and address.
func (a *app) initStore(ctx context.Context, diskPath string, ac *authContext, initialize bool) error {
	if !initialize {
		return a.cfg.Stores.InitEmptyStore(ctx, diskPath)
	}
	ownerName, ownerEmail := config.GetOwner(a.cfg.Conf)
	if ownerName == "" {
		ownerName = ac.Username
	}
	if ownerEmail == "" {
		ownerEmail = ac.Email
	}
	return a.cfg.Stores.InitStore(ctx, diskPath, ownerName, ownerEmail)
}

// handleUser renders a single user's visible databases (~user listing).
func (a *app) handleUser(w http.ResponseWriter, r *http.Request) {
	owner := chi.URLParam(r, "user")


@@ 249,6 276,7 @@ func (a *app) handleOverview(w http.ResponseWriter, r *http.Request) {
		Views         []View
		CloneURL      string
		BrowseFailed  bool
		Empty         bool
	}{
		Page:          a.page(r, repo.OwnerName+"/"+repo.Name+" — "+serviceName),
		Repo:          repo,


@@ 258,6 286,13 @@ func (a *app) handleOverview(w http.ResponseWriter, r *http.Request) {
		Views:         views,
		CloneURL:      a.cloneURL(repo),
		BrowseFailed:  browseFailed,
		// A database nothing has been pushed to yet: it has no branches and the
		// store read fine, so the emptiness is the answer rather than a symptom.
		// The page then teaches push instead of clone — a store with no commits
		// cannot be cloned at all, dolt refuses it as "contains no Dolt data".
		// A browse failure is deliberately NOT empty: an unreadable store must
		// not be advertised as a fresh one waiting for its first push.
		Empty: !browseFailed && len(branches) == 0,
	}
	a.render(w, http.StatusOK, "overview", view)
}

M web/templates/create.html => web/templates/create.html +14 -0
@@ 28,6 28,20 @@
      <option value="PRIVATE" {{if eq .Form.Visibility "PRIVATE"}}selected{{end}}>Private — only you and collaborators</option>
    </select>
  </div>
  <div class="form-group">
    <label class="form-check-label">
      <input type="checkbox" name="initialize" value="on"
        {{if .Form.Initialize}}checked{{end}}>
      Initialize with an empty commit
    </label>
    <small class="form-text text-muted">
      Leave this off to push a database you already have: the new database starts
      with no commits, so your first push lands as its history and needs no
      <code>--force</code>. Turn it on to get a <code>main</code> branch with an
      empty initial commit, which makes the database clonable right away — at the
      cost of forcing that first push from anywhere else.
    </small>
  </div>
  <button type="submit" class="btn btn-primary">Create</button>
</form>
{{- end}}

M web/templates/overview.html => web/templates/overview.html +21 -0
@@ 10,6 10,26 @@

{{template "viewtabs" (dict "Repo" .Repo "Views" .Views "Current" "overview" "Ref" .DefaultBranch)}}

{{if .Empty}}
<div class="clone-url">
  <h4>Push to get started</h4>
  <p>This database has no commits yet, so there is nothing to clone — push
  something and that push becomes its history. A database you already have works
  as well as a fresh one: its own commits land here unchanged, with no
  <code>--force</code> anywhere.</p>
  <pre>dolt init   # skip in a database you already have
dolt remote add origin {{.CloneURL}}
dolt push origin main</pre>
  <p>Authenticate with a meta.sr.ht personal access token (Basic auth):</p>
  <pre>export DOLT_REMOTE_PASSWORD=&lt;your meta access token&gt;
dolt push --user {{.Repo.OwnerName}} origin main</pre>
  <p>Or, associate a <a href="/settings/keys">dolt key</a> once and push with no
  credentials (like a git SSH key):</p>
  <pre>dolt creds new
dolt login --auth-endpoint {{doltHost .SelfOrigin}} --login-url {{.SelfOrigin}}/settings/keys
dolt push origin main</pre>
</div>
{{else}}
<div class="clone-url">
  <h4>Clone</h4>
  <p>With a meta.sr.ht personal access token (Basic auth):</p>


@@ 67,4 87,5 @@ dolt clone {{.CloneURL}}</pre>
    {{end}}
  </div>
</div>
{{end}}
{{- end}}

M web/web_test.go => web/web_test.go +105 -9
@@ 246,18 246,26 @@ func (f *fakeStore) DeleteKey(_ context.Context, id, userID int) error {
}

type fakeStoreManager struct {
	initErr     error
	deleteErr   error
	evictErr    error
	initCalls   []string
	deleteCalls []string
	evictCalls  []string
	initErr   error
	deleteErr error
	evictErr  error
	// initCalls records InitStore (with an initial commit); initEmptyCalls
	// records InitEmptyStore. They are separate so a test can say which of the
	// two creation paths ran, not merely that a store was created.
	initCalls      []string
	initEmptyCalls []string
	deleteCalls    []string
	evictCalls     []string
}

func (m *fakeStoreManager) InitStore(_ context.Context, absPath, _, _ string) error {
	m.initCalls = append(m.initCalls, absPath)
	return m.initErr
}
func (m *fakeStoreManager) InitEmptyStore(_ context.Context, absPath string) error {
	m.initEmptyCalls = append(m.initEmptyCalls, absPath)
	return m.initErr
}
func (m *fakeStoreManager) DeleteStore(_ context.Context, _, absPath string) error {
	m.deleteCalls = append(m.deleteCalls, absPath)
	return m.deleteErr


@@ 609,6 617,39 @@ func TestOverviewDoesNotPrintTheBrowseError(t *testing.T) {
	})
}

// A database created the new way — an empty store, nothing pushed yet — has no
// branches at all. Its overview must teach push rather than clone: dolt refuses
// to clone a store with no commits ("contains no Dolt data"), so a clone box
// there hands the reader a command that cannot work.
func TestOverviewOfAnEmptyDatabaseTeachesPush(t *testing.T) {
	h := newHarness(t)
	h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice",
		Path: "/var/lib/dolt/~alice/db", Visibility: core.VisibilityPublic})
	h.browse.sess = &fakeSession{}

	rec := h.do("GET", "/~alice/db", nil, nil)
	require.Equal(t, http.StatusOK, rec.Code)
	body := rec.Body.String()

	assert.Contains(t, body, "Push to get started")
	assert.Contains(t, body, "dolt remote add origin")
	assert.Contains(t, body, "dolt push origin main")
	assert.NotContains(t, body, "dolt clone", "an empty store cannot be cloned")
	assert.NotContains(t, body, "Could not read history",
		"emptiness is a state, not a browse failure")

	// A store that cannot be READ is not an empty one: the push instructions
	// would be a lie about a database that may well have history.
	broken := newHarness(t)
	broken.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice",
		Path: "/var/lib/dolt/~alice/db", Visibility: core.VisibilityPublic})
	broken.browse.errByPath = map[string]error{"/var/lib/dolt/~alice/db": errors.New(browseDetail)}

	rec = broken.do("GET", "/~alice/db", nil, nil)
	require.Equal(t, http.StatusOK, rec.Code)
	assert.NotContains(t, rec.Body.String(), "Push to get started")
}

func TestDashboardLists(t *testing.T) {
	h := newHarness(t)
	h.store.add(&core.Repo{Name: "mine", OwnerID: 3, OwnerName: "bob", Path: "/m", Visibility: core.VisibilityPrivate})


@@ 654,20 695,75 @@ func TestCreateValidationAndSuccess(t *testing.T) {
	if got := ok.Header().Get("Location"); got != "/~carol/gooddb" {
		t.Fatalf("create redirect: got %q", got)
	}
	if len(h.stores.initCalls) != 1 || h.stores.initCalls[0] != "/var/lib/dolt/~carol/gooddb" {
		t.Fatalf("InitStore not called correctly: %v", h.stores.initCalls)
	// The form was submitted without the "initialize" checkbox, so the store is
	// created EMPTY: no initial commit for the owner's first push to collide
	// with. The commit-writing path must not have run at all.
	if len(h.stores.initEmptyCalls) != 1 || h.stores.initEmptyCalls[0] != "/var/lib/dolt/~carol/gooddb" {
		t.Fatalf("InitEmptyStore not called correctly: %v", h.stores.initEmptyCalls)
	}
	if len(h.stores.initCalls) != 0 {
		t.Fatalf("InitStore must not run without the initialize checkbox: %v", h.stores.initCalls)
	}
	if _, err := h.store.GetRepoByOwnerAndName(context.Background(), "carol", "gooddb"); err != nil {
		t.Fatalf("repo row not created: %v", err)
	}
}

// TestCreateWithInitializeCheckbox is the other half of TestCreateValidationAndSuccess:
// the checkbox is the only way to get an "Initialize data repository" commit,
// and it must reach InitStore rather than the empty path.
func TestCreateWithInitializeCheckbox(t *testing.T) {
	h := newHarness(t)
	caller := testCaller(5, "carol")

	rec := h.do("POST", "/create", caller, url.Values{
		"name":       {"seeded"},
		"visibility": {"PUBLIC"},
		"initialize": {"on"},
	})
	require.Equal(t, http.StatusSeeOther, rec.Code, rec.Body.String())
	assert.Equal(t, []string{"/var/lib/dolt/~carol/seeded"}, h.stores.initCalls)
	assert.Empty(t, h.stores.initEmptyCalls, "the checkbox selects the commit-writing path exclusively")
}

// TestCreateFormOffersTheInitializeCheckbox pins the control itself: the
// handler's default is only reachable from a browser if the form renders an
// unchecked "initialize" box.
func TestCreateFormOffersTheInitializeCheckbox(t *testing.T) {
	h := newHarness(t)

	rec := h.do("GET", "/create", testCaller(5, "carol"), nil)
	require.Equal(t, http.StatusOK, rec.Code)
	body := rec.Body.String()
	assert.Contains(t, body, `name="initialize"`)
	assert.NotContains(t, body, "checked", "the initialize checkbox defaults to off")
}

// TestCreateEmptyStoreFailureRollsBackRow is TestCreateStoreFailureRollsBackRow
// for the default (empty) path: a failed InitEmptyStore must leave no metadata
// row behind either.
func TestCreateEmptyStoreFailureRollsBackRow(t *testing.T) {
	h := newHarness(t)
	h.stores.initErr = errors.New("disk full")

	rec := h.do("POST", "/create", testCaller(5, "carol"),
		url.Values{"name": {"gooddb"}, "visibility": {"PUBLIC"}})
	require.Equal(t, http.StatusInternalServerError, rec.Code)
	_, err := h.store.GetRepoByOwnerAndName(context.Background(), "carol", "gooddb")
	assert.ErrorIs(t, err, db.ErrNotFound, "orphan repo row survived an empty-store failure")
	assert.Len(t, h.store.deletedRepos, 1)
}

func TestCreateStoreFailureRollsBackRow(t *testing.T) {
	h := newHarness(t)
	h.stores.initErr = errors.New("disk full")
	caller := testCaller(5, "carol")

	rec := h.do("POST", "/create", caller, url.Values{"name": {"gooddb"}, "visibility": {"PUBLIC"}})
	// The checkbox path, so this covers InitStore's rollback specifically; the
	// default (empty) path is TestCreateEmptyStoreFailureRollsBackRow's.
	rec := h.do("POST", "/create", caller, url.Values{
		"name": {"gooddb"}, "visibility": {"PUBLIC"}, "initialize": {"on"},
	})
	if rec.Code != http.StatusInternalServerError {
		t.Fatalf("create with store failure: got %d, want 500", rec.Code)
	}