Shipping and operations
CI/CD, deployment, and seeds
Automate tenant delivery, promote query-selected data and backend assets, and preserve or overwrite target edits.
Use the GSB CLI for repeatable tenant operations and resource packs for promoting selected backend resources and data. Keep release content, target-specific approval, and deployment results separate.
See Backend source control with Git for committing schemas, workflows, functions, libraries, and templates alongside application code.
Available capabilities and boundaries
| Capability | Available surface |
|---|---|
| Resolve CI configuration | gsb config --json |
| Inspect local payloads against sync baselines | gsb status --json; not a remote content comparison |
| Read or mutate registered tool contracts | gsb call <tool> --json --input-file <path>; mutations require --yes |
| Upload changed checked-out assets | gsb push --all-changed, with --dry-run for a preview |
| Execute and follow a workflow | gsb run, including --json, --sync, and --attach |
| Batch supported entity operations | gsb bulk, with --raw for unwrapped JSON |
| Seed assistant configurations | gsb assistant seed <manifest> |
| Build/install a resource pack | Application Service APIs and the existing pack actions |
| Inspect asynchronous tasks | gsb task status <jobId> |
addResourcePack and installResourcePack are API operations, not currently
registered gsb call tools. Do not infer CLI availability from the API reference.
Use gsb tools to inspect the installed tool catalog. There is no general gsb seed
command. status → diff → plan → apply → verify is the accepted delivery direction,
not a fully shipped sequence. Local gsb status exists, but does not provide remote
three-way comparison. A push dry run is not an immutable approved plan.
pnpm gsb:sync refreshes managed repository files; it does not deploy tenant resources.
CI configuration and output
Pin @gsb-core/cli in the consuming repository's lockfile, install with
pnpm install --frozen-lockfile, and use Node.js 20 or newer. In the Core monorepo,
build the CLI and its workspace dependencies before invoking the binary. For parsed
output, use pnpm exec gsb, not a package script that prints its own banner.
Provide GSB_TOKEN, GSB_TENANT_CODE, and GSB_BASE_URL from the CI environment.
Store the token as an environment-scoped secret, never in source, command arguments,
browser-prefixed variables, or retained deployment artifacts. Do not run interactive
login in CI. Explicit environment credentials take precedence over saved credentials;
conflicting selectors or aliases fail. Avoid inheriting another tenant's variables.
pnpm exec gsb config --json
pnpm exec gsb call query --json --input-file release/verification-query.json
The request file must contain a reviewed query against a real definition in the
target tenant. config --json now reports resolved configuration and backend-verified
identity. gsb verify-token also exposes token verification. Neither a hostname nor
an unverified decoded JWT is sufficient authority for a mutation; resource permissions
must still be enforced by the backend for each operation.
Migrated config and call commands emit successful envelopes to stdout and errors
to stderr. call can also emit progress to stderr; do not parse the entire stderr
stream as one JSON object. Use the exit status, then inspect ok, data, and the
operation-specific result. An outer ok: true means the CLI invocation returned;
it does not prove an asynchronous job completed or an inner business result succeeded.
Do not print or retain unrestricted entity responses: token redaction is not PII
redaction. JSON flags and exit semantics are not yet uniform across legacy commands.
Automated delivery pipeline
Use these stages whether the runner is GitHub Actions, Azure Pipelines, or another CI:
- Validate without deployment credentials: lint, typecheck in no-emit mode, test, and review source changes. Untrusted pull requests must never receive tenant secrets.
- Select and build once: review the resource-pack template, explicit asset-family allowlist, and seed/query scope; build and retain the exact pack identity.
- Deploy to staging: apply the exact artifact through an authorized installer or an existing GSB deployment workflow; wait for terminal success and verify readback.
- Approve production: bind approval to the target, exact artifact, data scope, and skip/overwrite policy. Use protected environments and trusted branches.
- Promote without rebuilding: install the same pack into production, then verify definitions, references, workflows, functions, and selected data.
- Record the result: retain pack/file identity, source commit, selection policy, target, approval, job correlation, observed terminal state, and verification evidence. Restrict artifact access and retention; omit credentials and raw customer data.
A source commit alone does not identify pack content: packing reads the selected live source tenant. Verify that source resources match the reviewed commit and that the query-selected rows are the approved set. Record content hashes where available; version text alone is not integrity or approval evidence.
Serialize delivery per target. Do not cancel an in-flight production mutation when a newer commit arrives. CI concurrency protects that pipeline only, not writes from other clients. Strong remote preconditions and cross-client locks need backend support.
GitHub Actions job example
This example automatically runs a pre-existing, reviewed deployment workflow on
a trusted main push. It is a consuming-project example, not a workflow installed by
the CLI. Configure the staging environment with its three GSB variables/secrets and
GSB_DEPLOY_WORKFLOW_ID, and ensure the lockfile already pins the published CLI.
The GSB workflow must own the real deployment contract and any idempotency checks;
this example does not create an installer or build/select a resource pack.
name: GSB staging delivery
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: gsb-staging-delivery
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
environment: staging
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9.15.0
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Run the approved tenant workflow
env:
GSB_TOKEN: ${{ secrets.GSB_TOKEN }}
GSB_TENANT_CODE: ${{ vars.GSB_TENANT_CODE }}
GSB_BASE_URL: ${{ vars.GSB_BASE_URL }}
GSB_DEPLOY_WORKFLOW_ID: ${{ vars.GSB_DEPLOY_WORKFLOW_ID }}
run: |
test -n "$GSB_TOKEN"
test -n "$GSB_TENANT_CODE"
test -n "$GSB_BASE_URL"
test -n "$GSB_DEPLOY_WORKFLOW_ID"
pnpm exec gsb config --json
pnpm exec gsb run "$GSB_DEPLOY_WORKFLOW_ID" --sync --json --timeout 900 > "$RUNNER_TEMP/gsb-run.json"
Gate this job on your project's validation jobs. Pin third-party actions to reviewed
commit SHAs according to repository policy. Environment secrets are limited to the
deployment step, not dependency installation. Keep workflow output private; inspect
its documented result and execute target-specific post-deployment queries before
declaring the release healthy. Configure a separate protected production job with
reviewers and the retained exact pack identity; do not rebuild from dev in that job.
Never enable privileged deployment using untrusted checkout code or pull_request_target.
gsb run exits nonzero for observed Error/Cancelled states, but a timeout, waiting
human task, or nonterminal result is not success. A timeout does not roll back or
necessarily cancel server work. Inspect the existing instance with --attach or the
existing task before retrying; restarting a workflow can duplicate side effects.
Resource packs: schema, logic, and data
| Layer | Selection responsibility |
|---|---|
| Tenant checkout | Editable source; never copy credentials or another tenant's sync baseline |
GsbResourcePackTmpl |
modules, apps, dataDefinitions, and dataQueries define scope |
| Build request | moduleFields selects module-owned asset families at build time |
Built GsbResourcePack |
Exact artifact selected by packId for staging and production |
| Target review/plan | Conflicts, dependencies, data impact, and installation policy |
Build through POST /api/app/addResourcePack using an existing authorized template:
{
"note": "Approved catalog schema, logic, and reference-data release",
"version": "1.2.0",
"templateId": "reviewed-template-id",
"moduleFields": [
"entityDefs",
"propertyDefs",
"enums",
"workflows",
"functions",
"codeLibraries",
"docTemplates",
"permissions",
"roles"
]
}
moduleFields is a request option, not a template property. Omission includes every
supported module family; [] includes none. A data-only template can select data
without selecting module assets, provided the target already has the required schema.
Always make module selection explicit. Add related libraries, enums, permissions,
and other dependencies deliberately; do not assume omitted resources will appear.
Select only part of the source data
Resource packs are not limited to schema and code. Templates can select data by
definition using dataDefinitions, or select a subset through saved dataQueries.
For example, promote only reviewed catalog items in one release batch rather than
every catalog record. Configure the saved query in the source tenant and associate
its reference with the template; do not add an invented inline query field to the
build request.
Use a query with explicit release predicates, such as an approved flag and a fixed release-batch key, against fields that actually exist in your schema. Preview it under the builder's permissions, verify the complete row set and dependencies, and capture counts and permitted-field expectations before packing. Avoid time-relative filters and uncontrolled pagination. The backend determines query serialization and related-row inclusion; verify built content instead of assuming a join exports all referenced rows or that a UI column projection excludes sensitive fields from a pack.
- Do not also select the same whole definition through
dataDefinitionswhen the release must contain only query-selected rows; broader selection can defeat the filter. moduleFields: ["userQueries"]ships saved-query resources. TemplatedataQueriesselects data using queries. These are different responsibilities.- Canonical starter editions deliberately restrict seeds and keep
dataQueriesempty. That policy does not prohibit selective queries in other resource-pack templates. - Exclude credentials, sessions, PII, development users, test orders, and unrelated operational data. Sensitive seed artifacts require restricted storage and review.
- Resolve dependencies and identities in the target. Do not blindly copy source IDs, regenerate every ID, or assume a folder copy provides cross-tenant identity mapping.
- Verify selected rows arrived and nonselected rows were not changed. Installation is not continuous replication; absence from a pack is not an instruction to delete.
Preserve user edits or overwrite
Install through POST /api/app/installResourcePack with an explicit policy:
{
"packId": "approved-built-pack-id",
"sourceTenant": "dev1",
"tenantCode": "production-tenant",
"runAsync": true,
"skipUserModified": true
}
| User choice | skipUserModified |
Effect |
|---|---|---|
| Preserve target customizations | true |
Ask the installer to skip resources it recognizes as user-modified |
| Overwrite target customizations | false |
Allow the installer to apply packaged changes despite target edits |
The UI's Overwrite My Changes choice is the inverse of skipUserModified.
Use preserve as the normal update policy and require explicit approval for overwrite.
This policy matters for schema definitions, columns/properties, workflows, functions,
and other packaged resources; exact family and nested-property behavior is governed
by the backend installer. The request exposes one installation-level boolean, not
per-column switches. Verify preservation/overwrite with edited schema and property
fixtures in an isolated tenant before depending on that granularity in production.
Overwrite is not permission to delete absent columns or rows and does not imply a transaction, full replacement, or universal rollback. Preserve can leave a target different from the pack, so report skips and verify dependencies rather than claiming an exact match. Do not automatically rerun a preserve installation with overwrite when verification finds a conflict. Review a separate installation and its scope.
Select exactly one of packId, templateId, or templateName. Template selectors
resolve the latest pack and are unsuitable for promoting a previously approved build.
Both source and target access must be authorized by GSB; request fields are not authority.
Pack metadata alone does not provide an immutable hash/approval ledger.
For asynchronous installation, retain jobId and poll the owning tenant's
POST /api/task/getJobStatus with bounded backoff. Succeeded permits readback
verification; Failed or Deleted fails delivery. On timeout, inspect the same job
instead of submitting a duplicate installation. Task acceptance is not completion.
Seed systems and data migrations
| Need | Recommended mechanism |
|---|---|
| Shared languages, currencies, reference catalogs | Versioned resource pack with explicit definition/query data selection |
| A reviewed subset of business configuration | Query-selected data pack with target readback |
| A small reviewed set of entity writes | gsb bulk save through the existing Entity Service contract |
| Assistant configuration | gsb assistant seed <manifest>; keep model/provider credentials separate |
| Backfill or transformation of existing target data | Explicit reviewed GSB workflow/function, not an inferred pack merge |
pnpm exec gsb bulk save --input-file release/reference-data.json --chunk-size 100 --stop-on-error --yes --raw
pnpm exec gsb assistant seed release/assistant-manifest.json
Supply real reviewed files before running these examples. Bulk's input is an array
of tool inputs, for example { "request": { "entDefName": "YourDefinition", "entity": { ... } } } per item, not simply an array of entity rows. It returns
per-item results and a nonzero exit when a call fails. --stop-on-error stops after
a failing chunk; it does not undo earlier writes or stop other calls already submitted.
Seeds must be repeatable: use backend-supported stable identities or a unique business-key lookup that resolves the target ID before saving. Saving rows without identity can create duplicates. Do not assume a natural-key upsert, pack install, or workflow run is idempotent unless the backend contract proves it. Avoid blanket delete-and-reseed in production. Test a second application and verify that counts, identities, relationships, and user customizations remain correct.
Apply prerequisite schemas before data, preserve required references, and sequence data transformations explicitly. A type conversion or required-field addition may need a backfill before enforcement. Record migration completion and recovery evidence; choose forward repair or an explicitly approved entity-version/backup restore as appropriate. Full tenant backups are not a routine per-deployment action.
Release acceptance checklist
- Exact artifact, source selection, target, and overwrite policy were reviewed.
- Schema/code and the query-selected data match the approved source state.
- Target identities and dependencies resolve without ambiguous matching.
- Edited schemas/properties and nonselected rows behave as the chosen policy requires.
- Build/install jobs reached terminal success and target readback passed.
- Reapplication and partial-failure recovery were exercised in an isolated tenant.
- The release record excludes credentials and retains only permitted evidence.
CLI-managed resources use immutable plans, per-resource lastUpdateDate claims,
partial-application reports, and read-back verification. Resource-pack installation
has separate backend behavior and must not inherit those guarantees by assumption.
Tenant-wide deployment locks and backend migration ledgers still require real backend
contracts; neither a CI job nor a client-side plan can manufacture them.
docs/guides/cli-delivery-and-resource-packs.md