Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c8aedf9
The activity log filter covers the days it names, so 截止日期 2026-10-24 …
rudycelekli Oct 25, 2026
6431a5a
Prevent GitHub and Jira sync checkpoints from skipping unread tickets…
Floating-Y Oct 26, 2026
fb0caed
A deployment that refuses reasoning_effort none on tool requests too …
QuintinBotes Oct 26, 2026
6466a5e
A search may name a source and an external_id prefix, and both channe…
gedaiu Oct 26, 2026
5e5b30a
Saving one knowledge base settings card keeps unsaved edits in the ot…
Floating-Y Oct 26, 2026
475bb67
A model card's Test button tests only that card (#1447)
0xvectr Oct 27, 2026
04376a2
A restore checks its targets and --force before touching the database…
Floating-Y Oct 27, 2026
52fb5a6
A quantity read into a larger declared unit keeps its digits, so 350万…
rudycelekli Oct 27, 2026
3fc15f7
A Snowflake date or timestamp reads as a date, so 18262 is 2020-01-01…
rudycelekli Oct 27, 2026
8630da5
A decimal longer than a double keeps every digit, so 1234567890123456…
rudycelekli Oct 27, 2026
1142792
A Trino decimal reads as a number, so SUM(amount) is 1234.5 instead o…
rudycelekli Oct 27, 2026
8dab158
The source API says where a ticket source's reading stopped, so an op…
rollroyces Oct 29, 2026
ffb78b2
The test job runs on Ubuntu 24.04, so installing postgresql-client-16…
WaylandYang Oct 29, 2026
e2158c9
A Jira Cloud site lists issues through search/jql, so a sync is not s…
torrresagus Oct 29, 2026
7baecc9
A just-indexed chunk stays searchable, because a late automatic reloa…
Aschenbath Oct 29, 2026
e4b8a4e
The embedding ceiling test measures overlap rather than database writ…
Aschenbath Oct 29, 2026
3b09f9f
Merges involving names declared as identities wait for human review (…
Aschenbath Oct 29, 2026
64acec9
Dropping an identity document's name vectors reads only its evidence …
Aschenbath Oct 29, 2026
266456a
A human phrase decision materializes ahead of queued model work (#1358)
Aschenbath Oct 29, 2026
beb65fc
Govern conflicts in the typed graph and preserve human decisions (#1131)
Aschenbath Oct 29, 2026
7d699b7
The docs say Jira Cloud pages by token without a total, and the Jira …
WaylandYang Oct 29, 2026
1d94b53
Namesake decisions are measured along their adjudication paths, with …
virtue039 Oct 29, 2026
c34dd04
The overview deadline rides the open fast window, and the coalescer's…
WaylandYang Oct 30, 2026
fbe13bd
A merge keeps the identity evidence of the name it dedupes (#1466)
Aschenbath Oct 30, 2026
d026704
The ontology agent writes labels and definitions in the base's ontolo…
Schro-code Nov 4, 2026
c76e65a
A write token can rename, delete, re-extract and sweep the push sourc…
gedaiu Nov 4, 2026
f879b45
A rewritten open statement stays an open statement, with its phrase a…
Aschenbath Nov 4, 2026
01fb851
Sync upstream dev into re-dev (2026-11-05): #1427, #1131, #1469 and 2…
gedaiu Nov 5, 2026
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
10 changes: 7 additions & 3 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ jobs:
# migrations 再编一遍才真的跑。给这个 job 也起一个 Postgres,一次编译全部跑完,
# migrations job 只留迁移本身的检查。连库测试由此进了 backend 汇总,才真正成为合并门槛
test:
runs-on: ubuntu-latest
# 钉在 24.04:ubuntu-latest 换到 26.04 后 apt 里没有 postgresql-client-16,
# 装测试工具这一步直接失败,dev 与所有 PR 一起红(2026-10-29)
runs-on: ubuntu-24.04
services:
postgres:
image: pgvector/pgvector:pg16
Expand All @@ -66,8 +68,10 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.event_name == 'push' }}
- name: Install PDF text extraction tools
run: sudo apt-get update && sudo apt-get install -y poppler-utils poppler-data
- name: Install PDF and PostgreSQL test tools
run: |
sudo apt-get update && sudo apt-get install -y poppler-utils poppler-data postgresql-client-16
echo '/usr/lib/postgresql/16/bin' >> "$GITHUB_PATH"
# 库要先迁移好:store 的一百多个集成测试和 server 的多数 fixture 不自己跑迁移,
# 对着空库并发起几百个测试会成片撞 relation "organizations" does not exist(#869)。
# 不用 sqlx-cli——装它要一分多钟,而这个 job 在关键路径上;这个 example 复用
Expand Down
90 changes: 57 additions & 33 deletions .roadmap-proposals/backup-restore.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,40 @@ $ utopia restore --from <PATH> [flags]
--from <PATH> # archive to restore from (required)
--target-data-dir <PATH> # override the data_dir path
--pg-restore <PATH> # path to pg_restore binary
--dry-run # print the plan, don't write anything
--force # allow restore into a non-empty database
--dry-run # validate and print the plan; don't change targets
--force # allow overwriting a non-empty database or data dir
--yes # skip the "are you sure?" prompt
```

Before changing either target, restore checks required archive components,
checksums, version, target path types and overwrite permission. Declared data
must exist in the archive. `--force` is required for a database containing user
objects (even empty tables, schemas, functions or types), and for a non-empty
data directory when the archive includes data. `--yes` only skips confirmation.
`--dry-run` uses the same preflight, including a read-only database check; it
extracts temporary staging files in the current working directory but never
restores the database or writes the target data directory. After confirmation
and data preparation, restore repeats component, target-path and overwrite
checks immediately before running `pg_restore`.

Database-only archives leave the data directory untouched. Otherwise, data is
prepared separately and replaces the old directory after `pg_restore` succeeds;
files and keys are never merged with the old directory. A database restore
failure preserves the old files. With `--force`, `pg_restore --clean --if-exists`
replaces objects in the archive without removing unrelated database objects.
Every restore uses `--single-transaction --exit-on-error`: a SQL execution error
rolls back the database restore instead of leaving partial SQL changes.

Stop all writers and retain a backup of both targets before restoring. Preflight
does not prevent concurrent changes or later permission and disk failures.
Database and filesystem restoration are **not atomic**: the database may be
committed before a later directory installation fails. Failed installation
attempts to put the old directory back; a failed rollback reports its retained
location. After an interruption, also check the target's parent for
`.utopia-restore-data-*/previous`. Keep the service stopped, resolve the error,
and retry the archive with `--force`, or restore both targets from the previous
backup. Moving old files back alone does not undo database changes.

Both halves are implemented. `backup` landed first, and `restore`
followed in the same crate, for two reasons worth keeping on the record:

Expand Down Expand Up @@ -150,50 +179,45 @@ binary first, the image second.

- No docker image change (see Open question #3).
- No `UTOPIA_BACKUP_DIR` config (see Open question #5).
- No automatic migration of older manifests — `restore` will only
read `schema_version == current`.
- No automatic migration of older manifests — older versions are accepted
with a warning; newer versions are refused.
- **The sealing key is left out of the archive by default.** The dump carries
credentials sealed with `data/secret.key`, and an archive is the artifact that
gets copied between hosts and handed to whoever runs the restore, so shipping
both halves in one file is not an unencrypted archive — it is no sealing at
all. `--include-secret-key` puts it in on purpose, the manifest records
whether it is there, and a restore without it says so rather than letting the
server fail to open its own credentials later.
all. `--include-secret-key` opts in to copying an existing key file. The
manifest records that flag even when an environment-key deployment has no
key file; restore remains compatible with those archives. When replacing
data, an absent key is never taken from the old directory: restore warns to
set `UTOPIA_SECRET_KEY` to the original key, or re-enter sealed credentials
after starting the server.
- No encryption-at-rest. Out of scope; the operator's filesystem
encryption (LUKS, EBS encryption, etc.) is the right layer.
- No streaming upload to S3. Out of scope; the operator can pipe
`utopia backup --output -` to `aws s3 cp - s3://…` today.

## Test plan

8 unit tests, all pure (no live DB, no Docker):

1. `parses_backup_minimal` — `utopia backup --dry-run` produces a
`BackupArgs` with all defaults.
2. `parses_backup_full` — every flag set, every field populated.
3. `parses_restore_requires_from` — `--from` is mandatory.
4. `parses_restore_full` — every flag set, every field populated.
5. `rejects_unknown_subcommand` — `utopia frobnicate` fails clearly.
6. `redact_url_host_keeps_userinfo_at_host` — `postgres://u:***@h/db`
stays redacted on round-trip.
7. `redact_url_host_handles_no_at` — `postgres://h/db` redacts
nothing (no userinfo).
8. `hex_encode_known_value` — `hex_encode(&[0xde, 0xad]) == "dead"`.

Live integration (smoke test, not part of CI):

```
$ createdb utopia_test_restore
$ UTOPIA_DATABASE_URL=postgres://utopia:utopia@localhost:1543/utopia_test \
utopia backup --output /tmp/utopia.tar.gz --include-data-dir
$ dropdb utopia_test_restore && createdb utopia_test_restore
$ UTOPIA_DATABASE_URL=postgres://utopia:utopia@localhost:1543/utopia_test \
utopia restore --from /tmp/utopia.tar.gz --yes
Unit tests cover parsing, manifests, versions, checksums and key-copy policy.
The CLI integration suite runs in `cargo test --workspace`: a recording
`pg_restore` double checks preflight ordering, post-confirmation validation,
overwrite guards, replacement, rollback, dry runs and database-only archives.
Real `pg_dump`/`pg_restore` tests exercise database restoration and SQL rollback
using disposable test databases.
CI provides PostgreSQL 16 clients; the double also needs `rustc`, and
archive operations need `tar`.

Tests use `utopia_store::test_db::url()` and create uniquely named databases on
that test cluster; they never modify the guard database's contents. Use only a
dedicated test cluster with `CREATEDB` and catalog-fixture privileges, never a
business database. Without a URL, tests skip unless `UTOPIA_TEST_REQUIRE_DB=1`
requires them to run, as in CI.

```bash
UTOPIA_DATABASE_URL=postgres://test_role:test_password@localhost:1543/test_admin \
UTOPIA_TEST_REQUIRE_DB=1 cargo test -p utopia-cli
```

Smoke-tested manually on the maintainer's local docker-compose stack
once the restore side lands.

## Migration concern: existing data is already there

Backups are forward-looking. Anything written before this lands has
Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions crates/utopia-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ tracing.workspace = true
tracing-subscriber.workspace = true
sha2.workspace = true
dotenvy.workspace = true
sqlx.workspace = true
tokio.workspace = true
tempfile.workspace = true

[dev-dependencies]
utopia-store.workspace = true
Loading
Loading