Skip to content
Draft
Show file tree
Hide file tree
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
99 changes: 70 additions & 29 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ on:
required: false
type: string
default: ''
deploy_image_digest:
description: 'Promote an existing sha256 digest without rebuilding. Requires deploy_phala, matching image_version, and default dstack features.'
required: false
type: string
default: ''

env:
REGISTRY: ghcr.io
Expand All @@ -68,6 +73,10 @@ jobs:
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_IMAGE_VERSION: ${{ inputs.image_version }}
INPUT_DEPLOY_DIGEST: ${{ inputs.deploy_image_digest }}
INPUT_DEPLOY_PHALA: ${{ inputs.deploy_phala }}
INCLUDE_DUCKDB: ${{ inputs.include_duckdb }}
INCLUDE_TC_BENCH_V1: ${{ inputs.include_tc_bench_v1 }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
Expand Down Expand Up @@ -101,10 +110,19 @@ jobs:
if [ -n "${INPUT_IMAGE_VERSION}" ]; then
check_matches "image_version input" "${INPUT_IMAGE_VERSION}"
fi
if [ -n "${INPUT_DEPLOY_DIGEST}" ]; then
if [ "${EVENT_NAME}" != "workflow_dispatch" ] || [ "${INPUT_DEPLOY_PHALA}" != "true" ] || [ -z "${INPUT_IMAGE_VERSION}" ] \
|| [ "${INCLUDE_DUCKDB}" = "true" ] || [ "${INCLUDE_TC_BENCH_V1}" = "true" ] \
|| ! [[ "${INPUT_DEPLOY_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "::error::Digest promotion requires deploy_phala=true, matching image_version, default dstack features and a sha256 digest."
exit 1
fi
fi

build:
runs-on: ubuntu-latest
needs: [version-guard]
if: inputs.deploy_image_digest == ''
permissions:
contents: read
packages: write
Expand Down Expand Up @@ -166,7 +184,9 @@ jobs:
build-dstack:
runs-on: ubuntu-latest
needs: [version-guard]
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'push' && github.ref == 'refs/heads/main')
if: inputs.deploy_image_digest == '' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
outputs:
digest: ${{ steps.build.outputs.digest }}
permissions:
contents: read
packages: write
Expand Down Expand Up @@ -218,6 +238,7 @@ jobs:
type=raw,value=dstack${{ steps.build_features.outputs.image_suffix }},enable={{is_default_branch}}

- name: Build and push dstack Docker image
id: build
uses: docker/build-push-action@v5
with:
context: .
Expand All @@ -230,11 +251,26 @@ jobs:

deploy-phala:
runs-on: ubuntu-latest
needs: [build, build-dstack]
if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.deploy_phala)
needs: [version-guard, build, build-dstack]
if: >-
${{ !cancelled() && needs.version-guard.result == 'success' &&
(github.event_name == 'release' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
(github.event_name == 'workflow_dispatch' && inputs.deploy_phala)) &&
((inputs.deploy_image_digest != '' && needs.build.result == 'skipped' && needs.build-dstack.result == 'skipped') ||
(inputs.deploy_image_digest == '' && needs.build.result == 'success' && needs.build-dstack.result == 'success')) }}
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@v4

- name: Log in to GHCR for digest validation
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
Expand All @@ -248,34 +284,38 @@ jobs:
- name: Install Phala CLI
run: npm install -g phala@1.1.19

- name: Update compose with release tag
- name: Validate and pin deployment image
env:
INPUT_DEPLOY_DIGEST: ${{ inputs.deploy_image_digest }}
BUILD_DIGEST: ${{ needs.build-dstack.outputs.digest }}
CARGO_VERSION: ${{ needs.version-guard.outputs.cargo_version }}
INCLUDE_DUCKDB: ${{ inputs.include_duckdb }}
INCLUDE_TC_BENCH_V1: ${{ inputs.include_tc_bench_v1 }}
run: |
if [ "${{ github.event_name }}" = "release" ]; then
TAG="${{ github.event.release.tag_name }}"
else
TAG="${{ inputs.image_version }}"
if [ -z "${TAG}" ]; then
if [ "${GITHUB_REF_TYPE}" != "tag" ]; then
echo "::error::workflow_dispatch deploy_phala requires image_version unless the workflow is run from a tag"
exit 1
fi
TAG="${GITHUB_REF_NAME}"
fi
fi
# metadata-action emits {{version}} without the leading v (e.g. 1.3.0).
# Strip a leading v from the tag to match the pushed image tag.
VERSION="${TAG#v}"
DUCKDB_SUFFIX=""
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.include_duckdb }}" = "true" ]; then
DUCKDB_SUFFIX="-duckdb"
set -euo pipefail
DIGEST="${INPUT_DEPLOY_DIGEST:-${BUILD_DIGEST}}"
[[ "${DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "::error::Invalid deployment digest"; exit 1; }
export RESOLVED_IMAGE="${REGISTRY}/${IMAGE_NAME}@${DIGEST}"
docker pull "${RESOLVED_IMAGE}"
REVISION="$(docker image inspect "${RESOLVED_IMAGE}" --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}')"
VERSION="$(docker image inspect "${RESOLVED_IMAGE}" --format '{{ index .Config.Labels "org.opencontainers.image.version" }}')"
EXPECTED="${CARGO_VERSION}-dstack"
if [ "${INCLUDE_DUCKDB}" = "true" ]; then EXPECTED="${EXPECTED}-duckdb"; fi
if [ "${INCLUDE_TC_BENCH_V1}" = "true" ]; then EXPECTED="${EXPECTED}-tc-bench-v1"; fi
if [ "${REVISION}" != "$(git rev-parse HEAD)" ] || [ "${VERSION}" != "${EXPECTED}" ]; then
echo "::error::Image revision/version labels do not match the selected checkout and requested features"
exit 1
fi
# The prod CVM uses the dstack-suffixed image. Replace the floating
# ":dstack" tag in the checked-in compose with the versioned tag
# built by the build-dstack job (e.g. ":1.3.0-dstack" or
# ":1.3.0-dstack-duckdb").
sed -i "s|ghcr.io/tinycloudlabs/tinycloud-node:dstack|ghcr.io/tinycloudlabs/tinycloud-node:${VERSION}-dstack${DUCKDB_SUFFIX}|g" docker-compose.dstack-postgres.yaml
echo "Resolved image tag: ghcr.io/tinycloudlabs/tinycloud-node:${VERSION}-dstack${DUCKDB_SUFFIX}"
cat docker-compose.dstack-postgres.yaml
python3 - <<'PY'
import os
from pathlib import Path
path = Path('docker-compose.dstack-postgres.yaml')
content = path.read_text()
original = 'ghcr.io/tinycloudlabs/tinycloud-node:dstack'
assert content.count(original) == 1, 'Expected exactly one native image reference'
path.write_text(content.replace(original, os.environ['RESOLVED_IMAGE']))
PY
echo "Resolved image: ${RESOLVED_IMAGE} (revision ${REVISION}, version ${VERSION})"

- name: Deploy to Phala Cloud
env:
Expand Down Expand Up @@ -408,6 +448,7 @@ jobs:
/policy/v3/policies
/policy/v3/challenges
/policy/v3/delegations
/policy/v3/deliveries/authorize
)

# The direct TEE ingress obtains/loads its certificate after the
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/export-share-invitation-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@1.97.1

- name: Test the public descriptor exporter
run: cargo test -p tinycloud-node --bin export-share-invitation-descriptor
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ jobs:
fetch-depth: 0
token: ${{ secrets.RELEASE_PLZ_TOKEN }}

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install reviewed Rust toolchain
uses: dtolnay/rust-toolchain@1.97.1

- name: Run release-plz
id: release_pr
Expand Down Expand Up @@ -80,8 +80,8 @@ jobs:
fetch-depth: 0
token: ${{ secrets.RELEASE_PLZ_TOKEN }}

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install reviewed Rust toolchain
uses: dtolnay/rust-toolchain@1.97.1

- name: Run release-plz release
uses: release-plz/action@v0.5
Expand Down
30 changes: 26 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ jobs:
- name: Checkout TinyCloud repository
uses: actions/checkout@v4

- name: Install reviewed Rust toolchain
uses: dtolnay/rust-toolchain@1.97.1
with:
components: clippy, rustfmt

- name: Add the wasm32 target
run: rustup target add wasm32-unknown-unknown

Expand Down Expand Up @@ -65,6 +70,11 @@ jobs:
- name: Checkout TinyCloud repository
uses: actions/checkout@v4

- name: Install reviewed Rust toolchain
uses: dtolnay/rust-toolchain@1.97.1
with:
components: clippy, rustfmt

# TC-381: this job used to run `cargo test -p tinycloud-core postgres_`.
# libtest exits 0 when a name filter matches zero tests, and all four
# targets returned early when TINYCLOUD_TEST_POSTGRES_URL was unset — so
Expand Down Expand Up @@ -106,6 +116,11 @@ jobs:
- name: Checkout TinyCloud repository
uses: actions/checkout@v4

- name: Install reviewed Rust toolchain
uses: dtolnay/rust-toolchain@1.97.1
with:
components: clippy, rustfmt

- name: Add the wasm32 target
run: rustup target add wasm32-unknown-unknown

Expand All @@ -121,6 +136,11 @@ jobs:
- name: Checkout TinyCloud repository
uses: actions/checkout@v4

- name: Install reviewed Rust toolchain
uses: dtolnay/rust-toolchain@1.97.1
with:
components: clippy, rustfmt

- name: Fmt
run: cargo fmt --all -- --check

Expand All @@ -129,12 +149,14 @@ jobs:
steps:
- name: Checkout TinyCloud repository
uses: actions/checkout@v4
- name: Install reviewed Rust toolchain
uses: dtolnay/rust-toolchain@1.97.1
with:
components: clippy, rustfmt
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Ensure rustfmt is available
run: rustup component add rustfmt
- name: Verify generated capability artifacts match capabilities.json
run: node scripts/gen-capabilities.mjs --check
- name: Verify deployed Policy/v3 probe contract
Expand All @@ -151,9 +173,9 @@ jobs:
steps:
- name: Checkout TinyCloud repository
uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@1.97.1
with:
components: rustfmt
components: clippy, rustfmt
- uses: actions/setup-python@v5
with:
python-version: "3.12"
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [1.17.1] - 2026-09-15

- Integrate the reviewed native sharing correction into the TinyChat production lineage: exact-email delivery is authorized by embedded Policy v3, retries preserve strict request-body, JTI, and sender-DID replay binding, and recipient access remains scoped to the ordinary `/delegate` then `/invoke` storage-enforcer path. TinyChat meeting publication, legacy write guards, and digest-pinned deployment are unchanged (TC-500, #234).

## [1.17.0] - 2026-09-15

- Box large unauthorized resource payloads for current Rust Clippy checks; the two Rust error constructors now take `Box<Resource>`, with unchanged authorization decisions and error messages.
- Add the fixed TinyChat meeting publication v3 boundary: conditional reservation and publication, immutable digest-verified snapshots, retained aliases, and idempotent cleanup. Activation is explicit and fences legacy catalog writes; it does not automatically convert old records.
- Add a per-space pause for legacy meeting artifact writes with generation-checked freeze/release controls. The pause drains earlier KV commits, persists across restart, preserves ordinary chat and native snapshot publication, and remains releasable when content storage is full. This adds the central `meeting_legacy_write_guard` migration. Older binaries that do not recognize that migration cannot be used as a direct rollback; activated catalogs also require the compatible publication protocol.
- Preserve integral, fractional and null legacy REAL durations during meeting reservation and publication.
- Serialize SQLite graph transactions for invocation replay and SQL artifact persistence to avoid competing local writers.

## [1.16.0] - 2026-08-21

- Embed Policy v3 admission and control in the Node and move its routes off the Share namespace: `/share/v3/{policy/challenges,policy/delegations,policies,enforcer-bindings,deliveries/authorize,policy/status}` are now Node-owned `/policy/v3/{challenges,delegations,policies,enforcer-bindings,deliveries/authorize,status}`. Browser holder-bound exact-email credentials are admitted there, and the delegation the Node mints is then exercised over the ordinary `/delegate` and `/invoke` data plane, so no Share-specific data path remains on the Node (TC-500).
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ ARG RUNTIME_BASE=scratch
# Optional: pass "dstack", "duckdb", or "dstack duckdb" to enable build features.
ARG CARGO_FEATURES=""

FROM rust:alpine AS chef
FROM rust:1.97.1-alpine AS chef
RUN apk add --no-cache musl-dev pkgconfig openssl-dev openssl-libs-static g++ perl make
RUN test "$(rustc -V | awk '{print $2}')" = "1.97.1"
RUN cargo install cargo-chef
WORKDIR /app

FROM chef AS planner
COPY ./Cargo.lock ./
COPY ./Cargo.toml ./
COPY ./rust-toolchain.toml ./
COPY ./tinycloud-node-server/ ./tinycloud-node-server/
COPY ./tinycloud-auth/ ./tinycloud-auth/
COPY ./tinycloud-core/ ./tinycloud-core/
Expand All @@ -27,6 +29,7 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
ARG CARGO_FEATURES=""
COPY --from=planner /app/recipe.json recipe.json
COPY --from=planner /app/rust-toolchain.toml ./
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
if [ -n "$CARGO_FEATURES" ]; then \
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ With telemetry enabled, backend time-to-first-byte is recorded under the `server

## Quickstart

To run TinyCloud Protocol locally you will need the latest version of [rust](https://rustup.rs).
To run TinyCloud Protocol locally, install [Rustup](https://rustup.rs). The
repository selects the reviewed Rust `1.97.1` toolchain automatically through
`rust-toolchain.toml`; do not substitute a floating `stable` compiler.


You will need to create a directory for TinyCloud Protocol to store data in:
Expand Down
Loading
Loading