Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bf04344
feat(storage): provision dedicated seekdb and oceanbase databases
Lkx-JY Oct 16, 2026
5e76047
fix(evaluation): select the OceanBase database only in the URL path
Lkx-JY Oct 16, 2026
b719edf
fix(storage): prevent read-only migration commands from creating data…
Lkx-JY Oct 17, 2026
cfd823f
fix(storage): preserve seekdb paths during read-only inspection
Lkx-JY Oct 17, 2026
2fef5fa
fix(storage): preserve percent signs in database provisioning guidance
Lkx-JY Oct 18, 2026
b0e379f
fix(storage): preserve legacy seekdb migration errors
Lkx-JY Oct 19, 2026
bf34f3e
fix(seekdb): bound legacy migration CLI acceptance
Lkx-JY Oct 20, 2026
0cab734
fix(ci): preserve migration acceptance and align Kimi setup
Lkx-JY Oct 20, 2026
b9760f6
fix(storage): preserve database selection and resolve master conflicts
Lkx-JY Oct 20, 2026
5310b14
fix(cli): report database refusals and provisioning failures
Lkx-JY Oct 21, 2026
405a45c
merge: resolve master conflicts in CLI error contracts
Lkx-JY Oct 21, 2026
d8da2bc
fix(storage): reuse identity tables and align seekdb fixtures
Lkx-JY Oct 22, 2026
3582000
merge: update this branch with master
Lkx-JY Oct 22, 2026
e5d882b
test(seekdb): reduce startup DDL exposure and retry once past the eng…
Lkx-JY Oct 22, 2026
d87e61f
merge: update this branch with master
Lkx-JY Oct 24, 2026
d847f91
fix(desktop): gate current contract without qualification
Lkx-JY Oct 24, 2026
5a084b5
merge: sync master and address bootstrap review
Lkx-JY Oct 25, 2026
69c0239
merge: sync master and resolve database conflicts
Lkx-JY Oct 27, 2026
b54ccf5
fix(storage): sort only selected MySQL tables
Lkx-JY Oct 27, 2026
475b723
chore(release): review persistence compatibility
Lkx-JY Oct 27, 2026
830eff2
merge: sync master and repair database acceptance
hidb4ai Oct 29, 2026
eff0ceb
perf(storage): avoid copying whole migration inventories
hidb4ai Oct 29, 2026
dff2aa1
test(runtime): preserve terminal-cache progress checks under slow I/O
hidb4ai Oct 29, 2026
cfec315
merge: sync latest master
hidb4ai Oct 29, 2026
6744505
fix(storage): distinguish and provision selected seekdb databases
Lkx-JY Oct 29, 2026
f203fbf
fix(migrations): plan missing seekdb databases before apply
Lkx-JY Oct 30, 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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,16 @@ POWERCONTEXT_SERVER_DATABASE_KIND=sqlite
# Do not set the removed POWERCONTEXT_SERVER_DATABASE_VEC1_EXTENSION variable.

# OceanBase alternative: replace the SQLite kind with these values.
# Omit the URL database path to create/use powercontext; an explicit name is always honored.
# POWERCONTEXT_SERVER_DATABASE_KIND=oceanbase
# POWERCONTEXT_SERVER_DATABASE_URL=mysql+aoceanbase://user:password@host:2881/powercontext?charset=utf8mb4

# Embedded seekdb alternative: install powercontext[cli,server,seekdb], then replace the SQLite kind.
# Leave DATABASE_PATH unset to use the seekdb subdirectory below POWERCONTEXT_HOME.
# POWERCONTEXT_SERVER_DATABASE_KIND=seekdb
# POWERCONTEXT_SERVER_DATABASE_PATH=/absolute/path/to/seekdb
# The default database is powercontext; set test before upgrading an existing seekdb deployment.
# POWERCONTEXT_SERVER_DATABASE_DATABASE=test

# Runtime ---------------------------------------------------------------------
POWERCONTEXT_SERVER_RUNTIME_SOURCE_WINDOW_LIMIT=100
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/e2e-harness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,25 @@ jobs:
uv lock --check
uv run --locked prek run -a

- name: Run real OceanBase database bootstrap without skips
timeout-minutes: 5
env:
POWERCONTEXT_TEST_BOOTSTRAP_OCEANBASE_URL: ${{ env.POWERCONTEXT_TEST_OCEANBASE_URL }}
POWERCONTEXT_TEST_BOOTSTRAP_OCEANBASE_DEFAULT: "1"
POWERCONTEXT_TEST_BOOTSTRAP_PRIVILEGES: "1"
run: |
mkdir -p .powercontext/recall-effort-ci
uv run --locked python -m pytest tests/e2e/test_database_bootstrap.py -k oceanbase -q -rs \
--junitxml=.powercontext/recall-effort-ci/bootstrap.xml
uv run --locked python - <<'PYCODE'
from xml.etree import ElementTree

