Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 133 additions & 11 deletions content/docs/deployment/publish-and-preview.mdx
Original file line numberDiff line numberDiff line change
@@ -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.

<Callout type="warning">
The legacy direct-to-environment `os publish` / `os rollback` commands (which wrote
Expand All@@ -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 > <cwd>/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.

<Callout type="warn">
**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.
</Callout>

---

## 1. Compile

```bash
Expand All@@ -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
Expand DownExpand Up@@ -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).

---

Expand All@@ -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)
Loading