diff --git a/content/docs/deployment/publish-and-preview.mdx b/content/docs/deployment/publish-and-preview.mdx index 8d429898ac..baf7f8750b 100644 --- a/content/docs/deployment/publish-and-preview.mdx +++ b/content/docs/deployment/publish-and-preview.mdx @@ -1,19 +1,35 @@ --- title: Publish, Versioning & Preview -description: Compile metadata into an artifact, publish it as a versioned package to your Cloud catalog, optionally install it into an environment, and preview locally. +description: A metadata app is versioned in your catalog while the platform moves on its own release train. Compile the app into an artifact, then pick how it reaches a running platform — installed from the catalog, or pinned as the runtime's boot artifact. --- # Publish, Versioning & Preview -ObjectStack treats compiled metadata as an immutable artifact. The local loop is: +## Your app and the platform move on separate clocks + +A metadata app is **not part of the platform**. It compiles to its own immutable +artifact, carries its own version, and is released by you, when you decide. The +platform — the runtime image, the kernel, the plugins — is released on the +ObjectStack release train. Neither version implies the other, and neither has to +wait for the other. + +That independence is the point of the model, and it is also the question this +page answers: *if the app is a separate thing, how does it get **into** a running +platform?* + +The compile half is always the same: ```text -objectstack.config.ts -> os compile -> dist/objectstack.json -> os package publish -> Cloud package version (optionally installed into an environment) +objectstack.config.ts -> os compile -> dist/objectstack.json ``` -The framework CLI owns `compile` and `package publish`. The Cloud control plane -that stores package versions, installs them into environments, and serves them to -runtime nodes lives outside this framework repo. +The delivery half has **two** shapes, and picking the wrong one is the expensive +mistake. Both are described below, with the rule for choosing. + +The one place the two clocks touch is the artifact's declared `engines.protocol` +range: it states which platform majors the app can run on, and a runtime outside +that range **refuses** the app rather than half-loading it. Everything else about +the two versions is independent. The legacy direct-to-environment `os publish` / `os rollback` commands (which wrote @@ -26,6 +42,78 @@ Cloud control-plane concern. --- +## The two ways in + +| | **Catalog install** | **Artifact-pinned boot** | +|:---|:---|:---| +| The app arrives | *after* the platform is running | *at boot*, as an input to the process | +| Named by | package id + version (`com.acme.crm@1.2.0`) | a URL to the compiled artifact (`OS_ARTIFACT_URL`) | +| Who picks the version | whoever runs the install — an admin in the Console, CI calling the CLI, or the Cloud control plane | whoever sets the runtime's environment (your deploy pipeline) | +| Switching versions | install another version; no restart | change the variable, restart | +| Apps per runtime | many, side by side | one — it *is* this runtime's app | +| Integrity | the catalog is the trust boundary | optional `#sha256=` pin in the URL fragment; boot is refused on mismatch | +| Needs | a catalog the runtime can reach — or the artifact file, handed over inline | somewhere to host the artifact file | +| Detail lives in | this page | [Artifact-pinned boot](/docs/deployment/self-hosting#artifact-pinned-boot-os_artifact_url) | + +### The rule for choosing + +One question decides it: + +> **Is this app the reason the runtime exists?** +> +> **Yes** — pin the artifact. The deployment *defines* the app, so the app +> belongs in the deployment's own inputs. +> **No** — install from the catalog. The runtime is a platform that *receives* +> apps, so apps arrive through a surface that stays open while it runs. + +Three tie-breakers when both still look plausible: + +1. **Must "which bytes is this instance running?" have exactly one auditable + answer?** Pin the artifact. A `#sha256=` fragment makes the answer verifiable, + and a mismatch stops the boot instead of quietly serving something else. A + catalog install is a live mutation of a running platform — the right shape for + an app store, the wrong shape for a reproducible deployment. +2. **Must the app change without a restart, or must several apps share one + runtime?** Install from the catalog. The pinned artifact is resolved once, at + boot; there is no in-place upgrade of it, by design. +3. **Is there no catalog to reach (air-gapped)?** Either still works, so choose + on 1 and 2 rather than on connectivity: `os package install ./dist/objectstack.json` + hands the compiled artifact over inline with no catalog round-trip, and + `OS_ARTIFACT_URL=file:///srv/app/objectstack.json` pins a mounted file. + +### Using both at once + +They are not exclusive, and the combination is well-defined: + +- **A pinned runtime still accepts installs.** The install surface is mounted + independently of how the runtime obtained its own app, so a runtime booted from + `OS_ARTIFACT_URL` can still receive catalog installs — of **other** apps. +- **Two sources naming the same app is a refusal, not a merge.** An install whose + `manifest.id` is already registered by the runtime's own boot is rejected with + `409 MANIFEST_CONFLICT` ("already defined by this runtime's local code") rather + than overwriting it. Uninstall or unpin first; do not expect last-write-wins. +- **Artifact sources have a fixed precedence**, so a container that presets one + variable cannot shadow a deliberate override: + + ```text + --artifact > OS_ARTIFACT_URL > OS_ARTIFACT_PATH > /dist/objectstack.json + ``` + + `OS_ARTIFACT_URL` also outranks an `objectstack.config.ts` sitting in the + working directory — setting it is an instruction to boot one specific artifact, + not a hint. + + +**A catalog address is not an artifact URL.** `OS_ARTIFACT_URL` reads a *compiled +artifact* from wherever you host it — release storage, an object-store URL, a +mounted file. The catalog's manifest endpoints answer with an API envelope that +wraps the manifest, not with a bare artifact, so a catalog URL pasted into +`OS_ARTIFACT_URL` is not a supported reference. Publish to the catalog **and** +upload the artifact if you want both doors open. + + +--- + ## 1. Compile ```bash @@ -37,6 +125,9 @@ The artifact contains metadata, manifest requirements, and packaged function cod Deployment config stays outside the artifact: database URLs, secrets, runtime credentials, and environment identity are host inputs. +This one artifact feeds **both** delivery shapes — it is what `os package publish` +uploads, and it is what an artifact host serves to `OS_ARTIFACT_URL`. + --- ## 2. Publish a package version @@ -71,14 +162,37 @@ Common flags: In user mode the package is owned by your active organization; in service mode (bearer key) pass `--org`. See [Packages](/docs/plugins/packages) for the package model. +Publishing changes nothing that is running. It puts a version in the catalog; +step 3 is what moves an app. + --- -## 3. Install / update an environment +## 3. Install a version + +Two install targets, same catalog: + +**Into a Cloud environment** — either at publish time with `--env --install`, or +separately through the Cloud control plane / Marketplace. To "roll back," install +the prior version; there is no revision-activate CLI command anymore. + +**Into a running runtime you operate** — `os package install` calls that +runtime's local install endpoint, which registers the app into the live kernel +and caches the manifest on disk so the install survives a restart: + +```bash +# catalog mode: the target runtime fetches the version from its own catalog +os package install com.acme.crm --version 1.2.0 --runtime https://app.example.com + +# air-gapped mode: the artifact is read locally and sent inline, no catalog +os package install ./dist/objectstack.json +``` -An environment is updated by **installing a package version** into it — either at -publish time with `--env --install`, or separately via the Cloud control plane / -Marketplace. To "roll back," install the prior version; there is no -revision-activate CLI command anymore. +This authenticates against an account on the **target runtime**, not your cloud +login. Uninstall removes the cached manifest; the kernel needs a restart to fully +unload it, because app registration is additive. + +For the full command reference — every flag, every subcommand — see the +[CLI reference](/docs/deployment/cli). --- @@ -90,12 +204,20 @@ Use one of these shapes: |:---|:---| | Local artifact preview | `os dev --artifact ./dist/objectstack.json --ui` | | Production artifact host | `OS_ARTIFACT_PATH=./dist/objectstack.json os start` | +| Pinned, exactly as production runs it | `OS_ARTIFACT_URL="file://$PWD/dist/objectstack.json" os start` | | Cloud environment preview | Install to a preview environment, then route clients to it via `/api/v1/environments/:environmentId/...` or `X-Environment-Id`. | +The third shape is worth using before a pinned rollout: it exercises the same boot +path production will take, including the `engines.protocol` handshake and the +boot-time schema-drift policy, against a local file. + --- ## Related +- [Artifact-pinned boot](/docs/deployment/self-hosting#artifact-pinned-boot-os_artifact_url) — the operational detail for `OS_ARTIFACT_URL`: schemes, integrity pinning, cache behaviour, pre-signed URLs +- [CLI reference](/docs/deployment/cli) - [Packages](/docs/plugins/packages) - [Deployment Modes](/docs/deployment) +- [Environment Variables](/docs/deployment/environment-variables) - [Environment-Scoped Routing](/docs/api/environment-routing)