suites = ElementTree.parse(".powercontext/recall-effort-ci/bootstrap.xml").getroot()
cases = list(suites.iter("testcase"))
if not cases or any(case.find("skipped") is not None for case in cases):
raise SystemExit("Real OceanBase bootstrap contracts must execute; skipped tests are not acceptance.")
PYCODE

- name: Run OceanBase contracts and Jev integration without skips
run: |
mkdir -p .powercontext/recall-effort-ci
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -436,13 +436,39 @@ jobs:
persist-credentials: false

- name: Set up the environment
id: seekdb_setup
uses: ./.github/actions/setup-python-env
with:
python-version: ${{ matrix.python-version }}

- name: Test code indexing with embedded seekdb
run: make code-seekdb-test

- name: Test database bootstrap with embedded seekdb without skips
# Two 360s native-process deadlines still leave room for the remaining
# bootstrap cases and diagnostic upload before the step deadline.
timeout-minutes: 20
env:
POWERCONTEXT_TEST_BOOTSTRAP_SEEKDB: "1"
POWERCONTEXT_TEST_BOOTSTRAP_DIAGNOSTICS: ${{ github.workspace }}/.powercontext/database-bootstrap-ci
PYTHONUNBUFFERED: "1"
run: |
set -o pipefail
mkdir -p .powercontext/database-bootstrap-ci
uv run --locked --extra seekdb --extra code python -m pytest \
tests/e2e/test_database_bootstrap.py -k seekdb -vv -rs \
-o faulthandler_timeout=60 --durations=20 \
--junitxml=.powercontext/database-bootstrap-ci/seekdb.xml \
2>&1 | tee .powercontext/database-bootstrap-ci/pytest.log
uv run --locked --extra seekdb --extra code python - <<'PYCODE'
from xml.etree import ElementTree

suites = ElementTree.parse(".powercontext/database-bootstrap-ci/seekdb.xml").getroot()
cases = list(suites.iter("testcase"))
if not cases or any(case.find("skipped") is not None for case in cases):
raise SystemExit("Real seekDB bootstrap contracts must execute; skipped tests are not acceptance.")
PYCODE

- name: Test batched archive validation with embedded seekdb
timeout-minutes: 5
run: |
Expand All @@ -451,6 +477,7 @@ jobs:
'tests/e2e/test_portable_archive.py::test_archive_batches_validation_and_rechecks_after_dry_run[seekdb]'

- name: Test unified migrations with embedded seekdb
if: ${{ !cancelled() && steps.seekdb_setup.outcome == 'success' }}
# Full-Server cases run in separate processes and exceed fifteen minutes
# in aggregate; each runner keeps its 180-second deadline below.
timeout-minutes: 25
Expand Down Expand Up @@ -508,6 +535,14 @@ jobs:
2>&1 | tee -a seekdb-migrations.log
done

- name: Upload seekdb bootstrap diagnostics
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: seekdb-bootstrap-${{ matrix.python-version }}
path: .powercontext/database-bootstrap-ci/
if-no-files-found: ignore

- name: Upload seekdb migration diagnostics
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
Expand Down
4 changes: 3 additions & 1 deletion desktop/VALIDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ The real Server harness covers SQLite with anonymous loopback, static Bearer, HT

Selecting a compatibility profile does not attest the remote binary identity. Existing `sqlite-1.1.1-v1` / `sqlite-63f918b7-v1` / `sqlite-ab43e3a7-v1` / `sqlite-f1089f4e-v1` / `sqlite-6e237568-v1` / `sqlite-legacy-58f7f4f6-v1` selections require explicitly choosing an available matching profile and rechecking the connection. Desktop reads memories only through Atomic Memory references, so Servers that answer only with Memory entry citations are not qualified. CLI diagnostics separately verify the registered executable path and digest, then validate the diagnostic response schema. The observed version is optional report information; a registration does not require a version.

### Current Server protocol qualification
<a id="current-server-protocol-qualification"></a>

### Server protocol qualification records

#### Server 4ffdf4e9 protocol qualification

Expand Down
64 changes: 58 additions & 6 deletions desktop/src-tauri/tests/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,53 @@ async fn editing_a_scope_query_cancels_its_read_without_disconnecting() {
server.release.notify_one();
}

