Seedbed 0.4 is the deterministic Compose-first installer and offline operator control
plane for a local Gnolith installation. It is not the Gnolith application or a
server. Workshop is the sole HTTP /mcp service.
Seedbed owns:
- strict installation configuration and exact artifact/source digests;
- local Compose generation and hardened local-build plumbing;
- Diamond → Taproot → Workshop migration ordering;
- protected
local-bearer-v1credential creation and Workshopbootstrap-v1invocation before listener start; - bounded Docker-local diagnostics;
- opaque owner backup/restore and exact legacy 0.3.2 offline adoption;
- secret-safe plans and receipts for Alembic verification.
Seedbed contains no MCP transport, application runtime, domain CRUD, authorization implementation, search/semantic dispatcher, live snapshot endpoint, HTTP listener, published application image, or GHCR publication workflow.
Installable control planes use the fixed-command local adapter rather than constructing child-process commands:
import { createSeedbedControl } from '@gnolith/seedbed/local-control';
const local = createSeedbedControl(immutableSeedbedPolicy);
const plan = await local.plan(dockerInstallationRequest);
const receipt = await local.apply(plan);
await local.resume(plan.id);
const inspection = await local.inspect({ installationId, projectRoot });
const doctor = await local.diagnose({ installationId, projectRoot });The installable default uses createDefaultSeedbedControl({ projectRoot }).
Seedbed resolves its own installed package root and safe per-user external state
root, then returns the canonical opaque state selector in its plan. An explicitly
approved absolute external stateRoot may override the default; project-local,
worktree-local, symlink, junction, and path-alias selectors fail closed. Seedbed
alone reads and validates the fixed seedbed-component-lock.json; tests and
candidate staging may explicitly override artifactRoot.
defaultSeedbedControlPolicy() exposes localBuild, the exact
seedbed-local-build-v1 image selection copied into every control plan. It has
the fixed selector gnolith-seedbed-local-build-v1, pullPolicy: "never", and
SHA-256 attestations for the component lock, rendered graph, and deterministic
Compose bundle. Callers must pass that object unchanged as
DockerInstallationRequest.image. This selection trusts the pinned local
assembly and is never interpreted as an OCI image name. The separately typed
digest-qualified-pulled-image-v1 selection requires an
@sha256:<digest> reference, but application-image pulls remain unsupported by
the architecture-reset control plane.
seedbed init --config <path> --from <canonical-config-template>
seedbed plan --config <path> [--out <path>]
seedbed apply --config <path> --plan <path>
seedbed migrate plan --config <path> [--out <path>]
seedbed migrate apply --config <path> --plan <path>
seedbed doctor --config <path>
seedbed backup create --config <path> --out <path>
seedbed backup inspect --config <path> --archive <path>
seedbed backup verify --config <path> --archive <path>
seedbed backup restore --config <path> --archive <path> [--dry-run]
seedbed adopt legacy-local plan --config <path> --manifest <path> [--out <path>]
seedbed adopt legacy-local apply --config <path> --manifest <path> --plan <path>
seedbed uninstall --config <path> --retain-data
There is no native or stdio product mode. Remote Workshop installation and Codex configuration are Alembic responsibilities.
The default graph is one locally built Workshop service, an internal Compose network,
one application-data volume, and one Docker-owned read-only protected-credential
volume. Workshop
binds 0.0.0.0:4317 inside its container; Compose publishes only
127.0.0.1:<configured-port> by default. The explicit IPv6 override publishes
[::1]. The optional qdrant profile remains internal. The default waystone
profile mounts verified immutable browser assets at /app inside Workshop and
starts no second service. /, /mcp, and health routes remain Workshop-owned.
The build consumes exact tarball paths and lowercase SHA-256 values from the config. No Seedbed-owned application image is published.
For local-bearer-v1, apply creates a new protected user-state file outside the
project using exactly 32 OS-CSPRNG bytes encoded as canonical base64url without
padding and without a terminator. Readers accept exactly 43 encoded bytes, or those
same 43 bytes followed by exactly one LF. CRLF, empty values, additional newlines,
surrounding whitespace, and control bytes are rejected. The file is exclusively
created and owner-protected. Seedbed transfers the validated bytes only over a
bounded Docker stdin pipe into an owner-only Docker volume, then passes only the
in-container selector to gnolith-workshop bootstrap-v1. It never relaxes the
source file mode or places credential bytes in Compose, CLI arguments, or environment.
Receipts contain credential ID, a non-secret digest, the protected selector, and the
required environment selector GNOLITH_BEARER_TOKEN. They never contain the token.
Seedbed does not write Codex project configuration and reports clientVerified: false; Alembic performs the authenticated verification and selector-to-environment
handoff.
Seedbed binds a typed OpenAI-compatible provider and SQLite or Qdrant vector profile
into the digest-bound installation plan. Credential values are read only from the
Docker-owned protected-credential volume by the Workshop assembly adapter; receipts expose
only the configuration fingerprint, revision, and state. Configuration completes
after owner migrations and Workshop bootstrap but before the listener starts.
The provider endpoint is always the complete embeddings POST URL (for example,
http://127.0.0.1:8787/v1/embeddings), never an API base URL. Seedbed validates
and canonicalizes that URL once at the installation boundary, including the
Docker host-gateway hostname substitution described below, then passes the exact
result unchanged to the owner runtime. Neither Seedbed nor the owner appends a
path segment.
For a Qdrant profile, the generated graph waits for Qdrant health and
idempotently creates or validates the configured collection with the exact
dimensions and metric. After the full graph is stable, Seedbed repeats that
bounded check from the Workshop container's network view and asks Workshop's
public administration surface to reconnect before semantic planning.
When an installation requests semantic ready, Seedbed first asks Workshop's
public search administration surface to finish lexical catch-up, estimate one
bounded asap semantic plan, and explicitly approve that owner-created plan.
Seedbed records only the configuration ID, owner plan ID, and estimate digest in
a protected installation receipt so interrupted apply/resume reuses the same
approval and never creates a duplicate plan. Workshop—not Seedbed—owns the plan,
cost policy, execution, retries, materialization, and readiness transition.
External provider endpoints are denied by default. A loopback or single-label
Compose-private HTTP endpoint additionally requires explicit private-endpoint
approval and a protected credential selector; unsafe nonlocal/private targets,
redirects, and rebinding are rejected. Host loopback spellings (127.0.0.1,
localhost, and ::1) are canonicalized in the plan to
host.docker.internal, and the generated Workshop/operator services bind that
exact name to Docker's host gateway. The same mapping applies to an explicitly
configured loopback Qdrant endpoint. This keeps configuration fingerprints stable
while making the endpoint reachable from the container; secrets remain Compose
secret files and are never moved into the URL, plan receipt, or image.
Workshop uses restart: "no" in the generated Compose graph. Explicit
Seedbed repair and operator-requested restarts remain supported, but malformed
or unreachable semantic configuration cannot create an automatic container
restart loop. Compose container health uses authenticated /health/live;
Seedbed separately verifies authenticated /health/ready, which must return
200 for ready or unconfigured semantics and 503 with a bounded diagnostic only
when the immutable installation request explicitly permits degraded semantics.
Workshop reports semantic degradation while lexical search and the
authenticated service remain available.
The Ollama-compatible type is reserved, but local Ollama is deliberately unavailable in this candidate. Enabling it requires a separately supplied immutable model artifact selector and digest plus an offline verifier. Seedbed never performs a floating runtime model pull and does not report Ollama ready without that proof.
Owner backup sections and namespace-sliced Diamond ledger evidence are opaque to Seedbed. Packed dry-run and real restore first stage and build only the checksum-pinned one-shot operator. Dry-run validates every exact owner section in a networkless, read-only container with in-memory owner state and creates no target volume, project network, credential volume, or application service. Real restore validates before creating target storage, then passes those same opaque section values to the owner import APIs. Restore stages Taproot blob streams first, imports Diamond/Taproot/Workshop in order, and exposes the canonical database last. Dry-run reports retained, rebuildable, deferred, expired, and unsupported data.
legacy-local-v1 accepts only raw Seedbed 0.3.2 state or snapshot-v1 with the exact
Diamond 0.4.1 / Taproot 0.4.2 / Workshop 0.4.2 / Seedbed 0.3.2 tuple. It refuses
locked, changed, lossy, empty-target-from-nonempty-source, or nonempty-target
operations and leaves source state untouched.
Snapshot-v1 envelope verification belongs to Seedbed. Owner conversion belongs to
the checksum-pinned public Diamond, Taproot, and Workshop adapters running against a
read-only portable source capability; Seedbed never parses owner tables or fragment
payloads.
The package includes a frozen endpoint- and credential-free public legacy fixture
under fixtures/legacy-local-v1. Its evidence records exact npm tarball integrity,
intentional domain/revision/payload coverage, semantic configuration count zero,
and onboarding count zero with the explicit
unavailable-in-public-legacy-api disposition.
The public adopt legacy-local plan command stages and builds the checksum-pinned
one-shot owner adapter from the installed package when the target is fresh. It
does not start Workshop, create an application container, or modify the source.
See architecture reset, migration guide, and the example config.
npm ci --ignore-scripts
npm run check
npm run test:packed
npm run test:compose
npm run test:dockerAfter committing a clean candidate, npm run candidate regenerates the exact package,
Compose bundle, complete CycloneDX composition/dependency SBOM, rendered graph inventory, and lowercase SHA-256
checksums. It never tags, publishes npm, creates a Release, or pushes an image.
The consolidated Waystone boundary is frozen at protected-main commit
0c40be9753f2e4fd1b89b203c3aedf7742c23912 and tree
413e01179ee719c979485b68f32705c10794675c. Seedbed vendors its exact
0.3.0 archive together with the owner candidate, inventory, and CycloneDX
evidence under components/waystone. Candidate generation verifies those
offline inputs, the unchanged immutable asset manifest and CSS, the exact
Workshop coordinate, and includes every evidence digest in the graph, bundle,
complete SBOM, and handoff.
.github/workflows/registry.yml is the only npm publication path. A maintainer must
manually dispatch it from main with the exact merged commit, the independently
qualified package SHA-256, and the literal package/version confirmation. The job
checks out that exact merged commit, reruns the complete candidate matrix, verifies
the candidate ledger and release boundary, refuses an existing registry version,
and publishes publicly with npm trusted publishing/OIDC provenance. It has no npm
token, tag, GitHub Release, or container-image capability. Running npm run candidate locally remains non-publishing.
The npm package settings must name repository gnolith/seedbed, workflow
registry.yml, environment npm, and allow npm publish; the repository is
public so npm can attach provenance.