#[tokio::test]
async fn historical_qualification_cannot_authorize_the_current_contract() {
let server = fixture().await;
let dir = tempfile::tempdir().unwrap();
let manager = ConnectionManager::new(
ProfileRepository::open(dir.path().join("profiles.json"), Arc::new(NativeVault)).unwrap(),
);
let contract: serde_json::Value =
serde_json::from_str(include_str!("../src/transport/operations.json")).unwrap();
let current_contract = contract["contractSha256"]
.as_str()
.expect("the bundled contract needs a SHA-256 digest");
let state = manager.state().unwrap();
let historical = state
.compatibility_profiles
.iter()
.find(|profile| profile.contract_sha256 != current_contract)
.expect("the compatibility catalog needs a historical contract for this test");
assert_ne!(
Some(historical.contract_sha256.as_str()),
Some(current_contract)
);
let state = manager
.save_profile(
serde_json::from_value(serde_json::json!({
"id":null,"revision":null,"name":"Historical Server","endpoint":server.endpoint,
"authentication":"unauthenticated_loopback","caPem":null,
"compatibility":historical.id,"keepCredential":false,"credential":null
}))
.unwrap(),
)
.unwrap();
let state = manager.check(&state.profiles[0].id, true).await.unwrap();
let active = state.active.unwrap();
assert!(!active.report.compatibility_verified);
assert!(active.report.supported_operations.is_empty());
assert_eq!(
manager
.scopes(active.generation, "", None)
.await
.err()
.unwrap()
.code,
SafeError::CompatibilityUnverified
);
}

#[tokio::test]
async fn bundled_qualification_allows_explicit_scope_browsing() {
let server = fixture().await;
Expand All @@ -304,12 +351,17 @@ async fn bundled_qualification_allows_explicit_scope_browsing() {
.find(|profile| {
Some(profile.contract_sha256.as_str()) == contract["contractSha256"].as_str()
})
.expect("the shipped preview needs a qualified Server for its current contract");
let state = manager.save_profile(serde_json::from_value(serde_json::json!({
"id":null,"revision":null,"name":"Explicit qualified Server","endpoint":server.endpoint,
"authentication":"unauthenticated_loopback","caPem":null,
"compatibility":qualification.id,"keepCredential":false,"credential":null
})).unwrap()).unwrap();
.expect("the bundled contract has a qualified Server record");
let state = manager
.save_profile(
serde_json::from_value(serde_json::json!({
"id":null,"revision":null,"name":"Explicit qualified Server","endpoint":server.endpoint,
"authentication":"unauthenticated_loopback","caPem":null,
"compatibility":qualification.id,"keepCredential":false,"credential":null
}))
.unwrap(),
)
.unwrap();
assert!(
state.active.is_none(),
"saving a profile must not activate it"
Expand Down
97 changes: 96 additions & 1 deletion docs/en/docs/get-started/install-and-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ powercontext server run
pass `--env-file <path>` to select another file, or pass `--no-env-file` to ignore environment files. Process managers
and containers should normally provide an explicit environment instead of relying on their working directory.

PowerContext always uses seekDB's built-in `test` database. Leave `POWERCONTEXT_SERVER_DATABASE_PATH` unset to store
PowerContext defaults to a dedicated `powercontext` database and creates it before initializing application tables.
To select another database, set `POWERCONTEXT_SERVER_DATABASE_DATABASE=my_context`; a missing database is created,
and an existing one is reused. Names containing backticks are rejected before initialization.
Leave `POWERCONTEXT_SERVER_DATABASE_PATH` unset to store
the instance in the `seekdb` subdirectory of the PowerContext user data directory. If `POWERCONTEXT_HOME` is set, the
default is `$POWERCONTEXT_HOME/seekdb`; set `POWERCONTEXT_SERVER_DATABASE_PATH` only when a different location is
required.
Expand All @@ -155,6 +158,98 @@ powercontext ready
powercontext capabilities
```

### Existing seekDB installations

Before upgrading, back up the seekDB instance and configuration, then explicitly retain the old database:

```bash
export POWERCONTEXT_SERVER_DATABASE_KIND=seekdb
export POWERCONTEXT_SERVER_DATABASE_DATABASE=test
powercontext server run
```

Keep the original `POWERCONTEXT_SERVER_DATABASE_PATH` or `POWERCONTEXT_HOME`. The instance directory and database name
are separate settings: changing either can select different data. Configure services and containers with the same values.

If no database name was explicitly configured and `test` contains tables named with PowerContext's `pc_` prefix,
startup stops before creating application tables and asks you to choose a database. It never automatically selects,
copies, or deletes the old database. Explicitly choose `test` to retain old data, or `powercontext` to use the new database.
The configuration wizard also asks for the database name; existing users should enter `test`.

To move data, keep using `test` until you have a backup and a validated export/import procedure for your engine version.
Stop all source writers, provision the target database, export/import the schema and data using that procedure, verify
identities and relationships, and read the migrated data through PowerContext before switching the configuration.
Automatic database creation does not perform this migration. Do not assume an arbitrary SQL dump is compatible across
engine or schema versions; existing schemas remain subject to compatibility checks and explicit schema maintenance.
Code graph references identify their instance and database: copying relational rows alone does not migrate graph storage.
Include target Code index rebuilding and queries in your validation; explicitly retaining `test` preserves legacy graph identities.

After switching, keep the old database without writers for a retention period you choose. Record the source and target
instance paths and names, backup location, stop-write and cutover times, validation results, and a review date. Before
manually deleting the source, confirm that no deployment uses it and that a usable backup remains. New writes to the
target are not copied back to the source, so keeping the source does not provide lossless rollback. PowerContext does
not automatically migrate data, send retention reminders, compare databases, or delete old databases.

## Use OceanBase

Connect to an existing OceanBase **MySQL-compatible user tenant**. Prepare the selected database and initialize its
full Server schema through explicit maintenance before starting the Server. PowerContext does not provision an
OceanBase instance, tenant, or account. Ensure network access and complete the Server
installation steps above, then configure the connection:

```bash
export POWERCONTEXT_SERVER_DATABASE_KIND=oceanbase
# Store credentials in your secret manager or a protected environment file.
export POWERCONTEXT_SERVER_DATABASE_URL='mysql+aoceanbase://user%40tenant:encoded-password@host:2881/?charset=utf8mb4'
```

Use the tenant account and the actual direct-connection or OBProxy endpoint and port. Percent-encode reserved characters
in the URL username and password; for example, `user@tenant` becomes `user%40tenant`. The URL requires the
`mysql+aoceanbase` driver, an explicit port, and `charset=utf8mb4`.

The example omits the database name and therefore selects `powercontext`. To select a custom database, put its name in
the URL path, for example `/team_context?charset=utf8mb4`. Existing URLs with an explicit database continue to select
that database. Select the database only in the URL path; `db` and `database` query parameters are rejected.
Database identifiers support 1–64 BMP characters, excluding control characters, slash, backslash, dot, backticks,
leading/trailing whitespace, and system database names; names are never silently trimmed or renamed by PowerContext. The server's identifier case rules still apply.
Names containing backticks are rejected during configuration validation, before connecting or creating a database.
Choose a name such as `powercontext`, `team_context`, or a Chinese name without backticks.

Python SDK initialization first connects to the selected database. If it is usable, no database-creation privileges are required. Only a
confirmed unknown-database error triggers an instance/tenant connection and `CREATE DATABASE IF NOT EXISTS`, followed
by a new connection to the selected database and application-table initialization. Authentication and network errors
do not trigger creation or fallback. If access is denied without proving whether the database exists, the error asks
an administrator to check both existence and privileges.

For a new full Server deployment, create the selected database first. If SDK initialization cannot create a missing
database, the same administrator step applies. Run the following in the **same tenant**, using your configured name,
then grant the application account the required database privileges:

```sql
CREATE DATABASE IF NOT EXISTS `powercontext` CHARACTER SET utf8mb4;
```

For the full Server, follow [database migrations](../operate/database-migrations.md) to plan, apply, and verify the
registered Server bundle with an explicit evidence directory and coordination mode. Start `powercontext server run`
only after the database is ready. Server startup checks readiness and does not initialize OceanBase schemas or
automatically migrate existing databases. SDK callers can retry initialization using the same URL after provisioning.
Creating missing tables requires table-creation privileges; an existing fully initialized database does not acquire a
new database-creation privilege requirement. Initialization never drops or
recreates an existing database, never switches to another database after a failure, and does not authorize schema upgrades.
Concurrent creation uses idempotent database/table DDL and handles duplicate-index creation races. This does not provide
coordination for existing-schema migrations or change embedded seekDB's process-ownership restrictions.

For Python callers, `OceanBaseProfile.open()` resolves an omitted URL database and provisions it when necessary.
`OceanBaseProfile.open_existing()` connects only to an already provisioned database, and `attach()` requires a
caller-owned engine that already selects an existing database; neither replaces that engine or provisions a different
connection target. Read-only inspection (`plan`, `verify`, and the `db-migrate` inspection commands) never creates a
missing OceanBase or embedded seekDB database; explicit writable seekDB maintenance can provision its configured database
before applying its bundle.

Read-only seekDB inspection requires an initialized instance directory. A missing or empty path is rejected before
starting the native engine, without creating parent directories or storage files. A file or a nonempty directory without
recognized native storage is also rejected without changing its contents. Ordinary startup still initializes new instances.

## Verify the installation

```bash
Expand Down
Loading
Loading