diff --git a/.gitattributes b/.gitattributes index f8aa38eeef..fe1b4eda15 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,3 +9,8 @@ integrations/dsh/plugins/powercontext/lib/*.js text eol=lf integrations/dsh/plugins/powercontext/lib/*.d.ts text eol=lf # Generated guidance compares canonical bytes; keep user notes outside its markers untouched. integrations/**/skills/powercontext-project-context/** text eol=lf +# Migration evidence hashes exact resource bytes on every platform. +src/powercontext/builtin/persistence/migrations/resources/** text eol=lf +src/powercontext/builtin/persistence/migrations/mysql_resources/** text eol=lf +src/powercontext/builtin/persistence/migrations/server_resources/** text eol=lf +src/powercontext/builtin/persistence/migration_resources/** text eol=lf diff --git a/.github/actions/setup-upgrade-oceanbase/action.yml b/.github/actions/setup-upgrade-oceanbase/action.yml new file mode 100644 index 0000000000..4851ab98ca --- /dev/null +++ b/.github/actions/setup-upgrade-oceanbase/action.yml @@ -0,0 +1,62 @@ +name: Setup upgrade OceanBase +description: Start the disposable MySQL tenant used by installed-release upgrade checks +runs: + using: composite + steps: + - name: Start and verify OceanBase + shell: bash + env: + OCEANBASE_IMAGE: ghcr.io/oceanbase/oceanbase-ce:4.3.5.6-106000012026040916 + run: | + docker run --detach --name upgrade-oceanbase \ + --publish 127.0.0.1:2881:2881 \ + --ulimit nofile=65536:65536 \ + --env MODE=slim \ + --env OB_DATABASE=pc_upgrade_test \ + --env OB_TENANT_PASSWORD=powercontext-e2e \ + "$OCEANBASE_IMAGE" + for attempt in $(seq 1 120); do + if docker exec upgrade-oceanbase \ + obclient -h127.0.0.1 -P2881 -uroot@test -ppowercontext-e2e \ + -Dpc_upgrade_test -e 'SELECT 1' >/dev/null 2>&1; then + # The slim image's default 1 GB maximum is too small for the + # indexes created across the clean migration acceptance cases. + docker exec upgrade-oceanbase \ + obclient -h127.0.0.1 -P2881 -uroot@sys \ + -e "ALTER SYSTEM SET datafile_maxsize='8G'; ALTER SYSTEM SET datafile_next='512M'; ALTER SYSTEM SET datafile_size='4G';" + datafile_ready=false + for config_attempt in $(seq 1 30); do + docker exec upgrade-oceanbase \ + obclient -h127.0.0.1 -P2881 -uroot@sys --batch --skip-column-names \ + -e "SHOW PARAMETERS WHERE name IN ('datafile_size', 'datafile_maxsize', 'datafile_next');" \ + > "$RUNNER_TEMP/upgrade-datafile.tsv" + if awk ' + $5 == "datafile_size" && $7 == "4G" { size = 1 } + $5 == "datafile_maxsize" && $7 == "8G" { maximum = 1 } + $5 == "datafile_next" && $7 == "512M" { increment = 1 } + END { exit !(size && maximum && increment) } + ' "$RUNNER_TEMP/upgrade-datafile.tsv"; then + datafile_ready=true + break + fi + sleep 2 + done + cat "$RUNNER_TEMP/upgrade-datafile.tsv" + if [ "$datafile_ready" != true ]; then + echo 'OceanBase datafile configuration did not become effective within one minute.' >&2 + exit 1 + fi + docker exec upgrade-oceanbase \ + obclient -h127.0.0.1 -P2881 -uroot@test -ppowercontext-e2e \ + -Dpc_upgrade_test \ + -e "CREATE DATABASE pc_upgrade_fresh; CREATE DATABASE pc_upgrade_code_test; CREATE DATABASE pc_upgrade_code_fresh; CREATE DATABASE pc_upgrade_vector_test; CREATE DATABASE pc_upgrade_vector_fresh; CREATE DATABASE pc_upgrade_vector_code_test; CREATE DATABASE pc_upgrade_vector_code_fresh; SET GLOBAL recyclebin=OFF; SELECT VERSION(); SHOW VARIABLES LIKE 'ob_compatibility_mode'; SHOW VARIABLES LIKE 'recyclebin';" + exit 0 + fi + if [ "$(docker inspect --format '{{.State.Running}}' upgrade-oceanbase)" != true ]; then + echo 'OceanBase exited before becoming ready.' >&2 + exit 1 + fi + sleep 5 + done + echo 'OceanBase did not become ready within 10 minutes.' >&2 + exit 1 diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml index efb745159d..3f4355096d 100644 --- a/.github/workflows/desktop.yml +++ b/.github/workflows/desktop.yml @@ -132,6 +132,7 @@ jobs: desktop/.artifacts/windows-smoke.json desktop/.artifacts/ci-compatibility.json desktop/.artifacts/real-server.json + desktop/.artifacts/real-server-*/ desktop/.artifacts/real-cli.json desktop/.artifacts/installed-ui.json desktop/.artifacts/installed-ui.png @@ -139,6 +140,7 @@ jobs: desktop/.artifacts/installed-ui-app.log desktop/.artifacts/installed-ui-processes.json desktop/.artifacts/installed-ui-window.png + desktop/.artifacts/installed-ui-server-*/ # Early validation failures can leave no installer or acceptance evidence. # Still reject missing artifacts when every preceding step succeeded. if-no-files-found: ${{ job.status == 'success' && 'error' || 'warn' }} diff --git a/.github/workflows/e2e-harness.yml b/.github/workflows/e2e-harness.yml index c9d9e8074b..85b0a26a9e 100644 --- a/.github/workflows/e2e-harness.yml +++ b/.github/workflows/e2e-harness.yml @@ -175,6 +175,46 @@ jobs: tests/builtin/persistence/test_database_snapshots.py \ --junitxml=.powercontext/recall-effort-ci/regressions.xml -q -rs + - name: Recover and maintain the full Server chain on real OceanBase without skips + env: + POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL: >- + mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_migration_probe_server_recovery?charset=utf8mb4 + run: | + docker exec recall-effort-oceanbase \ + obclient -h127.0.0.1 -P2881 -uroot@test -ppowercontext-e2e \ + -Dpowercontext_test -e 'CREATE DATABASE pc_migration_probe_server_recovery' + timeout --signal=TERM --kill-after=10s 600s uv run --locked pytest -q -rs \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_mysql_full_server_interruption_preserves_history_and_original_maintenance \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_oceanbase_full_server_client_exit_requires_acknowledgement_and_preserves_recovery \ + -o faulthandler_timeout=180 --durations=10 \ + -k oceanbase --junitxml=.powercontext/recall-effort-ci/full-server-recovery.xml + + - name: Maintain the registered Server chain on real OceanBase without skips + env: + POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL: >- + mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_migration_probe_server_recovery?charset=utf8mb4 + run: | + timeout --signal=TERM --kill-after=10s 600s uv run --locked pytest -q -rs \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_mysql_registered_processing_mode_switch_preserves_revision_and_history \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_mysql_atomic_compatibility_command_uses_shared_server_upgrade \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_mysql_registered_projection_repair_preserves_authority_and_revision \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_mysql_topic_bundle_refuses_managed_server_storage \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_mysql_managed_identity_initialization_requires_no_ddl \ + -o faulthandler_timeout=180 --durations=10 \ + -k oceanbase --junitxml=.powercontext/recall-effort-ci/full-server-maintenance.xml + uv run --locked python - <<'PY' + from pathlib import Path + from xml.etree import ElementTree + + reports = ( + Path(".powercontext/recall-effort-ci/full-server-recovery.xml"), + Path(".powercontext/recall-effort-ci/full-server-maintenance.xml"), + ) + cases = [case for report in reports for case in ElementTree.parse(report).iter("testcase")] + if len(cases) != 10 or any(case.find(tag) is not None for case in cases for tag in ("skipped", "failure", "error")): + raise SystemExit("All ten native full Server recovery and maintenance scenarios must pass without skips.") + PY + - name: Collect the database log if: always() run: | @@ -202,7 +242,7 @@ jobs: ${{ !cancelled() && (github.event_name != 'workflow_dispatch' || inputs.suite != 'recall-effort') && (needs.changes.result != 'success' || fromJSON(needs.changes.outputs.suites || '{}').database) }} runs-on: ubuntu-latest - timeout-minutes: 70 + timeout-minutes: 100 env: OCEANBASE_IMAGE: ghcr.io/oceanbase/oceanbase-ce:4.3.5.6-106000012026040916 POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL: >- @@ -290,13 +330,14 @@ jobs: tests/e2e/test_background_metrics.py - name: Run clean migration evidence, grants and vectors without skips - timeout-minutes: 45 + timeout-minutes: 60 run: | uv run --locked python scripts/run_atomic_memory_clean_migration.py \ oceanbase --output "$MIGRATION_REPORTS/clean-migration" - name: Test long-query retrieval and admission without skips - timeout-minutes: 10 + # Eight isolated cases include full Server setup and retain 180s deadlines. + timeout-minutes: 30 run: | uv run --locked python scripts/run_atomic_memory_clean_migration.py \ oceanbase --output "$MIGRATION_REPORTS/long-query" --tests \ diff --git a/.github/workflows/kimi-code-acceptance.yml b/.github/workflows/kimi-code-acceptance.yml index 8084f33f4a..8d70880bf8 100644 --- a/.github/workflows/kimi-code-acceptance.yml +++ b/.github/workflows/kimi-code-acceptance.yml @@ -135,6 +135,8 @@ jobs: reports.mkdir(exist_ok=True) for evidence in (temporary / "kimi-acceptance").glob("*/evidence.json"): shutil.copy2(evidence, reports / (evidence.parent.name + ".json")) + for evidence in (temporary / "kimi-acceptance").glob("*/server-startup.json"): + shutil.copy2(evidence, reports / (evidence.parent.name + "-server-startup.json")) PY - name: Upload controlled acceptance evidence diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 19d1b1968c..58ae89a5f3 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -19,6 +19,113 @@ jobs: changes: uses: ./.github/workflows/ci-changes.yml + upgrade-release-gate: + name: Upgrade release gate + runs-on: ubuntu-24.04 + timeout-minutes: 180 + env: + PC_UPGRADE_OCEANBASE_URL: mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_upgrade_test?charset=utf8mb4 + PC_UPGRADE_OCEANBASE_FRESH_URL: mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_upgrade_fresh?charset=utf8mb4 + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + persist-credentials: false + - name: Set up the environment + uses: ./.github/actions/setup-python-env + - name: Verify upgrade policy and gate regressions + run: | + make upgrade-policy-check + uv run pytest -q tests/test_upgrade_release_gate.py tests/test_upgrade_publication.py + - name: Build the target artifact + run: uv build --out-dir dist + - name: Start the isolated upgrade OceanBase environment + uses: ./.github/actions/setup-upgrade-oceanbase + + - name: Run declared installed-release acceptance + run: >- + uv run python scripts/run_upgrade_release_gate.py + --output "$RUNNER_TEMP/upgrade-release-evidence" + - name: Preserve OceanBase diagnostics + if: always() + run: | + mkdir -p "$RUNNER_TEMP/upgrade-release-evidence" + docker logs upgrade-oceanbase > "$RUNNER_TEMP/upgrade-release-evidence/oceanbase.log" 2>&1 || true + docker inspect --format '{{json .State}}' upgrade-oceanbase \ + > "$RUNNER_TEMP/upgrade-release-evidence/oceanbase-state.json" 2>&1 || true + - name: Stop the isolated upgrade OceanBase environment + if: always() + run: docker rm --force upgrade-oceanbase + - name: Preserve upgrade acceptance evidence + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: upgrade-release-evidence-${{ github.sha }} + path: ${{ runner.temp }}/upgrade-release-evidence/ + if-no-files-found: error + retention-days: 30 + + oceanbase-engine-recovery: + name: OceanBase engine crash recovery + needs: changes + if: ${{ !cancelled() && (needs.changes.result != 'success' || fromJSON(needs.changes.outputs.suites || '{}').tests) }} + runs-on: ubuntu-24.04 + timeout-minutes: 20 + env: + POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL: mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_migration_probe_engine_crash?charset=utf8mb4 + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up the environment + uses: ./.github/actions/setup-python-env + - name: Start an isolated OceanBase engine + uses: ./.github/actions/setup-upgrade-oceanbase + - name: Bind the destructive probe to this job's owned container + run: | + docker exec upgrade-oceanbase \ + obclient -h127.0.0.1 -P2881 -uroot@test -ppowercontext-e2e \ + -e 'CREATE DATABASE pc_migration_probe_engine_crash;' + container_id=$(docker inspect --format '{{.Id}}' upgrade-oceanbase) + [[ "$container_id" =~ ^[0-9a-f]{64}$ ]] + echo "POWERCONTEXT_TEST_MIGRATION_OCEANBASE_ENGINE_CRASH_ID=$container_id" >> "$GITHUB_ENV" + - name: Verify migration recovery after observer SIGKILL + run: | + mkdir -p "$RUNNER_TEMP/oceanbase-engine-recovery" + uv run pytest -q tests/native/test_oceanbase_engine_recovery.py \ + --junitxml "$RUNNER_TEMP/oceanbase-engine-recovery/junit.xml" \ + --basetemp "$RUNNER_TEMP/oceanbase-engine-recovery/cases" + uv run python - <<'PY' + import os + from pathlib import Path + from xml.etree import ElementTree + path = Path(os.environ["RUNNER_TEMP"]) / "oceanbase-engine-recovery/junit.xml" + suites = list(ElementTree.parse(path).getroot().iter("testsuite")) + assert sum(int(s.get("tests", 0)) for s in suites) == 1 + assert all(int(s.get(key, 0)) == 0 for s in suites for key in ("failures", "errors", "skipped")) + PY + - name: Report recovery failures + if: failure() + run: >- + uv run python .github/scripts/report_pytest_failures.py + "$RUNNER_TEMP/oceanbase-engine-recovery/junit.xml" + - name: Preserve diagnostics and stop the owned engine + if: always() + run: | + mkdir -p "$RUNNER_TEMP/oceanbase-engine-recovery" + docker logs upgrade-oceanbase > "$RUNNER_TEMP/oceanbase-engine-recovery/container.log" 2>&1 || true + docker stop --timeout 30 upgrade-oceanbase || true + - name: Save engine recovery evidence + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: oceanbase-engine-recovery-${{ github.sha }} + path: ${{ runner.temp }}/oceanbase-engine-recovery/ + if-no-files-found: error + retention-days: 7 + quality: timeout-minutes: 20 runs-on: ubuntu-latest @@ -277,7 +384,7 @@ jobs: code-seekdb: needs: changes if: ${{ !cancelled() && (needs.changes.result != 'success' || fromJSON(needs.changes.outputs.suites || '{}').database) }} - timeout-minutes: 20 + timeout-minutes: 30 runs-on: ubuntu-latest strategy: fail-fast: false @@ -298,7 +405,9 @@ jobs: run: make code-seekdb-test - name: Test unified migrations with embedded seekdb - timeout-minutes: 10 + # Full-Server cases run in separate processes and exceed fifteen minutes + # in aggregate; each runner keeps its 180-second deadline below. + timeout-minutes: 25 env: POWERCONTEXT_TEST_MIGRATION_SEEKDB: "1" POWERCONTEXT_TEST_MIGRATION_SQL_LOG: "1" diff --git a/.github/workflows/native-service.yml b/.github/workflows/native-service.yml index b4fae40996..69e1055663 100644 --- a/.github/workflows/native-service.yml +++ b/.github/workflows/native-service.yml @@ -50,6 +50,9 @@ jobs: wheel=$(find dist -name '*.whl' -print -quit) uv venv .native-venv uv pip install --python .native-venv/bin/python "powercontext[cli,server] @ file://$PWD/$wheel" pytest + uv venv .native-source-venv + uv pip install --python .native-source-venv/bin/python "powercontext[cli,server] @ https://files.pythonhosted.org/packages/30/bd/463f998f8794bc7f8ff588d47157ca0e62dd03a9f3ae466e2eb34fb66045/powercontext-1.3.0-py3-none-any.whl#sha256=8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5" + echo "POWERCONTEXT_NATIVE_UPGRADE_SOURCE_PYTHON=$PWD/.native-source-venv/bin/python" >> "$GITHUB_ENV" - name: Exercise the real systemd user lifecycle run: >- @@ -105,6 +108,9 @@ jobs: # Finish bytecode compilation during installation, before launchd # runs the cold-start lifecycle under its bounded startup deadline. uv pip install --compile-bytecode --python .native-venv/bin/python "powercontext[cli,server] @ file://$PWD/$wheel" pytest + uv venv .native-source-venv + uv pip install --compile-bytecode --python .native-source-venv/bin/python "powercontext[cli,server] @ https://files.pythonhosted.org/packages/30/bd/463f998f8794bc7f8ff588d47157ca0e62dd03a9f3ae466e2eb34fb66045/powercontext-1.3.0-py3-none-any.whl#sha256=8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5" + echo "POWERCONTEXT_NATIVE_UPGRADE_SOURCE_PYTHON=$PWD/.native-source-venv/bin/python" >> "$GITHUB_ENV" - name: Exercise the real LaunchAgent lifecycle run: >- @@ -157,6 +163,9 @@ jobs: $wheelUri = "file:///" + $wheel.Replace('\', '/') uv venv .native-venv uv pip install --python .native-venv\Scripts\python.exe "powercontext[cli,server] @ $wheelUri" pytest + uv venv .native-source-venv + uv pip install --python .native-source-venv\Scripts\python.exe "powercontext[cli,server] @ https://files.pythonhosted.org/packages/30/bd/463f998f8794bc7f8ff588d47157ca0e62dd03a9f3ae466e2eb34fb66045/powercontext-1.3.0-py3-none-any.whl#sha256=8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5" + "POWERCONTEXT_NATIVE_UPGRADE_SOURCE_PYTHON=$PWD\.native-source-venv\Scripts\python.exe" >> $env:GITHUB_ENV - name: Verify binary cursor persistence and restart compatibility shell: pwsh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dff86dbc30..2e3cc49050 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,10 @@ concurrency: jobs: release-build: runs-on: ubuntu-24.04 - timeout-minutes: 20 + timeout-minutes: 180 + env: + PC_UPGRADE_OCEANBASE_URL: mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_upgrade_test?charset=utf8mb4 + PC_UPGRADE_OCEANBASE_FRESH_URL: mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_upgrade_fresh?charset=utf8mb4 steps: - name: Check out uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 @@ -74,11 +77,40 @@ jobs: uv build --out-dir dist uvx --from twine twine check dist/* + - name: Start the isolated upgrade OceanBase environment + uses: ./.github/actions/setup-upgrade-oceanbase + + - name: Gate upgrade claims on installed release acceptance + run: >- + uv run python scripts/run_upgrade_release_gate.py + --release --release-event "$GITHUB_EVENT_PATH" + --output "$RUNNER_TEMP/upgrade-release-evidence" + + - name: Preserve OceanBase diagnostics + if: always() + run: | + mkdir -p "$RUNNER_TEMP/upgrade-release-evidence" + docker logs upgrade-oceanbase > "$RUNNER_TEMP/upgrade-release-evidence/oceanbase.log" 2>&1 || true + docker inspect --format '{{json .State}}' upgrade-oceanbase \ + > "$RUNNER_TEMP/upgrade-release-evidence/oceanbase-state.json" 2>&1 || true + - name: Stop the isolated upgrade OceanBase environment + if: always() + run: docker rm --force upgrade-oceanbase + + - name: Preserve upgrade acceptance evidence + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: upgrade-release-evidence + path: ${{ runner.temp }}/upgrade-release-evidence/ + if-no-files-found: error + retention-days: 30 + - name: Upload release distributions uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: name: release-dists - path: dist/* + path: ${{ runner.temp }}/upgrade-release-evidence/validated-dist/* if-no-files-found: error retention-days: 30 diff --git a/Makefile b/Makefile index ce5b0a57f6..5f73013aaf 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ notebooks-test: ## Execute provider-free tutorials in fresh kernels; use ARGS fo @uv run --locked --group notebooks python examples/jupyter/run.py $(ARGS) .PHONY: check -check: version-check workflow-actions-check integration-manifest-check integration-guidance-check ## Run code quality tools. +check: version-check upgrade-policy-check workflow-actions-check integration-manifest-check integration-guidance-check ## Run code quality tools. .PHONY: integration-guidance integration-guidance-check integration-guidance: ## Refresh the managed regions of packaged integration guidance. @@ -236,18 +236,25 @@ version-check: ## Check release references and generated API code; optionally as @$(MAKE) api-generate-check js-api-generate-check .PHONY: release-check -release-check: version-check docs-install ## Check version references and require matching bilingual notes for a stable release. +release-check: version-check upgrade-policy-check docs-install ## Check version references and require matching bilingual notes for a stable release. @cd website && node --import tsx --test tests/releases.test.ts +.PHONY: upgrade-policy-check +upgrade-policy-check: ## Verify the declared upgrade matrix, persistence review, and installation guidance. + @uv run python scripts/upgrade_release_gate.py + +.PHONY: upgrade-release-test +upgrade-release-test: ## Validate distributions in dist and require successful installed-release upgrades. + @uv run python scripts/run_upgrade_release_gate.py + .PHONY: clean-build clean-build: ## Clean build artifacts @echo "🚀 Removing build artifacts" @uv run python -c "import shutil; import os; shutil.rmtree('dist') if os.path.exists('dist') else None" .PHONY: publish -publish: ## Publish a release to PyPI. - @echo "🚀 Publishing." - @uv publish dist/* +publish: ## Publish after release upgrade acceptance; UPGRADE_RELEASE_NOTES must name the reviewed release notes file. + @uv run python scripts/run_upgrade_release_gate.py --release --publish --release-notes "$$UPGRADE_RELEASE_NOTES" .PHONY: build-and-publish build-and-publish: build publish ## Build and publish. diff --git a/desktop/README.md b/desktop/README.md index a661c77d41..ae4a6d96a9 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -119,7 +119,7 @@ Installed boundary checks exercise an 8192-byte Unicode note, reject over-budget After upgrading from an earlier preview, open Connections and select your saved connection. Profiles matching the bundled API contract appear under “Available for this Desktop build”; compare their tested Server builds with your deployment before selecting one, saving, checking and explicitly using the connection again. New connections start with no compatibility selection. -Historical profiles appear as disabled records. A saved incompatible or unavailable selection remains visible with an explanation until explicitly replaced; it is never silently upgraded. For the qualified current Server sources, explicitly choose `sqlite-atomic-d9a78567-v1` and check the connection before selecting a Scope; that record's contract digest matches this build's bundled contract, while `sqlite-atomic-caf2a35a-v1`, `sqlite-atomic-683e2db0-v1` and `sqlite-atomic-cab054db-v1` qualify their corresponding Server source deployments and do not match it. The recorded Server source and wheel identities must match the deployment you intend to use. The independently qualified `ci-fixtures` build is for disposable acceptance tests. Historical qualification evidence and remaining requirements are documented in the [validation guide](VALIDATION.md). +Historical profiles appear as disabled records. A saved incompatible or unavailable selection remains visible with an explanation until explicitly replaced; it is never silently upgraded. For the qualified current Server sources, explicitly choose `sqlite-atomic-8f10c560-v1` and check the connection before selecting a Scope; that record's contract digest matches this build's bundled contract, while `sqlite-atomic-caf2a35a-v1`, `sqlite-atomic-683e2db0-v1`, `sqlite-atomic-cab054db-v1` and `sqlite-atomic-d9a78567-v1` qualify their corresponding Server source deployments and do not match it. The recorded Server source and wheel identities must match the deployment you intend to use. The independently qualified `ci-fixtures` build is for disposable acceptance tests. Historical qualification evidence and remaining requirements are documented in the [validation guide](VALIDATION.md). ## Build on macOS diff --git a/desktop/README.zh.md b/desktop/README.zh.md index 40fd44df3e..b2bc710428 100644 --- a/desktop/README.zh.md +++ b/desktop/README.zh.md @@ -102,7 +102,7 @@ Windows GitHub Actions 会构建未签名安装包,安装到临时中文路径 从早期预览版升级后,请打开「连接」并选中已保存的连接。与当前 API 契约匹配的配置显示在「此 Desktop 构建可用」中;先核对已测试的 Server 构建与实际部署,再明确选择、保存、检查并使用该连接。新连接默认不选择兼容配置。 -历史配置显示为不可选择的记录。已保存的不匹配或不可用配置仍会显示并说明原因,须明确替换,不会静默升级。对于已资格化的当前 Server 源码,明确选择 `sqlite-atomic-d9a78567-v1` 并在选取范围前检查连接;该记录的契约摘要与本构建的打包契约匹配,而 `sqlite-atomic-caf2a35a-v1`、`sqlite-atomic-683e2db0-v1` 与 `sqlite-atomic-cab054db-v1` 只资格化各自对应 Server 源码的部署,并不匹配打包契约。已记录的 Server 源码和 wheel 身份必须与实际部署一致。独立验证的 `ci-fixtures` 构建仅用于临时验收测试。历史资格证据和剩余要求见[验证说明](VALIDATION.md)。 +历史配置显示为不可选择的记录。已保存的不匹配或不可用配置仍会显示并说明原因,须明确替换,不会静默升级。对于已资格化的当前 Server 源码,明确选择 `sqlite-atomic-8f10c560-v1` 并在选取范围前检查连接;该记录的契约摘要与本构建的打包契约匹配,而 `sqlite-atomic-caf2a35a-v1`、`sqlite-atomic-683e2db0-v1`、`sqlite-atomic-cab054db-v1` 与 `sqlite-atomic-d9a78567-v1` 只资格化各自对应 Server 源码的部署,并不匹配打包契约。已记录的 Server 源码和 wheel 身份必须与实际部署一致。独立验证的 `ci-fixtures` 构建仅用于临时验收测试。历史资格证据和剩余要求见[验证说明](VALIDATION.md)。 ## 在 macOS 上构建 diff --git a/desktop/VALIDATION.md b/desktop/VALIDATION.md index 5ec28c8e48..07956389d3 100644 --- a/desktop/VALIDATION.md +++ b/desktop/VALIDATION.md @@ -22,7 +22,7 @@ cargo test --locked --manifest-path desktop/src-tauri/Cargo.toml ## Compatibility scope -[compatibility.json](src-tauri/src/connections/compatibility.json) preserves the recorded Server sources, normalized OpenAPI contract digests and qualification-wheel digests for the historical profiles below. The current contract is qualified by `sqlite-atomic-d9a78567-v1` below; ordinary builds require explicitly selecting it and checking the connection before business operations become available. The `sqlite-atomic-caf2a35a-v1`, `sqlite-atomic-683e2db0-v1` and `sqlite-atomic-cab054db-v1` records qualify their corresponding Server source deployments and do not match this bundled contract. Historical records are disabled in the selector, and saved unavailable selections remain visible until explicitly replaced. CI builds have their own artifact identities and do not change product qualification. +[compatibility.json](src-tauri/src/connections/compatibility.json) preserves the recorded Server sources, normalized OpenAPI contract digests and qualification-wheel digests for the historical profiles below. The current contract is qualified by `sqlite-atomic-8f10c560-v1` below; ordinary builds require explicitly selecting it and checking the connection before business operations become available. The `sqlite-atomic-caf2a35a-v1`, `sqlite-atomic-683e2db0-v1`, `sqlite-atomic-cab054db-v1` and `sqlite-atomic-d9a78567-v1` records qualify their corresponding Server source deployments and do not match this bundled contract. Historical records are disabled in the selector, and saved unavailable selections remain visible until explicitly replaced. CI builds have their own artifact identities and do not change product qualification. The real Server harness covers SQLite with anonymous loopback, static Bearer, HTTPS with explicit CA/base path, and injected-provider/enforced access. It exercises save/search/exact reads, identity changes, binding revocation, ambiguous writes without replay and Scope pagination. Installed UI checks cover repeated launches preserving the existing editor, late committed-save responses preserving a new Scope draft, explicit connection/Scope selection, clipboard copies, connection isolation, draft cancellation, byte limits and independent Server survival after Desktop exit. @@ -30,9 +30,36 @@ Selecting a compatibility profile does not attest the remote binary identity. Ex ### Current Server protocol qualification +#### Server 8f10c560 protocol qualification + +The bundled contract is qualified by `sqlite-atomic-8f10c560-v1`. It identifies Server sources +at `8f10c560776b32b717c3511fad9da349982a5748` and normalized OpenAPI digest +`5878999ce9e239d6642e4da858074b281ed465629432c578b3584eec47cbce6c`. +The qualification wheel is `powercontext-1.4.1.dev242+g8f10c5607-py3-none-any.whl`, +SHA-256 `c771a39de6d6797efc33d406619aa02eeb7d7731f27147b3287079616709db97`. +All 583 packaged runtime files across `powercontext/` and `powercontext_service_bootstrap/` +match that Git commit byte for byte. The OpenAPI contract, project configuration and lock also match. +The sorted runtime source-manifest digest is +`d9f578a88dc9a2e0fffb3b75b9c4fba8bc245720857333c6bd6ad590e488b17c` +(one `wheel/path sha256` entry per line, with a final newline). + +The existing real-Server harness passed against the isolated wheel and SQLite in all four modes: +anonymous loopback, static Bearer, HTTPS with an explicit CA and base path, and an injected +provider with enforced access. The ten recorded operations cover Scope selection and pagination, +Atomic Memory save/search/exact revision reads, identity changes, same-identity revocation, +committed writes with lost responses without replay, and independent Server survival after the +client exits. + +Qualification ran on macOS 15.7.3 ARM64 with Python 3.12.13 and Rust 1.95.0, from a clean worktree +at `8f10c560776b32b717c3511fad9da349982a5748`. The native `server_probe` was built with the unchanged +product modules and Cargo lock. Credentials used session-only storage. This establishes the stated +Server protocol compatibility on that host; it does not establish installed GUI workflows, native +credential persistence, Intel or Windows execution, signing, notarization, or release qualification. +Historical records retain their exact source, contract, wheel and platform evidence. + #### Server d9a78567 protocol qualification -The bundled contract is qualified by `sqlite-atomic-d9a78567-v1`. The record identifies Server +The corresponding historical contract is qualified by `sqlite-atomic-d9a78567-v1`. The record identifies Server sources at `d9a785675dedb72cd72a386b346e590f4c067ac0` and normalized OpenAPI digest `a6758fd35296abefb55ab86b152584a369d5f2deedfee7a3f9822e16ad1edc25`. The qualification wheel is `powercontext-1.4.1.dev69+ga200d6094.d20261022-py3-none-any.whl`, @@ -139,8 +166,8 @@ matching `sqlite-atomic-caf2a35a-v1`. The observed app is identified by the exec this record does not identify its exact build source revision. This is historical evidence, not a procedure or native UI qualification for the current bundled contract. -The current `sqlite-atomic-d9a78567-v1` record has non-GUI protocol evidence on Linux x86_64 only. -Native macOS save/search/exact-read and clipboard acceptance against that record remains unverified. +The current `sqlite-atomic-8f10c560-v1` record has native protocol evidence on macOS ARM64. +Installed macOS GUI save/search/exact-read and clipboard acceptance against that record remains unverified. The normal release app (without `ci-fixtures`) was operated on macOS 26.5.2 ARM64 against the wheel recorded under [Server caf2a35a protocol qualification](#server-caf2a35a-protocol-qualification) diff --git a/desktop/src-tauri/src/connections/compatibility.json b/desktop/src-tauri/src/connections/compatibility.json index 9132847dfb..5e0738421a 100644 --- a/desktop/src-tauri/src/connections/compatibility.json +++ b/desktop/src-tauri/src/connections/compatibility.json @@ -151,5 +151,24 @@ "search_memory" ], "evidence": "desktop/VALIDATION.md#server-d9a78567-protocol-qualification" + }, + { + "id": "sqlite-atomic-8f10c560-v1", + "serverCommit": "8f10c560776b32b717c3511fad9da349982a5748", + "contractSha256": "5878999ce9e239d6642e4da858074b281ed465629432c578b3584eec47cbce6c", + "artifactSha256": "c771a39de6d6797efc33d406619aa02eeb7d7731f27147b3287079616709db97", + "operations": [ + "get_access_principal", + "get_artifact_revision", + "get_capabilities", + "get_default_scope", + "get_liveness", + "get_readiness", + "get_scope", + "list_scopes", + "remember_memory", + "search_memory" + ], + "evidence": "desktop/VALIDATION.md#server-8f10c560-protocol-qualification" } ] diff --git a/desktop/tests/installed_fixture.py b/desktop/tests/installed_fixture.py index 015b2326a7..037a4fc776 100644 --- a/desktop/tests/installed_fixture.py +++ b/desktop/tests/installed_fixture.py @@ -33,7 +33,7 @@ import httpx from fixture_qualification import fixture_profile -from real_server import HarnessFailure, control_pipe +from real_server import HarnessFailure, retained_fixture_server def fixture_compatibility_profile() -> str: @@ -89,6 +89,7 @@ def isolated_server( if len(wheels) != 1: raise HarnessFailure("installed_server_wheel_count") wheel = wheels[0] + wheel_digest = hashlib.sha256(wheel.read_bytes()).hexdigest() with tempfile.TemporaryDirectory(prefix="desktop-installed-server-") as directory: temp = Path(directory) wheel_root = temp / "wheel" @@ -105,6 +106,7 @@ def isolated_server( "prefix": "", "port": port, "tls": False, + "uvicorn_log_level": "warning", "response_gate_path": str(response_gate) if response_gate else None, "response_loss_path": str(response_loss_counter) if response_loss_counter else None, } @@ -125,27 +127,26 @@ def isolated_server( env=environment, creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, ) - control = control_pipe(process) - try: - with httpx.Client(base_url=f"http://127.0.0.1:{port}", trust_env=False, timeout=10) as client: - wait_ready(client, process) - response = client.post( - "/v1/scopes", - json={ - "title": "Desktop installed CI", - "summary": "Synthetic installed UI acceptance", - "idempotency_key": "desktop-installed-ui-scope", - }, - ) - response.raise_for_status() - yield client, response.json()["scope_id"], hashlib.sha256(wheel.read_bytes()).hexdigest() - finally: - if process.poll() is None: - control.write(b"stop\n") - control.flush() - try: - process.wait(timeout=30) - except subprocess.TimeoutExpired: - process.kill() - process.wait(timeout=10) - control.close() + with ( + retained_fixture_server( + process, + log, + temp / "server.log", + desktop / ".artifacts", + label="installed-ui-server", + port=port, + wheel_sha256=wheel_digest, + ), + httpx.Client(base_url=f"http://127.0.0.1:{port}", trust_env=False, timeout=10) as client, + ): + wait_ready(client, process) + response = client.post( + "/v1/scopes", + json={ + "title": "Desktop installed CI", + "summary": "Synthetic installed UI acceptance", + "idempotency_key": "desktop-installed-ui-scope", + }, + ) + response.raise_for_status() + yield client, response.json()["scope_id"], wheel_digest diff --git a/desktop/tests/real_server.py b/desktop/tests/real_server.py index fb2808e591..43db2b8821 100644 --- a/desktop/tests/real_server.py +++ b/desktop/tests/real_server.py @@ -35,8 +35,10 @@ import threading import time import zipfile +from collections.abc import Iterator +from contextlib import contextmanager, suppress from pathlib import Path -from typing import IO +from typing import IO, TextIO class HarnessFailure(RuntimeError): @@ -132,6 +134,46 @@ def wait_for_release(): await send(message) +def measured_requests(app): + """Observe synthetic HTTP completion without recording request content.""" + request_number = 0 + + async def measured_proxy(scope, receive, send): + nonlocal request_number + if scope["type"] != "http": + await app(scope, receive, send) + return + request_number += 1 + number = request_number + started = time.perf_counter() + status = None + complete = False + print(json.dumps({"request": number, "phase": "start", "method": scope["method"]}), flush=True) + + async def observed_send(message): + nonlocal status + if message["type"] == "http.response.start": + status = message["status"] + await send(message) + + try: + await app(scope, receive, observed_send) + complete = True + finally: + print( + json.dumps({ + "request": number, + "phase": "finish", + "elapsed_ms": int((time.perf_counter() - started) * 1000), + "status": status, + "complete": complete, + }), + flush=True, + ) + + return measured_proxy + + def serve(config_path: Path) -> None: config = json.loads(config_path.read_text(encoding="utf-8")) sys.path.insert(0, config["wheel_root"]) @@ -175,10 +217,10 @@ async def proxy(scope, receive, send): server = uvicorn.Server( uvicorn.Config( - proxy, + measured_requests(proxy), host="127.0.0.1", port=config["port"], - log_level="critical", + log_level=config.get("uvicorn_log_level", "critical"), access_log=False, ssl_keyfile=config["key"] if config["tls"] else None, ssl_certfile=config["certificate"] if config["tls"] else None, @@ -196,12 +238,15 @@ async def run(): if not config.get("provider"): await server.serve() return + from powercontext.builtin.persistence.migrations.server_readiness import prepare_server_storage from powercontext.server.authz import PrincipalRef from powercontext.server.authz.composition import open_builtin_access_control from powercontext.server.settings import AccessControlConfig admin = PrincipalRef(type="service", id="desktop-fixture-admin") + # Bootstrap the complete Server before the separate Access context opens it. + await asyncio.to_thread(prepare_server_storage, settings) async with open_builtin_access_control( settings.database, bootstrap_administrators=(admin,), deployment_id="desktop-fixture" ) as access: @@ -225,6 +270,66 @@ def control_pipe(process: subprocess.Popen[bytes]) -> IO[bytes]: return process.stdin +@contextmanager +def retained_fixture_server( + process: subprocess.Popen[bytes], + log: TextIO, + log_path: Path, + artifact_dir: Path, + *, + label: str, + port: int, + wheel_sha256: str, +) -> Iterator[None]: + """Retain bounded synthetic diagnostics before either harness removes storage.""" + control = control_pipe(process) + report = { + "pid": process.pid, + "port": port, + "wheel_sha256": wheel_sha256, + "outcome": "failed", + "forced_kill": False, + } + try: + yield + report["outcome"] = "passed" + except BaseException as error: + report["exception_type"] = type(error).__name__ + error.add_note(f"Fixture {label} Server pid={process.pid}, port={port}") + raise + finally: + report["before_cleanup_returncode"] = process.poll() + try: + if process.poll() is None: + with suppress(BrokenPipeError): + control.write(b"stop\n") + control.flush() + try: + process.wait(timeout=30) + except subprocess.TimeoutExpired: + report["forced_kill"] = True + process.kill() + process.wait(timeout=10) + finally: + report["after_cleanup_returncode"] = process.poll() + with suppress(BrokenPipeError): + control.close() + # Databases, credentials and configuration stay in disposable storage. + try: + log.flush() + with log_path.open("rb") as source: + size = source.seek(0, os.SEEK_END) + source.seek(max(0, size - 256 * 1024)) + tail = source.read(256 * 1024) + report["log_bytes"] = size + report["log_truncated"] = size > len(tail) + retained = Path(tempfile.mkdtemp(prefix=label + "-", dir=artifact_dir)) + (retained / "server.log").write_bytes(tail) + (retained / "process.json").write_text(json.dumps(report, indent=2), encoding="utf-8") + except OSError: + print("Fixture Server diagnostics could not be retained.", file=sys.stderr) + + def main() -> None: import httpx from fixture_qualification import fixture_profile @@ -269,6 +374,7 @@ def main() -> None: "prefix": prefix, "port": port, "tls": tls, + "uvicorn_log_level": "warning", "key": str(temp / "server-key.pem"), "certificate": str(temp / "server.pem"), } @@ -290,81 +396,78 @@ def main() -> None: env=allowed_env, creationflags=flags, ) - control = control_pipe(process) context = ssl.create_default_context(cafile=str(temp / "ca.pem")) if tls else True - try: - with httpx.Client( + with ( + retained_fixture_server( + process, + log, + case / "server.log", + artifact_dir, + label=f"real-server-{mode}", + port=port, + wheel_sha256=hashlib.sha256(wheel.read_bytes()).hexdigest(), + ), + httpx.Client( base_url=endpoint, verify=context, trust_env=False, timeout=3, headers={"Authorization": f"Bearer {token}"} if token else {}, - ) as client: - for _ in range(100): - if process.poll() is not None: - raise HarnessFailure( - "startup", (case / "server.log").read_text(encoding="utf-8")[-3000:] - ) - try: - if client.get("/health/ready").status_code == 200: - break - except httpx.HTTPError: - pass - time.sleep(0.2) - else: - raise HarnessFailure("readiness_timeout") - result = client.post( - "/v1/scopes", - json={ - "title": "Desktop synthetic fixture", - "summary": "Dedicated no-model validation", - "idempotency_key": "desktop-fixture-scope", - }, - ) - result.raise_for_status() - scope_id = result.json()["scope_id"] - fixture = { - "compatibility_profile": compatibility, - "response_loss_path": config["response_loss_path"], - "reader_token": config["reader_token"] if config["provider"] else None, - "identity_change_path": config["identity_change_path"] if config["provider"] else None, - "endpoint": endpoint, - "scope_id": scope_id, - "token": token, - "ca_pem": (temp / "ca.pem").read_text(encoding="utf-8") if tls else None, - } - fixture_path = case / "client.json" - fixture_path.write_text(json.dumps(fixture), encoding="utf-8") - probe = subprocess.run( - [str(executable), str(fixture_path)], - capture_output=True, - text=True, - timeout=120, - creationflags=flags, - ) - if probe.returncode: - raise HarnessFailure(mode, probe.stderr[-2000:]) - client.get("/health/live").raise_for_status() - reports.append({ - "mode": mode, - "result": "passed", - "serverAliveAfterClientExit": True, - "committedWriteWithLostResponseUnknownWithoutReplay": True, - "providerIdentityChangeInvalidatesContext": bool(config["provider"]), - "sameIdentityRevocationDeniesExactMemoryRead": bool(config["provider"]), - "sameTitleScopePagination51": bool(config["provider"]), - "performance": probe_measurement(probe.stdout), - }) - finally: - if process.poll() is None: - control.write(b"stop\n") - control.flush() + ) as client, + ): + for _ in range(100): + if process.poll() is not None: + raise HarnessFailure("startup", (case / "server.log").read_text(encoding="utf-8")[-3000:]) try: - process.wait(timeout=30) - except subprocess.TimeoutExpired: - process.kill() - process.wait(timeout=10) - control.close() + if client.get("/health/ready").status_code == 200: + break + except httpx.HTTPError: + pass + time.sleep(0.2) + else: + raise HarnessFailure("readiness_timeout") + result = client.post( + "/v1/scopes", + json={ + "title": "Desktop synthetic fixture", + "summary": "Dedicated no-model validation", + "idempotency_key": "desktop-fixture-scope", + }, + ) + result.raise_for_status() + scope_id = result.json()["scope_id"] + fixture = { + "compatibility_profile": compatibility, + "response_loss_path": config["response_loss_path"], + "reader_token": config["reader_token"] if config["provider"] else None, + "identity_change_path": config["identity_change_path"] if config["provider"] else None, + "endpoint": endpoint, + "scope_id": scope_id, + "token": token, + "ca_pem": (temp / "ca.pem").read_text(encoding="utf-8") if tls else None, + } + fixture_path = case / "client.json" + fixture_path.write_text(json.dumps(fixture), encoding="utf-8") + probe = subprocess.run( + [str(executable), str(fixture_path)], + capture_output=True, + text=True, + timeout=120, + creationflags=flags, + ) + if probe.returncode: + raise HarnessFailure(mode, probe.stderr[-2000:]) + client.get("/health/live").raise_for_status() + reports.append({ + "mode": mode, + "result": "passed", + "serverAliveAfterClientExit": True, + "committedWriteWithLostResponseUnknownWithoutReplay": True, + "providerIdentityChangeInvalidatesContext": bool(config["provider"]), + "sameIdentityRevocationDeniesExactMemoryRead": bool(config["provider"]), + "sameTitleScopePagination51": bool(config["provider"]), + "performance": probe_measurement(probe.stdout), + }) report = { "environment": { "os": platform.platform(), diff --git a/docs/en/development/releasing.md b/docs/en/development/releasing.md index 0300298b75..1f76f902e9 100644 --- a/docs/en/development/releasing.md +++ b/docs/en/development/releasing.md @@ -56,6 +56,7 @@ the website lists stable releases only. `make docs-test` also runs this check as | Source | Managed value | | --- | --- | | `openapi/powercontext.yaml` | `info.version`, the prepared release version. | +| `release/upgrade-support.json` | Target release version; source versions and the support policy require compatibility review. | | `src/powercontext/http/_generated/` | `API_VERSION` in `operations.py` and `info.version` in `schema.py`, regenerated through `make api-generate`. | | `README.md`, `README_CN.md`, `README_JP.md` | Current release prose, pinned package installation commands, and matching Agent integration tag. | | `docs/en/docs/get-started/quickstart.md`, `docs/zh/docs/get-started/quickstart.md` | Current release and matching installation/tag instructions. | @@ -126,6 +127,64 @@ installation pins are synchronized. ## Verify before publication +### Database upgrade support + +`release/upgrade-support.json` declares the target version, source-version selection policy, backends, and +configurations. `automatic_sources` pins `source_version: "1.3.0"` with the `fixed-version` strategy and expands it +across SQLite, embedded seekDB, and OceanBase. The target is the actual wheel built by CI. Future releases such as +1.4.1 and 1.5.0 continue testing from 1.3.0 automatically; intermediate releases are not added as sources. Source +wheel URLs and SHA-256s are resolved from public PyPI and recorded with the target artifact. This fixed baseline +alone does not certify databases created/populated by other releases; use explicit cases for additional claims. +The current `upgrade-required` policy mandates positive acceptance on all three backends while keeping upgrade +support unverified. Every profile points to `tests/release_upgrades/test_full_server.py`; missing migration support +must fail. Neither a fresh-install-only declaration nor correct rejection of an old database satisfies this gate. +After the full migration implementation and acceptance pass, review the declaration, set `supported-upgrades`, and +regenerate the installation notices before publication. Both modes run the same mandatory acceptance matrix. +The suite contract and evidence requirements are in `tests/release_upgrades/README.md` in the repository. + +`make check` and `make release-check` validate this policy and the bilingual installation notices. Changes to +managed persistence inputs require a compatibility review with an updated digest and rationale. A digest is a +review trigger, not evidence that a migration is correct. Managed text hashes normalize CRLF to LF so checkout +line endings do not require a new review; distribution hashes always cover the exact archive bytes. `make version-bump` also updates the policy target; +regenerate the support notices with `uv run python scripts/upgrade_release_gate.py --write-docs` after matrix changes. + +Build exactly one wheel and its matching source distribution into `dist/`, then execute the declared upgrade cases: + +```bash +make upgrade-release-test +uv run python scripts/run_upgrade_release_gate.py --release --release-notes /path/to/release-notes.md +``` + +The publication input directory must contain exactly one wheel and one matching `.tar.gz` source distribution; +leftover distributions and unexpected files fail validation (`uv`'s `.gitignore` is not an artifact). The runner checks +source metadata and builds a wheel from the source archive, comparing all member contents except the derived +`RECORD` listing. A stale source archive with the same version therefore cannot bypass validation. Archive timestamps +and compression do not affect this comparison. A build-tool change that alters wheel contents requires rebuilding +the complete pair, not ignoring the mismatch. + +The runner tests a private snapshot, records exact artifact SHA-256s in `publication.json`, and exports only passing +files to the evidence directory's `validated-dist/`. CI uploads that set. `make publish` uses `--release --publish` to +upload those exact paths in the same invocation; subsequent changes to the original `dist/` cannot join the upload. +No packages are uploaded by validation commands without `--publish`. + +The release check requires the actual wheel version to match the policy and the release notes to include the exact +English support declaration from the installation guide. It installs each exact source release and the target wheel +in separate environments. Missing tests, skipped backends, failures, and missing evidence block the check. Static +policy validation alone does not establish an upgrade path. Ordinary PR builds may have a development +version; the `--release` check must run against the final tagged wheel without overriding its version. + +The **Upgrade release gate** CI job requires successful public migration commands, full Server readiness, target +startup, preserved records and new writes on real 1.3.0 databases. Limited bundle verification with +`server_ready=false` fails. Every backend runs even if another backend fails, with separate logs and results. +seekDB runs embedded in a temporary runner directory. PR and publication jobs start a disposable OceanBase CE +container, wait for its MySQL tenant, archive diagnostics, and remove it even on failure. No external database +credentials are needed. Unavailable backends fail instead of being skipped. Historical rejection regressions are +not part of the positive acceptance gate. Administrators must make this job required in branch protection. +The release workflow gates distribution upload, PyPI publication, and release assets on the acceptance result and +archives its evidence. It runs after `release.published`, so it cannot prevent the earlier manual publication of +the GitHub Release page: run the command above before that action. `make publish` requires +`UPGRADE_RELEASE_NOTES=/path/to/release-notes.md` and runs the same gate before uploading packages. + Run the checks for the complete release changes, including generated contracts and documentation: ```bash diff --git a/docs/en/development/remote-access-implementation.md b/docs/en/development/remote-access-implementation.md index f0fe6c4b07..79077568d8 100644 --- a/docs/en/development/remote-access-implementation.md +++ b/docs/en/development/remote-access-implementation.md @@ -141,17 +141,7 @@ SQLite URI interpretation; uppercase schemes, leading spaces, and literal filena storage. Pooling keeps one connection for nonpersistent storage; offline maintenance, cursor-secret persistence, and subprocess workers consume that same persistence classification. -The unified migration implementation from [RFC #1771](../rfcs/1771-unified-database-migrations.md) currently -covers only a registered four-table Artifact bundle. It does not manage `pc_server_identity`, gate ordinary Server -startup, or establish complete Server readiness; its maintenance commands reject a complete business database with -unmanaged tables. Do not use that partial bundle to migrate a Server database. - -When unified migration takes ownership of the complete Server schema, identity table creation must move from startup -DDL into an immutable managed revision, with schema verification before Runtime composition. If discovery is already -released, the supported historical baseline must include the existing identity table and preserve its singleton value; -if still unmerged at framework enablement, discovery must ship that revision alongside the model. Deployment identity -remains distinct from `pc_schema_revision`, and schema adoption or upgrades must not rotate `server_id`. Clone rotation -remains an explicit offline operation. No independent schema-version or migration-readiness marker is added for identity. +[Full Server migration](../docs/operate/database-migrations.md) includes `pc_server_identity` in the frozen 1.3.0 baseline and shared revision chain, checking the schema before Runtime composition. Upgrades preserve the existing singleton value. Deployment identity remains independent of `pc_schema_revision`; upgrading does not rotate `server_id`. Clone rotation remains an explicit offline operation, with no separate identity schema version or readiness marker. A partial Artifact bundle cannot establish full Server readiness. Treat a restored backup as the same deployment and keep its ID. When a backup is used to create an independent clone, stop every Server process using the clone database and rotate only the clone: diff --git a/docs/en/docs/get-started/install-and-run.md b/docs/en/docs/get-started/install-and-run.md index 2a06f4215a..b0f1298546 100644 --- a/docs/en/docs/get-started/install-and-run.md +++ b/docs/en/docs/get-started/install-and-run.md @@ -174,40 +174,42 @@ For a long-running process, Docker, authentication, or remote access, continue w ## Update or replace an installation -Before upgrading an existing deployment, back up its database and configuration. Version 1.1.0 upgrades legacy -tag-table constraints during Server startup to support Topic Memory tags. Stop the old Server instances and start -one upgraded instance first so the schema upgrade completes before other instances connect. Databases from before -1.0.0 also need the [Artifact processing migration](../operate/artifact-processing-migration.md) if it has not already -been completed. Existing collection-based Memory databases also require the [offline Atomic Memory migration](../operate/atomic-memory-migration.md) -for existing collection databases. Complete any required Artifact processing migration first, stop all writers, then -apply and verify the Atomic Memory migration. Start the new service only after verification returns `ready: true`. -Upgrade the Server, clients, and Agent integrations together. -The Dashboard must be explicitly enabled; static Bearer authentication is optional for local use. See -[Deploy the Server](../operate/deploy-server.md). Remote plaintext HTTP connections require explicit client consent; -see [Connect to a remote Server](../operate/connect-remote-server.md). + +Upgrades from 1.3.0 require passing full Server acceptance on SQLite, seekDB and OceanBase. This migration path is not yet verified; do not upgrade existing databases. + + +Keep existing deployments on their working version until their source version and backend have an accepted upgrade +path. To evaluate a fresh installation, use a separate program environment, database location, and port. Replacing +the installed package does not migrate its database. Backups do not make an unsupported migration supported. + +The support declaration is checked against the release matrix and installed-artifact acceptance; see +[Preparing a release](../../development/releasing.md). Domain-specific +[Processing](../operate/artifact-processing-migration.md) and +[Atomic Memory](../operate/atomic-memory-migration.md) maintenance guides do not establish full Server readiness. Review the [Dream coordinated upgrade requirements](../workflows/review-dream-revisions.md#coordinated-upgrade) before upgrading an existing database. The unified Candidate routes replace `/v1/artifact-candidates/*` with `/v1/candidates/*` without aliases, and Dream Clients require contract 2. Existing Dream storage needs an explicit -migration after unfinished runs have drained. The current unified migration bundle reports `server_ready=false`; -it does not provide a complete production Server upgrade path. Atomic Memory verification alone does not establish -that readiness. Do not bypass `migration_required` or `migration_framework_not_ready`. +migration after unfinished runs have drained. [Full Server migration](../operate/database-migrations.md) requires +`state=ready` and `server_ready=true`; partial Artifact or Atomic Memory verification cannot replace it. Do not bypass +the migration gate. -To upgrade to 1.4.0: +To install 1.4.0 in a separate environment for a fresh deployment: ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv venv /path/to/powercontext-preview +uv pip install --python /path/to/powercontext-preview/bin/python "powercontext[cli,server]==1.4.0" ``` -To replace the installed tool with another Git ref: +To install another Git ref in the separate environment: ```bash -uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@" +uv pip install --python /path/to/powercontext-preview/bin/python "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@" ``` -Update each installed host using its [integration guide](../integrations/index.md) and the same ref. Restart the Server and open a new host session -after updating. Existing SQLite data remains in the user data directory unless `POWERCONTEXT_HOME` or the database URL -changes. +Use the matching ref for clients and Agent integrations connected to the fresh deployment. Preserve the original +deployment's database location and configuration; do not point the new Server at it as an installation check. +On Windows, the virtual environment interpreter is at `Scripts/python.exe`. ## Install a Python role diff --git a/docs/en/docs/operate/artifact-processing-migration.md b/docs/en/docs/operate/artifact-processing-migration.md index a80bb95686..9f342b71c1 100644 --- a/docs/en/docs/operate/artifact-processing-migration.md +++ b/docs/en/docs/operate/artifact-processing-migration.md @@ -9,6 +9,15 @@ marker is rejected before background processing starts. ## Plan and apply +For databases managed by `pc_schema_revision`, this command delegates to the registered +[Processing configuration maintenance](database-migrations.md#processing-configuration-maintenance) operation. +Its plan/apply/verify responses use the unified migration format. Apply additionally requires the reviewed +`--plan-id`, explicit `--backup` choice and `--yes` for automation, with the same backup declarations and +OceanBase coordination options as `db-migrate`. Complete any pending full Server upgrade first. +The domain-only procedure below applies to databases without a registered revision. +As with `db-migrate`, an explicit environment file is authoritative; otherwise the command discovers `.env` in the +current directory before inspecting ownership and executing the selected path. + Inspect the plan with the deployment configuration that will run after migration: ```shell diff --git a/docs/en/docs/operate/atomic-memory-migration.md b/docs/en/docs/operate/atomic-memory-migration.md index eb970257b4..5bbf970505 100644 --- a/docs/en/docs/operate/atomic-memory-migration.md +++ b/docs/en/docs/operate/atomic-memory-migration.md @@ -14,6 +14,16 @@ legacy entry tables; complete per-entry verification is the job of `verify`. ## Run the maintenance task +On a database with `pc_schema_revision`, or a recognized complete 1.3.0 Server baseline, this compatibility command +delegates `plan`, `apply` and `verify` to `server db-migrate --bundle server`. It returns the shared plan/result format +and uses the same maintenance lock, backup policy and recovery evidence. Review the plan, then pass its `--plan-id`, +an explicit `--backup` policy, `--yes` and `--maintenance-confirmed` when changes are required. OceanBase also requires +the fixed-host coordination/evidence options described in [Database migrations](./database-migrations.md). +Already-ready storage is a read-only no-op. Candidate and Owner decision files apply only to the unmanaged legacy +conversion and are rejected on this registered Server path. This does not register pre-1.3.0 Memory layouts. + +The remaining instructions describe domain-only conversion for unmanaged databases: + Inspect the read-only plan with the new deployment's configuration: ```shell @@ -231,6 +241,15 @@ Memory content restoration is a separate operation. ## Rebuild the current search projection +On managed Server storage, this command delegates to +[`db-migrate --bundle atomic-projection`](./database-migrations.md#atomic-memory-projection-maintenance). +Use `--action plan` to inspect without writes, then apply with `--plan-id`, explicit `--backup`, `--yes` and +`--maintenance-confirmed`. The registered no-vector repair retains a recovery point, commits the complete repair +in one transaction and returns the shared Server result. `--action verify` checks current readiness; an already +ready projection is a read-only no-op. This path does not register vector-profile changes. + +The remaining steps describe standalone repair on unmanaged storage: + After changing the embedding model, profile, dimension or normalization, or repairing current search data, back up the database and stop every API, host and Worker, their automatic restart and all input writes. Use the target deployment configuration: diff --git a/docs/en/docs/operate/database-migrations.md b/docs/en/docs/operate/database-migrations.md index 5e40364468..5096920f88 100644 --- a/docs/en/docs/operate/database-migrations.md +++ b/docs/en/docs/operate/database-migrations.md @@ -12,20 +12,37 @@ The design follows [RFC #1771](https://github.com/oceanbase/powercontext/pull/17 ## Current availability -`powercontext server db-migrate status/plan/apply/verify` use the configured persistent SQLite, embedded seekdb, or -OceanBase MySQL-mode database. They do not accept arbitrary test-bundle paths. Explicit environment files are authoritative -for maintenance commands; use the deployment's environment file rather than relying on temporary shell overrides. -The default `--bundle artifacts` ships frozen resources for Phase A acceptance of four Artifact tables: `pc_artifacts`, `pc_artifact_heads`, -`pc_artifact_candidate_versions`, and `pc_artifact_tags`. Only empty targets and registered historical layouts are accepted. -A complete Server database containing unmanaged objects is rejected. - -`ready` means this migration bundle passed its schema and data checks. Output explicitly includes -`readiness_scope=registered_bundle` and `server_ready=false`: it does not establish complete Server, index, legacy-task, or -cluster readiness. Do not substitute it for a production upgrade or initialization of the complete business database. -Full startup gates and complete historical baselines remain outside this bundle. Isolated four-table acceptance passed -through an ODP endpoint on OceanBase AI Database 4.6.3, covering coordinator exclusion, acknowledged recovery after -committed DDL, data preservation, and repeat execution. This does not establish complete Server readiness, automatic -detection of unfinished remote DDL after a crash, or full Fork restoration acceptance. +`powercontext server db-migrate status/plan/apply/verify` default to `--bundle server` and use the configured persistent SQLite, embedded seekDB, or OceanBase MySQL database. Explicit environment files are authoritative; arbitrary bundle paths are not accepted. + +The full Server path registers actual 1.3.0 storage as `s1300 → s1400 → s1401 → s1402`, with Code enabled or disabled on all three backends and a separate profile for unit-normalized L2 vectors. SQLite and OceanBase keep Code in a separate local cache. Embedded seekDB Code adds three tables through a frozen schema extension captured from installed 1.3.0; maintenance preserves existing graph rows and creates the registered tables for a new Code-enabled database. The path covers the complete schema, Candidate renaming and retained history, Dream task formats, Processing ownership, search projections, and existing Source references. Unknown layouts, extra objects and unregistered sources fail before writes. Older Memory collection conversion is outside this path. + +Only successful schema, domain, projection, and task validation returns `state=ready`, `readiness_scope=server`, and `server_ready=true`. Publication also requires installed-artifact acceptance on all three backends. An implemented path is not evidence that a release passed acceptance; consult the installation guide and release gate for support status. + +Stop all writers and drain queued/running 1.3.0 Dream tasks before upgrading: the target cannot execute the old prompt contract. Terminal task payloads and pending Processing counters are preserved; old worker leases are fenced. Registered capability additions retain the old automatic-processing choices instead of replacing ownership with the current configuration. + +Old Candidate tables are retained as `pc_retained_dream_candidate_heads` and `pc_retained_dream_candidate_versions` for migration-copy verification. They and the backup are not live write targets; subsequent business changes are not compared against old snapshots. New historical Artifact provenance fields remain unknown, and existing Source references are preserved. Projection rebuilds and verification scan their tables; budget downtime and capacity for backups and retained copies. + +Revision `s1401` registers `pc_memory_artifact_archive` in the managed target schema on all three backends. It stores frozen Memory history for offline maintenance and has no public Artifact read path. Fresh databases start with an empty archive; repeat migration preserves its bytes, and runtime admission verifies its registered structure. This revision does not register additional historical source layouts. + +Revision `s1402` registers portable restore receipts and preserves microsecond precision for the portable Artifact, +Profile, Topic publication, and tag timestamps on MySQL-compatible backends. These schema changes run through explicit +maintenance for existing managed storage. Runtime startup does not add the receipt table or alter timestamp columns. + +An interrupted [portable restore](portable-archive.md) keeps normal startup and workers blocked until its projections +are complete. Retry `powercontext archive restore --yes` with the same validated bundle. Only this explicit +recovery path can open an incomplete projection set, after validating the registered layout and the configured vector +profile; it does not start workers. Recovery without a pending restore receipt still performs full readiness checks. + +Ordinary Server startup checks readiness without repairing an existing database. Empty local persistent SQLite/seekDB storage initializes through the same revision chain; OceanBase requires an explicit maintenance job first. In-memory SQLite remains transient runtime storage. A changed schema or inconsistent projection requires maintenance, not restart-based repair. + +Server identity initialization checks registered tables without issuing DDL on managed storage. Creating the initial +identity value and explicitly rotating a stopped clone's identity retain their existing transactional behavior. + +Vector maintenance requires the exact historical embedding profile: profile ID, model, dimension, distance and normalization. It preserves historical embeddings and Topic retrieval shape without calling an embedding provider. The registered SQLite transition adds and backfills Atomic Memory's vec0 helper and transactional triggers; native backends retain the registered vector columns and HNSW parameters. `pc_server_storage_profile` stores the complete embedding configuration, not another revision or recovery journal. Generic storage readers validate this declaration, and Server startup uses the configured or injected model's actual profile. Missing managed helpers and profile changes are rejected before startup repair or writes. + +Empty persistent SQLite/seekDB Server storage with vectors initializes through the selected profile of the same chain. Existing unmanaged storage requires explicit maintenance and an exact registered source layout. Changing configuration cannot bypass admission, enable vectors on no-vector managed storage, or implicitly add vector/Code objects. Enabling embedded Code on an already managed database without its tables requires a separately registered configuration transition; startup and the standalone Code CLI reject it. The release gate requires all twelve combinations of backend, vectors enabled or disabled, and Code enabled or disabled. Code cases build an index with installed 1.3.0, preserve its cache files and embedded graph rows, and query the original fingerprint and symbols after the upgrade. Vector cases preserve nonempty historical Atomic and Topic embeddings, verify semantic reads and new writes through the installed target, reject profile drift, and compare fresh/upgraded storage. SQLite also tests missing-helper rejection and backup restoration. A successful no-vector case does not establish vector support; every declared case must pass for the actual target artifact. + +`--bundle artifacts` retains the partial Artifact prototype chain starting at `p0001`; `--bundle topic-memory-recovery` covers only registered Topic recovery layouts. Both report `server_ready=false`. Neither establishes full Server readiness, and their revisions cannot be stamped as full Server revisions. MySQL-compatible schema reflection supports arithmetic CHECK expressions with binary `+`, `-`, `*`, `/`, `%`, unary signs, and nested grouping, preserving arithmetic, comparison, and boolean precedence. Unsupported CHECK @@ -33,10 +50,102 @@ operators/functions remain rejected; their errors identify the reflected table, construct instead of misreporting balanced parentheses. Parsing an expression does not register a historical layout or bypass migration/data-integrity checks. -The existing `server processing-migrate` remains available. Follow -[Migrate Artifact processing state](artifact-processing-migration.md); it does not yet forward to the unified entry point. +The existing `server processing-migrate` delegates managed databases to `db-migrate --bundle processing`. +Databases without a registered revision retain their domain-only migration command. +Follow [Migrate Artifact processing state](artifact-processing-migration.md). See [Deploy the Server](deploy-server.md) for service deployment. +`server atomic-memory-migrate` delegates managed databases and recognized complete 1.3.0 Server baselines to +`db-migrate --bundle server`, including its reviewed-plan, backup and recovery requirements. It preserves domain-only +conversion for unmanaged legacy Memory layouts. Decision files for that conversion cannot modify the registered Server +chain. The compatibility command returns the shared Server plan/result for managed storage; an already-ready apply is a +read-only no-op. See [Migrate to Atomic Memory](atomic-memory-migration.md). + +## Initialization and maintenance ownership + +The following inventory identifies the owners of schema changes and projection repair. "Managed" means that the +database contains `pc_schema_revision`, including an unknown revision. Passing a partial domain check does not admit +that database for Server use. Full profile admission checks the registered Server layout and domain state first. + +| Entry point or helper | Managed storage | Unmanaged storage | +| --- | --- | --- | +| `SQLiteProfile.open`, `SeekDBProfile.open`, `OceanBaseProfile.open`; `schema.create_tables` | Full profile readiness checks precede use; selected tables must already exist | Retain selected-table creation and domain admission | +| Runtime composition: Skill distribution, tag families, Scope search, revision metadata and Artifact head columns | Skip compatibility DDL and backfills; the registered Server chain owns these objects | Retain the existing compatibility helpers | +| Runtime Atomic Memory authority/current indexes and SQLite FTS helpers | Validate current data; repair through `db-migrate --bundle atomic-projection` after layout admission | Retain domain initialization and offline projection repair | +| Runtime Experience/Skill searchable text and FTS indexes | The registered Server domain step builds and validates projections; startup does not rebuild them | Retain domain initialization and projection recovery | +| Runtime Topic Memory indexes, recovery tables and retrieval shape | Initialize with `validate_only=True`; partial Topic recovery rejects managed storage | Retain domain initialization; registered partial recovery covers only its declared layouts | +| Processing schema and ownership configuration | Validate at startup; change configuration through `db-migrate --bundle processing` | Retain domain bootstrap and `processing-migrate` | +| `ServerIdentityRepository.initialize` | Check the identity table without DDL; identity value creation and explicit rotation are ordinary transactions | Retain concurrent-safe identity table initialization | +| Offline Memory archive | The Server chain registers its structure at `s1401`; runtime admission checks it | Legacy offline Memory conversion owns its archive and conversion receipts | +| `atomic-memory-migrate` and `processing-migrate` | Delegate to the shared Server or registered maintenance plan | Retain their domain-only behavior; recognized historical Server storage must use the full upgrade first | +| Vector objects | The selected Server profile owns schema, helper backfill and exact configuration admission; changing embedding spaces requires a separate transition | A complete released vector layout can be explicitly migrated; standalone unmanaged SDK storage retains domain initialization | +| Embedded seekDB Code objects | The registered Code profile creates and validates the three tables and full-text index; Code startup never repairs missing managed objects | Recognize the complete released Code-enabled layout for explicit migration; retain standalone unmanaged Code bootstrap | +| Separate Code cache on SQLite/OceanBase | Preserve existing cache files; main database follows the shared chain | Code service owns local cache creation and indexing | + +These helpers remain available to their owning runtime or maintenance operations; calling a low-level helper directly +does not replace profile admission, a reviewed maintenance plan, or backup and recovery requirements. Future changes to +managed objects belong in the registered revision/domain steps and validators, rather than another startup helper. +Read-only SQLite inspection and dedicated maintenance connections can inspect historical storage without business +initialization; successfully opening an inspection connection does not establish Server readiness. + +Code's embedded seekDB store adds `pc_code_generations`, `pc_code_nodes`, `pc_code_edges` and `ix_code_search` to the +deployment database. Other Code deployments use a local SQLite cache. These are derived indexes owned by the Code +service; the frozen extension registers their schema for maintenance and full Server admission. A partial or changed +extension fails validation. Its existence does not permit implicit managed-schema expansion. +Code caches on SQLite/OceanBase do not change the main database layout. + +## Processing configuration maintenance + +`--bundle processing` registers an offline ownership-configuration operation on a complete, current Server database. +It does not advance `pc_schema_revision` or adopt an older schema. Complete the full Server upgrade first. +Use the deployment's new configuration and a new migration ID for each mode switch: + +```bash +powercontext server db-migrate plan --bundle processing --env-file .env \ + --migration-id global-to-dedicated-1 --backup auto +powercontext server db-migrate apply --bundle processing --env-file .env \ + --migration-id global-to-dedicated-1 --backup auto --plan-id PLAN_ID \ + --maintenance-confirmed --yes +powercontext server db-migrate verify --bundle processing --env-file .env \ + --migration-id global-to-dedicated-1 +``` + +The operation uses the same target lock, backup policies, backend connections, and stopped-writer declarations as +schema migration. OceanBase still requires one fixed maintenance host, `--evidence-dir`, and +`--lock-coordination single-host`; recovery requires the exact previous-run acknowledgment when reported. +Local service switching is not part of this operation. Stop all writers and automatic restarts before applying. + +All mode-switch phases commit together. Failure before commit rolls back the ownership declaration and lease +invalidation together. Reopen the plan after an interruption and resume with the original configuration, migration ID, +backup policy, and evidence directory; the original maintenance window and recovery point are retained. +Successful retry does not invalidate leases again. Full Server validators run before and after the change. +An already matching configuration is a read-only no-op. + +## Atomic Memory projection maintenance + +`--bundle atomic-projection` repairs the current Atomic Memory rows and SQLite FTS helper on a current managed +Server database in the registered no-vector configuration, including Code with a separate cache on SQLite/OceanBase. It preserves the Server revision, +immutable content/history, lifecycle, formal tags, permissions and Source/Processing state. + +```bash +powercontext server db-migrate plan --bundle atomic-projection --env-file .env --backup auto +powercontext server db-migrate apply --bundle atomic-projection --env-file .env \ + --backup auto --plan-id PLAN_ID --maintenance-confirmed --yes +powercontext server db-migrate verify --bundle atomic-projection --env-file .env +``` + +The operation shares Processing maintenance's target lock, backup, stopped-writer declarations and durable recovery +transaction. Plan and verify are read-only. Apply repairs only derived rows and commits after full Server validation; +invalid authority or an unregistered layout is rejected. A ready projection is a read-only no-op. Interruption before +commit rolls back the repair; resume from a new plan with the original artifact, configuration, backup policy and +evidence directory to reuse its recovery point. Finish any incomplete maintenance operation before starting another. +OceanBase requires the same fixed-host coordination and previous-run acknowledgment as other maintenance. + +Inspection and repair scan the relevant Atomic history, heads, tags, lineage and current projection. Keyset batches +bound row buffering; this is an offline whole-projection operation. It does not register vector-profile changes or +embedded seekDB Code configurations. The compatibility command `server atomic-memory-rebuild-projection` delegates managed +databases to this bundle and supports `--action plan/apply/verify` with the same confirmation and backup options. + ## Topic Memory recovery bundle Select `--bundle topic-memory-recovery` for the versioned `powercontext.topic-memory-recovery.v1` conversion. It recognizes @@ -44,6 +153,9 @@ the pre-recovery budget layout and the attempts-table layout from implementation and retains terminal blocks and accepted receipts. It verifies only its registered Topic objects in an existing Server database, including shared processing-lease fencing; other business tables remain untouched. Its output still reports `server_ready=false`. +This partial bundle is restricted to unmanaged storage. Any database containing `pc_schema_revision` is rejected before +backup or writes, including an unknown revision. Use `--bundle server` or a registered managed maintenance operation. + Stop all APIs, Workers, schedulers and SDK writers first, and suspend automatic restarts. Review and apply one plan: ```bash @@ -102,6 +214,11 @@ Plans bind target identity, source/target revisions, actual schema, frozen resou service scope, and migration coordination. Diagnostics do not print configuration or credential values. Expected failures return stable JSON categories and a nonzero exit status. +Configuration binding includes the target's canonical Processing ownership mode, capabilities and declared automatic +bindings. Changing those declarations requires a new plan; an accepted plan cannot be applied under a different +Processing declaration. Plan binding alone does not validate or migrate historical Processing state; the full Server +bundle separately validates the stored declaration and pending intents through its registered domain steps. + Within the supported acceptance scope, interactive users can invoke `apply` directly. PC-managed backup is the default. The command displays the plan, backup method, maintenance impact, and stopped-write responsibility, then asks for one confirmation before execution. It does not ask about each SQL statement: @@ -244,6 +361,12 @@ initialization reports `not_required`. Unsupported, failed, or incomplete auto b It returns `backup_unsupported` or a backup failure category and never silently skips. Changing policy requires a new plan confirmation. +The installed-release SQLite acceptance suite restores the generated automatic backup to a separate database. +It compares the complete source schema and row digests, reads historical records with the installed source Server, +then upgrades the restored copy and verifies target reads, retrieval and new writes. Code-enabled cases preserve +their separate immutable caches as well. This exercise is specific to SQLite; it does not verify seekDB or +OceanBase backup restoration. Keep the original recovery point until your own restored deployment is verified. + A separate `BackupProvider` reuses backend settings and maintenance connections. SQLite uses Online Backup API to create an independent file, including committed WAL, and checks integrity. seekdb and OceanBase executors reuse `ForkBackupProvider`, preferring native `FORK DATABASE`. The current executor rejects automatic `FORK TABLE`: enabling @@ -291,15 +414,38 @@ activation suppressed until an explicit `service start`; an unverified migration They manage only the current user's PC-owned local service, not a cluster or other clients. `stop` suppresses automatic restart during maintenance; `start` restores startup. One `restart` cannot replace stopped-write migration. -The complete managed-upgrade design drains requests, stops the service and automatic restarts, takes the migration lock, -reinspects, applies backup policy, migrates, verifies, switches the launch definition to the accepted new executable, and -starts a previously running service with readiness checks. Migration failure keeps it stopped; a previously stopped -service stays stopped. Database verification and service startup are reported separately. Resolve startup failures without -rerunning a successful migration. - -`--manage-service` always covers only the accepted local service. It cannot replace `--maintenance-confirmed` for other -writers and shared nodes. The Phase A partial bundle does not establish complete Server readiness: when persistent changes -are needed, `--manage-service` returns `service_unsupported` rather than switching a business service. +For `--bundle server`, `--manage-service` binds the owned local registration and target executable to the reviewed plan. +Use the registered environment file and data directory; a different effective Server configuration is rejected before +stopping the service. Stop incoming traffic and drain work first. Apply suppresses automatic restarts, stops the local +service, takes the migration lock, reinspects, applies the backup policy, migrates and verifies, then switches the launch +definition to the accepted executable. It starts a previously running service and checks readiness; a previously stopped +service stays stopped. Migration failure keeps the old registration stopped and protected against automatic activation. + +Database verification and service startup are reported separately. `service_start_failed` with `database_state=ready` +means the schema migration succeeded; fix the startup problem and use `powercontext service start`. If execution stopped +after database verification but before switching the registration, review and apply a new `--manage-service` plan. It can +finish the service transition when the database is already ready, without rerunning completed revisions. + +`--manage-service` covers only the accepted local service. It cannot replace `--maintenance-confirmed` for other writers +and shared nodes. The partial Artifact and Topic recovery bundles reject service management when changes are needed; +an already-ready no-op does not touch the service. They cannot establish complete Server readiness or switch a business +service. Controller tests use a simulated native manager with a real SQLite migration. The native lifecycle suite also +exercises the installed CLI with isolated service registrations: it upgrades a stopped frozen SQLite baseline, checks +that it stays stopped until an explicit start, and completes service maintenance for an already-ready running database. +Each operating system requires its own native run. A frozen baseline fixture is distinct from upgrading a database +populated by an installed historical release. + +The suite also installs the actual 1.3.0 wheel in a separate environment, starts it through the native manager and +populates a Scope through HTTP. Both running and stopped service cases use the public maintenance command, verify the +backup and retained data, and confirm that registration switches to the target executable while preserving the intended +running state. The CI setup verifies the source wheel's SHA-256 and supplies +`POWERCONTEXT_NATIVE_UPGRADE_SOURCE_PYTHON`; local native runs need the same installed-source interpreter. These cases +cover SQLite service switching. Backend crash recovery and the full installed-release domain suite remain separate +acceptance requirements. + +Failure cases cover unavailable backup storage, an actual port conflict when the target service starts, and injected +`ENOSPC` when replacing its registration file. Startup/switch failures retain the stopped service and recovery record +after database verification; an explicit service start completes recovery without another migration apply. For manual deployment, stop the old service first. After a supported complete migration succeeds, ensure the launch definition points to the new environment before starting it. For foreground deployment, exit the old process and use the @@ -326,7 +472,8 @@ without every handler supporting two table layouts. Legacy tasks require inspection in the read-only plan, after stopped-write locking, after migration, and before Worker startup. Unknown formats or unobservable sources must not be reported as an empty queue. Use declared compatible consumption, idempotent conversion, or bounded draining. Preserve task IDs, idempotency keys, Scope, retries, and domain -receipts. The Phase A bundle does not yet cover these complete business checks. +receipts. The partial Artifact bundle does not cover these complete business checks. The full Server path checks its +registered 1.3.0 Dream formats and rejects queued/running old tasks before migration; terminal payloads remain intact. ## Storage layers and recovery boundaries @@ -341,15 +488,45 @@ or finding an unacknowledged previous run blocks continuation. Unavailable native file locking is rejected, including runtime fallback to a soft lock; it does not reconnect or silently weaken coordination and continue. These locks do not stop business writers. +The separate Linux OceanBase engine-recovery test requires +`POWERCONTEXT_TEST_MIGRATION_OCEANBASE_ENGINE_CRASH_ID` with the exact disposable container ID and +`POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL` with its matching loopback port and dedicated `pc_migration_probe_*` database. +It sends `SIGKILL` to observer after registered DDL, then restarts observer in the same directory and verifies the original +recovery record, required acknowledgement, historical bytes, final revision and repeated apply. It does not restart the +slim image's bootstrap script, which unfolds a snapshot and truncates logs. CI requires one executed test and zero skips. +The test uses frozen historical resources and an operator backup declaration; it does not establish installed-release +acceptance or verified backup restoration. + seekdb/OceanBase DDL may commit implicitly, so one Python transaction cannot make the entire upgrade atomic. Recovery recognizes only the registered sequence: `p0001` creates the four frozen tables and two indexes; `p0002` adds the two nullable `MEDIUMBLOB` citation columns; `p0003` removes the tag-family CHECK. Actual schema, data checks, and persisted -maintenance evidence must establish the known completed prefix before continuing. Alembic advances the version only +maintenance evidence must establish the known completed prefix before continuing. `p0004` adds exact-revision Artifact +handles. Alembic advances the version only after the revision's postconditions pass. Unknown objects, layouts, or partial transitions block execution instead of being optimistically stamped. -Repositories run after schema checks. Versioned resources and controlled rebuilds for complete business full-text/vector -indexes, plus legacy-task readiness, remain outside this acceptance bundle. +MySQL schema steps are registered in frozen layout fragments selected by the `mysql_layouts` list in `manifest.json`. +The fragments together must cover exactly the Alembic chain, without duplicate declarations. New revisions append +immutable fragments and declare their files in `revision_resources`; they do not edit previously released fragments. +Optional checkpoints compare the accumulated steps with independently +frozen complete layouts. Reflection, next-revision prefix recovery, and version-table validation use that same registered +chain; adding a revision does not require changing a second version list in the recognizer. These schema registrations +do not replace domain conversions, compatibility checks, or full Server readiness. + +New MySQL execution wrappers are also registered in `manifest.json`, using a `mysql_wrappers` object that maps a +revision to its frozen Python resource. List that resource in the revision's `revision_resources` dependencies. +The wrapper declares the same `revision` and `down_revision` as the common chain and calls +`context.config.attributes["execute_revision"](revision)`. Historical execution wrappers cannot be overridden. +The executor validates complete, linear wrapper coverage before opening a maintenance target; missing or divergent +execution history is an invalid bundle rather than a failure discovered after earlier revisions have committed. +Wrapper bytes participate in both the reviewed bundle and the revision's execution checksum. + +The reviewed plan binds the registration through the complete bundle checksum. Active maintenance requires the same +bundle for retry. Released revision resources and their recorded checksums remain immutable. +Parameter-free DDL uses statement-scoped driver execution options so CHECK remainder operators and percent-containing +SQL literals reach the backend unchanged. + +Repositories run after schema checks. The full Server `s1300 → s1400 → s1401 → s1402` chain registers exact per-statement schema transitions and recovery prefixes through `mysql_state_layouts`. Domain steps attach to the shared revision and use its connection, lock, and recovery point; verification precedes version advancement. Read-only live checks are separate from migration-copy validation. Vector configurations still require separate registration. One version table offers no generic run-ID resume. After interruption, inspect actual schema/data through read-only commands. Ambiguous state returns `recovery_required`; never blindly stamp or replace the original recovery point with a @@ -367,4 +544,5 @@ The existing Scope discovery startup migration inspects the actual search-column storage. If an earlier startup stopped after adding nullable columns or backfilling them, the next startup fills any remaining missing values from the original text and completes `NOT NULL` constraints. Columns already marked `NOT NULL` do not receive another `MODIFY COLUMN`. Original Scope titles, summaries, external references, and binding -identifiers are preserved. This compatibility repair is separate from the four-table unified migration bundle above. +identifiers are preserved. This compatibility repair remains for unmanaged profiles. Managed Server storage uses the +registered migration chain and read-only startup admission; its startup never invokes this repair to upgrade the schema. diff --git a/docs/en/docs/workflows/review-dream-revisions.md b/docs/en/docs/workflows/review-dream-revisions.md index e3e0ebae39..de9cf7fb17 100644 --- a/docs/en/docs/workflows/review-dream-revisions.md +++ b/docs/en/docs/workflows/review-dream-revisions.md @@ -21,7 +21,7 @@ The unified Candidate routes and physical table rename ship together, including Before stopping all writers, stop new Dream admissions and let the old runtime finish every queued/running Dream run. Migration rejects unfinished runs with `dream_tasks_require_drain`; it does not change frozen prompts, model configuration, retry budgets or deadlines. Completed runs and pending Candidates remain readable and reviewable after upgrade. The migration also updates Handoff/Prompt processing bindings explicitly; ordinary startup does not repair the processing manifest. An unsupported manifest blocks migration with `unknown_processing_manifest`. -Production upgrade is not enabled by the Dream contribution alone. [PR #1772](https://github.com/oceanbase/powercontext/pull/1772) must register the complete Server baseline, revision chain, legacy-task checks and backend acceptance. Its four-table SQLite prototype does not establish Server readiness. This branch rejects legacy Dream storage with `migration_required` and versioned databases with `migration_framework_not_ready` until full framework readiness is integrated; do not bypass these errors by stamping a revision or creating empty replacement tables. Registered retained tables and recovery objects remain under the framework's retention policy. +The [full Server migration bundle](../operate/database-migrations.md) registers the complete 1.3.0 baseline, revision chain and legacy-task checks for the no-vector, Code-disabled profile. Stop writers and drain queued or running legacy Dream tasks before maintenance. Startup validates the complete managed database and rejects incomplete revisions with `migration_required`; stamping a revision or creating empty replacement tables cannot establish readiness. The partial Artifact bundle still reports `server_ready=false`. Consult the installation guide for the release's verified upgrade support. Registered retained tables and recovery objects remain under the framework's retention policy. ## Prompt configuration diff --git a/docs/zh/development/releasing.md b/docs/zh/development/releasing.md index f53da6fb39..4f65ff503b 100644 --- a/docs/zh/development/releasing.md +++ b/docs/zh/development/releasing.md @@ -53,6 +53,7 @@ beta 和 RC 跳过网站条目检查。`make docs-test` 也会通过网站测试 | 来源 | 自动管理的内容 | | --- | --- | | `openapi/powercontext.yaml` | `info.version`,作为待发布版本。 | +| `release/upgrade-support.json` | 目标发布版本;来源版本和支持策略需要单独进行兼容性审查。 | | `src/powercontext/http/_generated/` | `operations.py` 的 `API_VERSION` 和 `schema.py` 的 `info.version`,通过 `make api-generate` 重新生成。 | | `README.md`、`README_CN.md`、`README_JP.md` | 当前版本介绍、固定包版本的安装命令及配套 Agent 集成 tag。 | | `docs/en/docs/get-started/quickstart.md`、`docs/zh/docs/get-started/quickstart.md` | 当前版本介绍及匹配的安装命令、tag。 | @@ -119,6 +120,54 @@ DSH README 中说明某项能力已包含在 `1.1.0` 的文字同样属于历史 ## 发布前验证 +### 数据库升级支持范围 + +`release/upgrade-support.json` 声明目标版本、来源版本选择策略、后端和配置。`automatic_sources` 使用 +`fixed-version` 策略,将 `source_version` 固定为 `1.3.0`,展开 SQLite、嵌入式 seekDB 和 OceanBase 三种后端。 +目标始终是本次 CI 构建的实际 wheel;后续发布 1.4.1、1.5.0 时,自动测试 1.3.0 到本次版本,不增加中间版本来源。 +来源 wheel 的公开 PyPI 地址和 SHA-256 与目标产物一起记录。固定起点不能证明其他版本实际生成的数据同样兼容; +如需额外声明这些路径,使用显式 `cases` 补充验收。 +当前 `upgrade-required` 策略强制执行三种后端的正向升级验收,同时保持“升级支持尚未验证”的声明。 +所有配置均使用 `tests/release_upgrades/test_full_server.py`,迁移能力缺失必须失败。仅支持新建数据库的声明或 +正确拒绝旧库,都不能满足门禁。完整迁移实现和验收通过后,审查支持范围,将模式改为 `supported-upgrades`, +并在发布前重新生成安装说明;两种模式都运行相同的必测矩阵。 +验收契约和证据要求见仓库中的 `tests/release_upgrades/README.md`。 + +`make check` 和 `make release-check` 会核对策略及双语安装说明。受管持久化文件变化时,必须审查兼容性影响, +更新摘要和理由。摘要用于触发审查,本身不证明迁移正确。受管文本的哈希会将 CRLF 归一为 LF,避免检出行尾 +差异误报兼容性变更;发行包哈希仍覆盖完整原始字节。`make version-bump` 同时更新策略中的目标版本; +支持矩阵变化后,运行 `uv run python scripts/upgrade_release_gate.py --write-docs` 更新安装说明中的支持范围。 + +将唯一的目标 wheel 和配套源码包构建到 `dist/` 后,执行声明的升级组合验收: + +```bash +make upgrade-release-test +uv run python scripts/run_upgrade_release_gate.py --release --release-notes /path/to/release-notes.md +``` + +发布输入目录必须只有一个 wheel 和一个配套的 `.tar.gz` 源码包;残留发行包和其他文件都会使检查失败, +`uv` 生成的 `.gitignore` 不作为发行产物。门禁检查源码包元数据,并从该源码包实际重建 wheel,逐项比较归档中的 +文件内容,仅排除派生的 `RECORD` 哈希清单。因此,同版本但内容过期的源码包也无法绕过检查。归档时间戳和压缩 +方式不影响比较;构建工具变化导致内容不一致时,需要重新构建整组产物,不能忽略差异。 + +门禁验收输入文件的独立副本,在 `publication.json` 中记录实际 SHA-256,并且只有全部通过才将文件输出到证据目录 +下的 `validated-dist/`,供 CI 上传。`make publish` 使用 `--release --publish` 在同一次调用中上传这些确切路径; +验收期间原 `dist/` 的变化不会混入上传。未指定 `--publish` 的验收命令不会发布软件包。 + +发布检查要求实际 wheel 版本与策略一致,发布说明包含安装指南中的完整英文支持声明。每个组合分别安装准确的 +旧版发行包和目标 wheel。测试缺失、后端跳过、执行失败或证据缺失都会阻断检查。仅通过静态策略检查不表示 +存在升级路径。普通 PR 构建允许使用开发版本;`--release` 必须验证最终 tag 构建的 wheel,不能覆盖版本号。 + +CI 中的 **Upgrade release gate** 要求真实 1.3.0 数据库通过公开命令迁移、完整 Server 就绪验证、目标版本启动、 +历史数据读取和新增写入。局部迁移验证返回 `server_ready=false` 也会失败。一个后端失败后,仍继续执行其他后端, +分别保存结果和日志。seekDB 直接在 runner 临时目录中以嵌入式启动;PR 和发布工作流均启动隔离的 OceanBase CE +容器,等待 MySQL 租户就绪,归档诊断并在测试失败时也清理容器,不依赖外部数据库凭据。 +任一后端无法启动都使检查失败,不能跳过。历史拒绝行为回归不参与正向升级门禁。 +仓库管理员需要在分支保护中将该检查设为必需。 +发布工作流只有验收通过才会上传发行包、发布 PyPI 和上传 Release 附件,并归档验收证据。工作流在 +`release.published` 后运行,无法阻止之前手动发布 GitHub Release 页面,因此必须先运行上述命令再发布页面。 +`make publish` 要求提供 `UPGRADE_RELEASE_NOTES=/path/to/release-notes.md`,并在上传软件包之前执行相同门禁。 + 针对本次发布的完整变化运行检查,包括生成的契约和文档: ```bash diff --git a/docs/zh/development/remote-access-implementation.md b/docs/zh/development/remote-access-implementation.md index 7c70469b80..8f2b656aa6 100644 --- a/docs/zh/development/remote-access-implementation.md +++ b/docs/zh/development/remote-access-implementation.md @@ -130,15 +130,7 @@ SQLite 存储分类使用 dialect 的实际连接参数和解码后的 URI,包 不会归一化大写 scheme、前导空格或文件名中的控制字符。`SQLiteConfig.is_in_memory` 区分内存存储;`SQLiteConfig.is_persistent` 同时排除内存与临时 存储。非持久存储的连接池保留一个连接;离线维护、cursor secret 持久化及子进程 worker 统一使用该持久性分类。 -[RFC #1771](../rfcs/1771-unified-database-migrations.md) 对应的统一迁移实现目前只覆盖已注册的四张 Artifact 表。 -它尚未管理 `pc_server_identity`、接管普通 Server 启动检查或证明完整 Server readiness;维护命令会拒绝包含未管理表的 -完整业务数据库。不能使用这个局部 bundle 迁移 Server 数据库。 - -统一迁移接管完整 Server schema 时,identity table 创建必须从启动 DDL 移到不可变的受管 revision,并在 Runtime -组装前验证 schema。若 discovery 已发布,支持的历史 baseline 必须包含现有 identity table,并保留 singleton 值; -若在框架启用时仍未合并,discovery 必须随 model 一起提交该 revision。deployment identity 与 `pc_schema_revision` -保持独立,schema 纳管或升级不能轮换 `server_id`。clone 轮换仍是显式离线操作,identity 不另设 schema version 或 -migration readiness marker。 +[完整 Server 迁移](../docs/operate/database-migrations.md)已将 `pc_server_identity` 纳入冻结的 1.3.0 基线和共享版本链,并在 Runtime 组装前检查结构。迁移保留原有 singleton 值;deployment identity 与 `pc_schema_revision` 独立,升级不会轮换 `server_id`。clone 轮换仍是显式离线操作,identity 不另设 schema version 或 readiness marker。局部 Artifact bundle 不能证明完整 Server 就绪。 把备份恢复为原 deployment 时应保留原 ID。若用备份创建独立 clone,请停止所有使用 clone 数据库的 Server 进程,然后只在 clone 上轮换: diff --git a/docs/zh/docs/get-started/install-and-run.md b/docs/zh/docs/get-started/install-and-run.md index 1af6e476ab..225f8f0f89 100644 --- a/docs/zh/docs/get-started/install-and-run.md +++ b/docs/zh/docs/get-started/install-and-run.md @@ -160,35 +160,37 @@ Agent 诊断见[各自的集成文档](../integrations/index.md);Server 状态 ## 更新或替换安装 -升级已有部署前,先备份数据库和配置。1.1.0 会在 Server 启动时升级旧标签表约束,以支持 Topic Memory 标签。 -先停止旧 Server 实例,再启动一个升级后的实例,待结构升级完成后再启动其他实例。对于 1.0.0 之前的数据库, -如果尚未完成[Artifact 处理迁移](../operate/artifact-processing-migration.md),还需要先执行该迁移。 -升级到 1.4.0 时,已有集合式 Memory 数据库还需完成[Atomic Memory 离线迁移](../operate/atomic-memory-migration.md)。 -先完成所需的 Artifact 处理迁移,再停止所有写入进程,执行 Atomic Memory 迁移并验证 `ready: true` 后启动新服务。 -Server、客户端和 Agent 集成需一起升级。Dashboard 需要显式启用,本地使用可选择静态 Bearer 认证, -见[部署 Server](../operate/deploy-server.md);远程明文 HTTP 连接需要客户端明确同意, -见[连接远程 Server](../operate/connect-remote-server.md)。 + +1.3.0 升级必须通过 SQLite、seekDB 和 OceanBase 的完整 Server 验收。当前迁移路径尚未验证,请勿升级已有数据库。 + + +已有部署应保持当前可用版本,等待来源版本及后端具备已验收的升级路径。试用新建部署时,请使用独立程序环境、 +数据库位置和端口。替换程序包不会完成数据库迁移;备份也不能使不受支持的迁移变为可用。 + +支持声明由发布矩阵和安装包验收共同检查,详见[准备发布](../../development/releasing.md)。 +[Artifact 处理迁移](../operate/artifact-processing-migration.md)和 +[Atomic Memory 迁移](../operate/atomic-memory-migration.md)各自的验证结果不代表完整 Server 就绪。 升级已有数据库前,请先检查 [Dream 协调升级要求](../workflows/review-dream-revisions.md#协调升级)。 统一 Candidate 路由将 `/v1/artifact-candidates/*` 替换为 `/v1/candidates/*`,不提供别名,Dream 客户端要求契约 2。 -已有 Dream 存储需排空未完成任务后显式迁移。当前统一迁移包仍报告 `server_ready=false`,尚不构成完整的生产 -Server 升级路径;Atomic Memory 验证通过也不代表整体迁移就绪。不要绕过 `migration_required` 或 -`migration_framework_not_ready`。 +已有 Dream 存储需排空未完成任务后显式迁移。[完整 Server 迁移](../operate/database-migrations.md)要求 +`state=ready` 和 `server_ready=true`;局部 Artifact 或 Atomic Memory 验证不能替代它。不要绕过迁移门禁。 -升级到 1.4.0: +安装 1.4.0 到独立环境以试用新建部署: ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv venv /path/to/powercontext-preview +uv pip install --python /path/to/powercontext-preview/bin/python "powercontext[cli,server]==1.4.0" ``` -使用其他 Git ref 替换现有工具: +在独立环境安装其他 Git ref: ```bash -uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@" +uv pip install --python /path/to/powercontext-preview/bin/python "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@" ``` -按[各自的集成文档](../integrations/index.md)更新已安装宿主,并使用同一个 ref。更新后重启 Server,再开启新的宿主会话。只要没有修改 -`POWERCONTEXT_HOME` 或数据库 URL,现有 SQLite 数据会继续保留。 +连接新建部署的客户端及 Agent 集成应使用匹配的 ref。保留原部署的数据库位置和配置,不要用原数据库测试新版启动。 +Windows 的虚拟环境解释器位于 `Scripts/python.exe`。 ## 为 Python 项目安装角色 diff --git a/docs/zh/docs/operate/artifact-processing-migration.md b/docs/zh/docs/operate/artifact-processing-migration.md index 821e90dede..4aa8697399 100644 --- a/docs/zh/docs/operate/artifact-processing-migration.md +++ b/docs/zh/docs/operate/artifact-processing-migration.md @@ -8,6 +8,14 @@ Artifact Processing Supervisor。全新的空数据库自动初始化。已有 ## 查看计划并执行 +含有 `pc_schema_revision` 的受管数据库会转发到登记的 +[Processing 配置维护](database-migrations.md#processing-配置维护)操作,plan/apply/verify 使用统一迁移响应格式。 +自动化 apply 还须提供审阅后的 `--plan-id`、明确的 `--backup` 策略和 `--yes`, +并遵守 `db-migrate` 的备份声明及 OceanBase 协调选项。先完成尚未结束的整库升级。 +以下领域独立流程适用于未登记 revision 的数据库。 +与 `db-migrate` 一样,显式环境文件具有最高优先级;未指定时,先查找当前目录的 `.env`, +并在所有权检查和实际执行中使用同一份配置。 + 使用迁移后部署所采用的配置查看只读计划: ```shell diff --git a/docs/zh/docs/operate/atomic-memory-migration.md b/docs/zh/docs/operate/atomic-memory-migration.md index e5e65fdccd..6663f409af 100644 --- a/docs/zh/docs/operate/atomic-memory-migration.md +++ b/docs/zh/docs/operate/atomic-memory-migration.md @@ -11,6 +11,15 @@ title: 迁移到 Atomic Memory ## 执行顺序 +数据库已有 `pc_schema_revision`,或匹配完整的 1.3.0 Server 基线时,这个兼容命令将 `plan`、`apply`、`verify` +转发到 `server db-migrate --bundle server`,返回共享计划和结果格式,并使用相同的维护锁、备份策略及恢复证据。 +需要变更时,先审阅计划,再提供对应的 `--plan-id`、显式 `--backup` 策略、`--yes` 和 `--maintenance-confirmed`。 +OceanBase 还要求[数据库迁移](./database-migrations.md)中说明的固定维护主机和证据目录选项。 +已就绪的数据库保持只读,不重复迁移。Candidate 和 Owner 决策文件只适用于未受管的旧 Memory 转换, +在已登记的 Server 路径上会被拒绝;该路径不因此增加对 1.3.0 之前 Memory 布局的支持。 + +下文描述未受管数据库的领域独立转换: + 先使用新版部署的配置查看只读计划: ```shell @@ -203,6 +212,15 @@ RFC 1771 的结构版本。Atomic Memory 内容恢复接口不能替代数据库 ## 重建当前检索投影 +受管 Server 数据库上,该命令转发到 +[`db-migrate --bundle atomic-projection`](./database-migrations.md#atomic-memory-投影维护)。 +先用 `--action plan` 只读检查,再提供 `--plan-id`、显式 `--backup`、`--yes` 和 +`--maintenance-confirmed` 执行。已登记的无向量修复保留恢复点,在一个事务中提交完整修复, +返回共享 Server 结果。`--action verify` 检查 current 就绪状态;投影已就绪时只读返回。 +此路径不登记向量 profile 切换。 + +下文描述未受管数据库的领域独立修复: + 更换 embedding 模型、profile、维度或归一化方式,或修复当前检索数据时,先备份数据库,停止全部 API、宿主、Worker、自动重启和输入写入,然后使用目标部署配置执行: diff --git a/docs/zh/docs/operate/database-migrations.md b/docs/zh/docs/operate/database-migrations.md index bed350885c..e16126516a 100644 --- a/docs/zh/docs/operate/database-migrations.md +++ b/docs/zh/docs/operate/database-migrations.md @@ -10,20 +10,132 @@ description: 了解数据库迁移命令、备份策略、服务启停及当前 ## 当前可用范围 -`powercontext server db-migrate status/plan/apply/verify` 使用部署配置中的持久 SQLite、嵌入式 seekdb 或 OceanBase MySQL 模式数据库,不接受任意测试 bundle 路径。维护命令以显式环境文件为准,应使用部署的环境文件,不依赖临时 shell 环境覆盖。随 wheel 发布的冻结资源用于阶段 A 的 Artifact 四表迁移:`pc_artifacts`、`pc_artifact_heads`、`pc_artifact_candidate_versions`、`pc_artifact_tags`。仅接受空目标和已登记的历史结构;完整 Server 数据库含有尚未纳管的对象时拒绝执行。 +`powercontext server db-migrate status/plan/apply/verify` 默认选择 `--bundle server`,使用部署配置中的持久 SQLite、嵌入式 seekDB 或 OceanBase MySQL 模式数据库。维护命令以显式环境文件为准,不接受任意测试 bundle 路径。 -输出中的 `ready` 仅表示当前迁移 bundle 已通过结构和数据验证;`readiness_scope=registered_bundle`、`server_ready=false` 明确区分它与完整 Server、索引、旧任务及集群可用。不要以此替代生产升级或完整业务库初始化。普通业务启动的全面版本门禁和完整历史基线尚未纳管。已通过 OceanBase AI 4.6.3 的 ODP 入口完成隔离四表验收,覆盖固定维护主机互斥、DDL 提交后中断的显式确认恢复、数据保留与重复执行。这不代表完整 Server 升级、崩溃后远端 DDL 自动判定或完整 Fork 恢复已通过验收。 +完整 Server 路径登记了真实 1.3.0 的存储结构,版本链为 `s1300 → s1400 → s1401 → s1402`,三个后端均可启用或关闭 Code,并为单位归一化的 L2 向量登记独立 profile。SQLite、OceanBase 的 Code 本地缓存独立于 Server 数据库;嵌入式 seekDB Code 的三张表通过实际安装的 1.3.0 采集并冻结为结构扩展。维护保留已有图数据,为新建的 Code 启用数据库创建已登记的表。此路径覆盖完整数据库结构、Candidate 改名与历史保留、Dream 任务格式、Processing 所有权声明、检索投影和既有 Source 引用。未知结构、额外对象及未登记来源均在写入前拒绝。更早的 Memory 集合转换不属于此路径。 + +只有结构、领域数据、搜索投影和任务校验全部通过,才返回 `state=ready`、`readiness_scope=server`、`server_ready=true`。发布还必须通过真实安装包在三个后端上的完整验收;实现路径存在不等于某次发布已通过验收,支持状态以安装指南和发布门禁为准。 + +升级前必须停止所有写入者并排空 1.3.0 中 queued/running Dream 任务;新版不能执行旧提示词契约。终态任务保留原始 payload,待处理 Processing intent 的计数保留,旧 worker lease 被 fencing。迁移按登记规则扩展处理能力,保留原有自动处理选择,不根据当前配置覆盖旧所有权。 + +Candidate 旧表保留为 `pc_retained_dream_candidate_heads` 和 `pc_retained_dream_candidate_versions`,用于迁移当次的复制验证。它们与迁移备份都不是线上写入目标;升级后的正常修改不再与旧快照比较。历史 Artifact 新增的来源时间/操作者字段保持未知,既有 Source 引用保持不变。投影重建和数据校验会扫描相关表;请为停机时间、备份与保留副本安排容量。 + +`s1401` 在三种后端的托管目标结构中登记 `pc_memory_artifact_archive`。该表保存供离线维护使用的 Memory 历史快照,不提供公开 Artifact 读取入口。新数据库的归档表为空;重复迁移保留其原始字节,运行时准入检查验证登记的表结构。本 revision 不新增对其他历史来源结构的支持。 + +`s1402` 登记可移植归档的恢复回执,并为 MySQL 兼容后端中可移植的 Artifact、Profile、Topic 发布及标签时间戳 +保留微秒精度。已有受管库通过显式维护执行这些结构变更;运行时启动不补建回执表,也不修改时间戳列。 + +[可移植归档恢复](portable-archive.md) 中断后,普通启动和 worker 会继续拒绝未完成的检索投影。 +请使用同一个通过校验的归档,重新执行 `powercontext archive restore --yes`。 +仅此显式恢复路径可在验证已登记结构和配置的向量 profile 后打开投影尚未完整的数据库,且不启动 worker。 +没有待完成恢复回执时,恢复入口仍执行完整就绪校验。 + +普通 Server 启动先做只读就绪检查,不修补已有库。空的本地持久 SQLite/seekDB 可通过同一版本链初始化;OceanBase 必须先执行显式维护命令初始化。内存 SQLite 仍为临时运行时存储。迁移后发现结构或投影被篡改,必须停写维护,不能靠重启修复。 + +Server identity 初始化在受管库上只检查已登记的表,不执行 DDL。首次创建 identity 值以及显式轮换已停止副本的 +identity,仍使用原有事务语义。 + +向量维护要求声明与历史数据完全一致的 embedding profile:profile ID、模型、维度、距离和归一化方式。迁移保留历史向量及 Topic 检索配置,不调用 embedding provider。SQLite 的登记步骤添加并回填 Atomic Memory 的 vec0 辅助索引和事务触发器;原生后端保留登记的向量列与 HNSW 参数。`pc_server_storage_profile` 保存完整的 embedding 配置,不是另一张 revision 表或恢复日志。通用存储读入口校验该声明,Server 启动使用配置或注入模型的实际 profile。受管辅助对象缺失或 profile 变化会在启动修补或写入之前被拒绝。 + +空的持久 SQLite/seekDB Server 启用向量时,通过同一版本链所选的 profile 初始化。已有未受管库必须显式维护,且来源结构必须与登记布局完全一致。切换配置不能绕过准入检查,不能给受管无向量库直接启用向量,也不能隐式增加向量或 Code 对象。已经受管但没有 Code 表的数据库若要启用嵌入式 Code,需要另行登记配置转换,启动入口与独立 Code CLI 均拒绝隐式扩展。发布闸门要求三个后端、向量开关及 Code 开关组成的十二种配置全部通过。Code 配置用实际安装的 1.3.0 创建索引,保留缓存文件与嵌入式图数据,并在升级后查询原指纹和符号。向量配置保留非空的历史 Atomic 与 Topic 向量,通过实际安装的目标包验证语义读取和新写入,拒绝 profile 漂移,并对比新建库与升级库。SQLite 还验证辅助对象缺失时的拒绝行为及备份还原。无向量配置通过不能证明向量支持,实际目标安装包必须通过每个登记配置的验收。 + +`--bundle artifacts` 保留 `p0001` 开始的局部 Artifact 原型链;`--bundle topic-memory-recovery` 只处理登记的 Topic 恢复布局。两者均报告 `server_ready=false`,不得用于证明完整 Server 就绪,也不能把其版本号直接 stamp 为完整 Server 版本。 MySQL 兼容后端的结构反射支持 CHECK 表达式中的二元 `+`、`-`、`*`、`/`、`%`、一元正负号及嵌套括号,并保留算术、比较和布尔运算的优先级。未支持的 CHECK 运算符或函数仍被拒绝,错误会标明反射的表名、约束名及未支持的语法,避免将合法分组误报为括号不匹配。表达式解析成功不代表历史结构已登记,也不会绕过迁移或数据完整性检查。 -原有 `server processing-migrate` 保留,请按[迁移 Artifact 处理状态](artifact-processing-migration.md)使用。它尚未自动转发到统一入口。服务部署参考[部署 Server](deploy-server.md)。 +原有 `server processing-migrate` 在受管数据库上转发到 `db-migrate --bundle processing`;未登记 revision 的数据库仍保留领域独立迁移。具体步骤见[迁移 Artifact 处理状态](artifact-processing-migration.md)。服务部署参考[部署 Server](deploy-server.md)。 + +`server atomic-memory-migrate` 在受管数据库或匹配完整 1.3.0 Server 基线的数据库上转发到 +`db-migrate --bundle server`,包括计划确认、备份和恢复要求;未受管的旧 Memory 布局继续使用领域独立转换。 +该转换的决策文件不能修改已登记的 Server 版本链。兼容命令在受管库上返回共享 Server 计划或结果, +已就绪时 apply 保持只读,不重复迁移。参见[迁移到 Atomic Memory](atomic-memory-migration.md)。 + +## 初始化与维护职责 + +下表列出结构变更与投影修复的执行入口。含有 `pc_schema_revision` 的数据库均视为受管库,包括未知 revision; +局部领域检查通过不代表整个 Server 可以使用该库。Profile 准入先验证已登记的整库结构与领域状态。 + +| 入口或辅助函数 | 受管数据库 | 未受管数据库 | +| --- | --- | --- | +| `SQLiteProfile.open`、`SeekDBProfile.open`、`OceanBaseProfile.open`;`schema.create_tables` | 使用前先做整库就绪检查;所选表必须已经存在 | 保留所选表创建和领域准入 | +| Runtime composition:Skill 分发、标签 Family、Scope 搜索、revision 元数据和 Artifact Head 列 | 跳过兼容性 DDL 与回填,由已登记的 Server 版本链负责 | 保留既有兼容辅助函数 | +| Runtime Atomic Memory 权威记录、current 索引和 SQLite FTS 辅助对象 | 校验 current 数据;结构准入后通过 `db-migrate --bundle atomic-projection` 修复 | 保留领域初始化与离线投影修复 | +| Runtime Experience/Skill 搜索文本与 FTS 索引 | 由已登记的 Server 领域步骤构建和校验;启动不重建 | 保留领域初始化与投影恢复 | +| Runtime Topic Memory 索引、恢复表和检索配置 | 使用 `validate_only=True` 初始化;局部 Topic 恢复拒绝受管库 | 保留领域初始化;局部恢复只覆盖其登记的布局 | +| Processing 结构与所有权配置 | 启动时校验;配置变更走 `db-migrate --bundle processing` | 保留领域 bootstrap 和 `processing-migrate` | +| `ServerIdentityRepository.initialize` | 检查 identity 表,不执行 DDL;创建 identity 值和显式轮换仍使用普通事务 | 保留支持并发的 identity 表初始化 | +| 离线 Memory 归档 | Server 版本链在 `s1401` 登记结构,运行时准入检查该结构 | 旧 Memory 离线转换负责其归档和转换回执 | +| `atomic-memory-migrate`、`processing-migrate` | 转发到共享 Server 或已登记的维护计划 | 保留领域独立行为;识别为历史完整 Server 的库必须先完成整库升级 | +| 向量对象 | 所选 Server profile 负责结构、辅助索引回填及精确配置准入;切换 embedding 空间需要独立转换 | 完整的已发布向量结构可显式迁移;独立未受管 SDK 存储保留领域初始化 | +| 嵌入式 seekDB Code 对象 | 已登记 Code profile 创建并验证三张表及全文索引;Code 启动不修补缺失的受管对象 | 完整的已发布 Code 启用结构可显式迁移;独立未受管 Code 保留既有初始化 | +| SQLite、OceanBase 的独立 Code 缓存 | 保留缓存文件,主数据库走共享版本链 | Code 服务负责本地缓存创建和索引构建 | + +这些辅助函数仍供所属 runtime 或维护操作使用;直接调用底层函数不能替代 Profile 准入、已确认的维护计划和 +备份恢复要求。未来受管对象的变化应加入已登记的 revision、领域步骤与校验器,不另建启动迁移入口。 +只读 SQLite 检查入口与独立维护连接可以在业务初始化之前检查历史存储;检查连接打开成功不代表 Server 已就绪。 + +Code 的嵌入式 seekDB 存储在部署数据库中添加 `pc_code_generations`、`pc_code_nodes`、`pc_code_edges` 和 +`ix_code_search`;其他 Code 部署使用本地 SQLite 缓存。这些派生索引的数据由 Code 服务负责, +冻结扩展为维护与完整 Server 准入登记其结构。部分缺失或发生变化的扩展会被拒绝,不能隐式扩展受管结构。 +SQLite、OceanBase 的 Code 缓存不改变主库结构。 + +## Processing 配置维护 + +`--bundle processing` 在结构完整、已到当前 revision 的 Server 数据库上登记离线所有权配置操作。 +它不推进 `pc_schema_revision`,也不接管旧结构;必须先完成整库升级。 +使用切换后的部署配置,每次模式切换指定新的迁移 ID: + +```bash +powercontext server db-migrate plan --bundle processing --env-file .env \ + --migration-id global-to-dedicated-1 --backup auto +powercontext server db-migrate apply --bundle processing --env-file .env \ + --migration-id global-to-dedicated-1 --backup auto --plan-id PLAN_ID \ + --maintenance-confirmed --yes +powercontext server db-migrate verify --bundle processing --env-file .env \ + --migration-id global-to-dedicated-1 +``` + +该操作复用结构迁移的目标锁、备份策略、后端维护连接和停写声明。OceanBase 仍要求固定维护主机、 +`--evidence-dir` 和 `--lock-coordination single-host`;报告上次运行未结束时,恢复必须提供准确的运行确认 token。 +此操作不切换本地服务;执行前须自行停止全部写入者并暂停自动重启。 + +模式切换的所有阶段在一个事务中提交。提交前失败时,所有权声明和租约撤销一起回滚。 +中断后重新查看计划,保留原配置、迁移 ID、备份策略和证据目录恢复;原维护窗口和恢复点继续沿用。 +成功后的重试不会再次撤销租约。操作前后都运行完整 Server 校验;配置已匹配时只读返回。 + +## Atomic Memory 投影维护 + +`--bundle atomic-projection` 在当前受管 Server 数据库上修复 Atomic Memory current 行和 SQLite FTS +辅助索引,适用于已登记的无向量配置,包括 SQLite、OceanBase 使用独立缓存的 Code 配置。它保留 Server revision、不可变正文与历史、生命周期、 +正式标签、权限和 Source/Processing 状态。 + +```bash +powercontext server db-migrate plan --bundle atomic-projection --env-file .env --backup auto +powercontext server db-migrate apply --bundle atomic-projection --env-file .env \ + --backup auto --plan-id PLAN_ID --maintenance-confirmed --yes +powercontext server db-migrate verify --bundle atomic-projection --env-file .env +``` + +该操作与 Processing 维护共用目标锁、备份、停写声明和持久恢复事务。plan 和 verify 只读; +apply 只修复派生行,完整 Server 校验通过后才提交。权威数据损坏或结构未登记时拒绝执行。 +投影已就绪则只读返回。提交前中断会回滚修复;使用新的计划、原软件产物、配置、备份策略和证据目录恢复, +继续沿用原恢复点。另一项维护未完成时,须先完成原操作。OceanBase 同样要求固定维护主机协调, +并在报告上次运行未结束时提供准确的确认 token。 + +检查和修复扫描相关 Atomic 历史、head、标签、lineage 及 current 投影,通过键集分页限制行缓冲; +这是停服执行的全投影操作,不登记向量 profile 切换或嵌入式 seekDB Code 配置。 +兼容命令 `server atomic-memory-rebuild-projection` 在受管数据库上转发到此 bundle, +支持 `--action plan/apply/verify` 和同样的确认、备份选项。 ## Topic Memory 恢复 bundle 选择 `--bundle topic-memory-recovery` 执行版本化的 `powercontext.topic-memory-recovery.v1` 转换。它识别恢复功能之前的 预算布局和实现 #1966 的 attempts 表布局,保留已消费额度、终止阻塞和已接受回执。它仅验证已有 Server 数据库中 登记的 Topic 对象和共享处理 lease 的 fencing,不改变其他业务表;输出仍报告 `server_ready=false`。 -默认 `--bundle artifacts` 仍为上面的局部验收范围。 +默认 `--bundle server` 的完整升级路径见上文。 + +此部分 bundle 仅适用于未纳入统一框架的存储。只要数据库包含 `pc_schema_revision`,即使 revision 未知, +也会在备份和写入前拒绝。请使用 `--bundle server` 或已登记的受管维护操作。 先停止全部 API、Worker、调度和 SDK 写入者,并暂停自动重启。审阅并执行一次计划: @@ -70,6 +182,8 @@ powercontext server db-migrate verify --env-file deployment.env 计划绑定目标身份、源/目标 revision、实际结构摘要、冻结脚本摘要、配置摘要、备份策略、服务范围和迁移协调范围。配置与凭据值不输出到诊断中;预期错误返回稳定 JSON 类别和非零退出码。 +配置绑定包含目标部署的规范 Processing 归属模式、能力和自动绑定声明。改变这些声明后必须重新生成计划,不能使用旧计划执行。计划绑定本身不验证或迁移历史 Processing 状态;完整 Server bundle 另由已登记的领域步骤校验持久化声明与待处理 intent。 + 在支持范围内,终端用户可直接执行 `apply`,默认 PC 自动备份。命令展示计划、备份方法、维护影响及停写责任,确认一次后执行,不逐条 SQL 询问: ```bash @@ -165,6 +279,11 @@ powercontext server db-migrate verify --env-file oceanbase.env \ 自动备份报告 `completed`,手动备份报告 `user_confirmed`,跳过报告 `skipped`;真正空库初始化报告 `not_required`。自动备份不支持、失败或未完成时停在迁移写入之前,返回 `backup_unsupported` 或备份失败类别,不能自动改成跳过。选择其他策略后需要重新确认计划。 +SQLite 安装版本验收套件将自动备份还原到独立数据库,比较完整来源结构和逐表数据摘要, +使用已安装的旧版 Server 读回历史记录,再升级还原副本并验证新版读取、检索和新写入。 +启用 Code 的用例还保留独立的不可变缓存。这项演练仅覆盖 SQLite,不证明 seekDB 或 OceanBase 备份可以还原。 +在确认自己的还原部署可用前,应保留原始恢复点。 + 独立 `BackupProvider` 复用后端配置和维护连接。SQLite 使用 Online Backup API 创建独立文件,包含已提交 WAL,并检查文件完整性。seekdb 和 OceanBase 执行器复用 `ForkBackupProvider`,优先采用原生 `FORK DATABASE`。当前执行器拒绝自动 `FORK TABLE`;启用前须单独登记同库备份对象,并完成受影响依赖、跨表一致性和恢复路径的验收。对于需要备份的升级,实际引擎和结构缺少通过验收的恢复证据时,`--backup auto` 在迁移写入前返回 `backup_unsupported`。用户手动备份与明确接受风险的不备份路径不依赖 Fork 支持。PC 不替用户声明已完成验收,也不自动改为物理备份或其他策略。 | 产品 | `FORK TABLE` 起始版本 | `FORK DATABASE` 起始版本 | @@ -194,9 +313,31 @@ powercontext service restart 这些命令只管理当前用户归 PC 所有的服务,不管理集群或其他客户端。`stop` 禁止维护期间自动拉起旧服务;`start` 恢复启动。涉及迁移时不能用一次 `restart` 替代停写维护。 -完整升级的托管设计为:排空请求、停服并禁止自动重启 → 取迁移锁并复核 → 按策略备份 → 迁移 → 验证 → 将启动定义切换到已确认新版程序 → 启动原本运行的服务 → readiness。迁移失败保持停服;原本停止的服务保持停止。数据库验证成功而服务启动失败时分别报告,处理启动问题而不重跑迁移。 +`--bundle server` 的 `--manage-service` 将本机自有服务注册和目标可执行程序绑定到待确认的计划。 +必须使用注册时的环境文件和数据目录;实际 Server 配置不一致时,在停止服务前拒绝执行。 +先停止流量并排空旧任务,再应用计划。应用会抑制自动重启、停止本机服务、取得迁移锁、重新检查并执行备份策略, +完成迁移和数据库验证后才切换注册。原先运行的服务会重新启动并检查 readiness;原先停止的服务保持停止。 +迁移失败时保留旧注册和停止保护,不恢复自动启动。 + +数据库验证与服务启动分别报告。`service_start_failed` 且 `database_state=ready` 表示迁移已成功, +修复启动问题后使用 `powercontext service start`。如果在数据库验证之后、切换服务注册之前中断, +可以重新审阅并应用 `--manage-service` 计划;数据库已就绪时也能完成服务切换,不重复执行已完成的 revision。 + +`--manage-service` 仅管理已确认的本机服务,不能代替 `--maintenance-confirmed` 对其他写入者和共享库节点的声明。 +部分 Artifact 和 Topic recovery bundle 需要变更时返回 `service_unsupported`;已就绪的空操作不触碰服务。 +它们不能用于完整 Server readiness 或业务服务切换。 +控制器测试使用模拟的原生服务管理器和真实 SQLite 迁移。原生生命周期套件还通过独立服务注册验证安装后的 CLI: +升级已停止服务的冻结 SQLite 基线,确认升级后仍保持停止并可显式启动;同时验证数据库已就绪时运行中服务的维护恢复。 +每个操作系统须分别执行原生测试。冻结基线夹具与实际历史版本安装并写入数据库后的升级验收是不同的证据。 + +套件还在独立环境中安装真实 1.3.0 wheel,通过原生管理器启动,并用 HTTP 创建 Scope。运行中和已停止两种服务状态 +均通过公开维护命令升级,验证备份与旧数据保留,并确认注册切换到目标解释器且保持预期运行状态。 +CI 安装时校验来源 wheel 的 SHA-256,并设置 `POWERCONTEXT_NATIVE_UPGRADE_SOURCE_PYTHON`; +本地运行原生测试时也需提供该已安装来源版本的解释器。这些用例覆盖 SQLite 服务切换, +后端崩溃恢复与完整安装版本领域验收仍需分别验证。 -`--manage-service` 的范围始终只是已确认的本机服务,不能代替 `--maintenance-confirmed` 对其他写入者和共享库节点的声明。阶段 A 的有限 bundle 未完成完整 Server readiness 验收;需要持久化变更时,`--manage-service` 返回 `service_unsupported`,不能据此自动切换生产服务。 +失败场景覆盖备份存储不可用、目标服务启动时真实的端口冲突,以及替换服务注册文件时注入的 `ENOSPC`。 +启动或切换失败后,保留已停止的服务和恢复记录;数据库验证已经完成时,通过显式启动服务恢复,无需再次执行迁移 apply。 手动管理时,先停旧服务,在已支持的完整迁移成功后确认服务启动定义指向新版,再启动。前台部署则退出旧进程,使用新版环境的现有入口: @@ -210,15 +351,29 @@ powercontext server run --env-file deployment.env --role all 发布作者应提供受影响对象、执行模式、程序/schema 兼容范围、API 变化和任务格式声明。公共 API 不兼容替换需标记弃用并说明替代入口和移除计划;同包内部入口可与调用方一起替换。保留的 API 可转换请求/响应后共用当前实现,不要求每个 handler 支持两套表结构。 -旧格式任务需要在只读计划、停写取锁后、迁移后及 Worker 启动前检查。未知格式或不可观察来源不能假报无任务;应按声明兼容消费、幂等转换或有界排空,并保留任务 ID、幂等键、Scope、重试和领域收据。这些完整业务检查尚未由阶段 A bundle 覆盖。 +旧格式任务需要在只读计划、停写取锁后、迁移后及 Worker 启动前检查。未知格式或不可观察来源不能假报无任务;应按声明兼容消费、幂等转换或有界排空,并保留任务 ID、幂等键、Scope、重试和领域收据。完整 Server bundle 对登记的 1.3.0 任务执行这些检查;局部阶段 A bundle 不作此声明。 ## 存储层与恢复边界 +独立的 Linux OceanBase 引擎恢复测试要求通过 `POWERCONTEXT_TEST_MIGRATION_OCEANBASE_ENGINE_CRASH_ID` +提供隔离容器的完整 ID,并通过 `POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL` 提供与其匹配的回环端口及专用 +`pc_migration_probe_*` 数据库。测试在已登记 DDL 完成后向 observer 发送 `SIGKILL`,从原目录重启 observer, +验证原恢复记录、必需的确认 token、历史数据字节、最终 revision 和重复执行。它不重新执行 slim 镜像的启动脚本, +因为该脚本会展开预置快照并截断日志。CI 要求这一个测试实际执行且零跳过。 +测试使用冻结历史资源和操作员备份声明,不能替代安装版本升级验收,也不代表已验证备份可恢复。 + 维护执行器复用配置,使用专用连接承载 Alembic;冻结历史 SQL 不依赖当前 Repository 或应用 metadata。seekdb 从启动引擎前到关闭引擎后持有规范化目录的锁。OceanBase 在每条 DDL 前后验证操作系统锁、主机绑定、持久运行记录及维护连接。正常结束时先关闭迁移主连接,才清理运行记录并释放文件锁;引擎随后关闭。执行中断或关闭连接失败均保留记录;后续执行需要准确的旧运行 token,以及 DBA 确认远端执行已结束的人工声明。PC 不从本机进程退出或本地锁可获取推断远端执行结束。锁文件被替换、连接丢失或旧运行未确认时阻止继续。平台不支持原生文件锁时拒绝执行,包括运行时降级为软锁的情况,不重连或静默弱化互斥继续迁移。这些锁不阻止业务写入者。 -seekdb/OceanBase DDL 可能隐式提交,不能用 Python 事务承诺整个迁移原子化。恢复只识别已登记顺序:`p0001` 创建冻结四表及两个索引;`p0002` 添加两个可空的 `MEDIUMBLOB` 引用列;`p0003` 删除标签 family 的 CHECK。继续执行前须用实际结构、数据检查和持久维护证据证明已完成的已知前缀;revision 的后置条件通过后才推进 Alembic 版本。未知对象、结构或中间状态拒绝执行,不乐观 stamp。 +seekdb/OceanBase DDL 可能隐式提交,不能用 Python 事务承诺整个迁移原子化。局部 Artifact 原型只识别已登记顺序:`p0001` 创建冻结四表及两个索引;`p0002` 添加两个可空的 `MEDIUMBLOB` 引用列;`p0003` 删除标签 family 的 CHECK;`p0004` 添加指向精确 revision 的 Artifact handle。继续执行前须用实际结构、数据检查和持久维护证据证明已完成的已知前缀;revision 的后置条件通过后才推进 Alembic 版本。未知对象、结构或中间状态拒绝执行,不乐观 stamp。 + +MySQL 结构步骤通过冻结的布局片段注册,由 `manifest.json` 的 `mysql_layouts` 列表选择,合起来必须恰好覆盖 Alembic 版本链,且不能重复声明。新增 revision 追加不可改写的片段,并在 `revision_resources` 中声明其文件,不修改已发布片段。可选 checkpoint 将累积步骤与独立冻结的完整结构对照。反射、下一 revision 的已知前缀恢复和版本表校验使用同一注册链;新增 revision 无需修改识别器中的另一份版本列表。这些结构注册不能代替领域转换、兼容性检查或完整 Server readiness。 + +新增 MySQL 执行入口也在 `manifest.json` 中登记:`mysql_wrappers` 对象将 revision 映射到冻结的 Python 资源,并将该文件列入对应 revision 的 `revision_resources` 依赖。入口声明与公共链一致的 `revision` 和 `down_revision`,调用 `context.config.attributes["execute_revision"](revision)`,不能覆盖历史执行入口。执行器在打开维护目标前验证完整、线性的入口覆盖;缺失或分叉的执行历史直接判为无效 bundle,不等到前序 revision 提交后才发现。入口字节同时纳入计划的 bundle 摘要和该 revision 的执行摘要。 + +计划通过完整 bundle 摘要绑定注册资源,未完成维护的重试要求使用同一 bundle。已发布 revision 资源及其记录摘要保持不可改写。 +无参数 DDL 使用单语句驱动选项,确保 CHECK 的取余运算符和 SQL 字面量中的百分号原样到达后端。 -Repository 仅在结构满足后运行。完整业务的全文/向量索引版本管理及受控重建、旧任务 readiness 尚未包含在本验收 bundle 中。 +Repository 仅在结构满足后运行。完整 Server 的 `s1300 → s1400 → s1401 → s1402` 链用 `mysql_state_layouts` 登记逐语句的精确结构变化和可恢复前缀。领域步骤附着在共享 revision 上,使用同一连接、锁和恢复点;校验成功后才推进版本。只读 live 校验与迁移当次的保留副本校验分离。向量配置仍需独立登记。 单版本表不提供通用 run ID 续跑。中断后先用只读命令检查真实结构和数据;含混状态返回 `recovery_required`,不盲目 stamp、不用部分状态的备份替换原恢复点。库外摘要在 DDL 前记录执行及备份信息;重试保留原维护窗口和恢复点。在恢复条件允许时可显式重新确认 manual/skip 策略,但不绕过结构验证,也不替代必需的进度证据。 @@ -228,4 +383,5 @@ Repository 仅在结构满足后运行。完整业务的全文/向量索引版 现有 Scope discovery 启动迁移会检查 MySQL 兼容存储中搜索列的实际可空性。如果上次启动在添加可空列或回填后中断, 下次启动会根据原文补齐剩余缺失值,再完成 `NOT NULL` 约束。已为 `NOT NULL` 的列不会再次执行 `MODIFY COLUMN`。 -Scope 的原始标题、摘要、外部引用和 binding 标识保持不变。此兼容修复独立于上述四表统一迁移 bundle。 +Scope 的原始标题、摘要、外部引用和 binding 标识保持不变。此兼容修复仅供未受管理的配置使用。 +受管理的 Server 存储使用登记的迁移链和只读启动准入,不会在启动时调用此修复来升级结构。 diff --git a/docs/zh/docs/workflows/review-dream-revisions.md b/docs/zh/docs/workflows/review-dream-revisions.md index f110875db5..6b641691b7 100644 --- a/docs/zh/docs/workflows/review-dream-revisions.md +++ b/docs/zh/docs/workflows/review-dream-revisions.md @@ -21,7 +21,7 @@ Handoff 批准即发布新版本,后续显式 Continue 按既有 latest 规则 停止全部写入前,先停止新增 Dream 请求,并由旧程序将 queued/running Dream Run 处理到终态。未完成任务会使迁移返回 `dream_tasks_require_drain`;迁移不改写冻结的提示词、模型配置、重试预算或截止时间。已完成 Run 和 pending Candidate 保留,升级后可继续读取与审核。Handoff/Prompt 的 Processing 绑定也由显式迁移更新,普通启动不修补 manifest;不支持的 manifest 返回 `unknown_processing_manifest` 并阻止迁移。 -Dream 提供的变更资源本身不启用生产升级。[PR #1772](https://github.com/oceanbase/powercontext/pull/1772) 还需完成完整 Server 基线、revision 链登记、历史任务检查与后端验收,其四表 SQLite 原型不代表 Server 就绪。在完整框架就绪检查接入前,本分支对旧 Dream 结构返回 `migration_required`,对已登记 revision 的数据库返回 `migration_framework_not_ready`;不能通过手工 stamp 或创建空替代表绕过。已登记旧表与恢复对象按统一框架的保留策略管理。 +[完整 Server 迁移 bundle](../operate/database-migrations.md) 为无向量、Code 关闭的配置登记了完整 1.3.0 基线、revision 链和历史任务检查。维护前须停止写入,并排空 queued 或 running 的旧 Dream 任务。启动时校验完整受管数据库,对未完成的 revision 返回 `migration_required`;手工 stamp 或创建空替代表不能建立就绪状态。局部 Artifact bundle 仍返回 `server_ready=false`。具体版本已验证的升级支持范围以安装指南为准。已登记旧表与恢复对象按统一框架的保留策略管理。 ## Prompt 配置 diff --git a/e2e/bub/README.md b/e2e/bub/README.md index ecfc3cfce7..0ca322ddb1 100644 --- a/e2e/bub/README.md +++ b/e2e/bub/README.md @@ -126,6 +126,12 @@ make harness-compose-acceptance \ ARGS='--id locomo-support-group --id project-database-decision' ``` +For OceanBase, Compose runs public `db-migrate plan/apply/verify` commands before starting the Server, including +configurations with embedding or Code enabled. The commands use the Server container's configuration, one maintenance +container, and `/data/migration-state`; ordinary remote Server startup only checks readiness. The harness owns disposable +volumes and stops business writers during setup. A maintenance failure fails startup rather than allowing a partially +initialized Server to run. + Each selected workload writes the same layout: ```text diff --git a/e2e/bub/run.sh b/e2e/bub/run.sh index d6c1b16c64..3fbd8c33e9 100755 --- a/e2e/bub/run.sh +++ b/e2e/bub/run.sh @@ -90,6 +90,33 @@ show_startup_diagnostics() { docker compose $compose_files logs --no-color --timestamps --tail 200 >&2 || true } +initialize_database() { + if [ "$database" != oceanbase ]; then + return + fi + docker compose $compose_files up --detach --wait oceanbase || return + # Plan/apply/verify share one container, fixed host and evidence directory. + # Remote Server startup only verifies storage; it cannot initialize it. + docker compose $compose_files run --rm --no-deps --entrypoint python powercontext -c ' +import json +import subprocess + +common = [ + "--evidence-dir", "/data/migration-state", + "--lock-coordination", "single-host", +] +command = ["powercontext", "server", "db-migrate"] +plan = json.loads(subprocess.check_output([*command, "plan", *common, "--backup", "auto"], text=True)) +print(json.dumps(plan), flush=True) +subprocess.run([ + *command, "apply", *common, + "--backup", "auto", "--plan-id", plan["plan_id"], + "--yes", "--maintenance-confirmed", +], check=True) +subprocess.run([*command, "verify", *common], check=True) +' +} + start_services() { attempt=1 max_attempts=1 @@ -98,7 +125,7 @@ start_services() { fi while :; do - if docker compose $compose_files up --detach --wait powercontext; then + if initialize_database && docker compose $compose_files up --detach --wait powercontext; then return else status=$? diff --git a/release/upgrade-support.json b/release/upgrade-support.json new file mode 100644 index 0000000000..d6c7aabdf4 --- /dev/null +++ b/release/upgrade-support.json @@ -0,0 +1,76 @@ +{ + "format": 2, + "target_version": "1.4.0", + "mode": "upgrade-required", + "schema_review": { + "persistence_sha256": "56be4a21d721f2c8025c5b4a8b4bef34a91a72028f0eab01d1d30c01b87a0c31", + "rationale": "The shared s1300 -> s1400 -> s1401 -> s1402 chain registers installed 1.3.0 no-vector Server storage on SQLite, seekDB and OceanBase with Code disabled or enabled. SQLite/OceanBase Code caches remain separate; embedded seekDB Code uses the three frozen released tables. A selected vector profile binds the frozen schema and domain contract to the exact historical profile ID, model, dimension, distance and normalization. SQLite maintenance registers and backfills the Atomic vec0 helper and transactional triggers; native layouts preserve vector column dimensions and explicit HNSW parameters, including ordered key columns, algorithm, distance, library and construction/search options. Unknown index options remain rejected. The vector-only configuration declaration is not a second revision or recovery journal. Full layout and domain checks precede managed use; configured or injected model drift and missing helpers cannot trigger startup repair. No embedding provider is called during migration and historical embeddings, Topic shape, authority and evidence are preserved. Full Server readiness validates Atomic Memory projections against authority and checks complete SQLite FTS identities for both vector and no-vector profiles before admission. Validation is read-only and damaged projections require explicit maintenance. SQLite startup preflight reads authority and projections in one explicit read transaction so concurrent WAL writes cannot produce a mixed-revision validation result; no schema or stored data format changes are introduced. Reviewed plans bind the draft validation resources; selected vector profiles have separately bound checksums. Target locks, original recovery points, acknowledgements and backup policies remain shared. The installed-release gate requires all twelve backend/configuration combinations, with Code enabled or disabled and vectors enabled or disabled. Vector cases seed nonempty Atomic and Topic vectors through the actual source Server, preserve exact embedding digests and retrieval shape, and verify historical/new semantic reads, configuration-drift rejection, SQLite missing-helper rejection and fresh-storage equivalence using the independently installed target. Maintenance must not call an embedding provider. SQLite vector cases also restore the automatic backup with the old Server before re-upgrading. Complete current-artifact and recovery/restore acceptance remain required. Source tests and schema captures do not establish that release acceptance passed. Policy remains upgrade-required. Revision s1402 adapts the storage already present on master: portable restore receipts and native microsecond precision for Artifact creation, policy updates, Topic publications and tag assignments. Earlier revision resources and checksums remain unchanged. Pending archive restores reject ordinary managed startup and readiness; only the explicit archive recovery entry can open a registered layout to finish projections, and it still checks the configured embedding profile. Interrupted first-revision MySQL initialization defers domain preflight until the executor has completed the registered baseline statements and seed data; missing maintenance evidence and unregistered DDL remain rejected. The s1402 portable receipt boolean is explicitly declared and registered as TINYINT(1), matching native MySQL-compatible reflection without relaxing schema matching; earlier revision resources are unchanged." + }, + "cases": [], + "automatic_sources": { + "strategy": "fixed-version", + "source_version": "1.3.0", + "profiles": [ + { + "backend": "sqlite", + "configuration": "full-server-no-vector", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "seekdb", + "configuration": "full-server-no-vector", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "oceanbase", + "configuration": "full-server-no-vector", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "sqlite", + "configuration": "full-server-code", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "oceanbase", + "configuration": "full-server-code", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "seekdb", + "configuration": "full-server-code", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "sqlite", + "configuration": "full-server-vector", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "seekdb", + "configuration": "full-server-vector", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "oceanbase", + "configuration": "full-server-vector", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "sqlite", + "configuration": "full-server-vector-code", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "seekdb", + "configuration": "full-server-vector-code", + "suite": "tests/release_upgrades/test_full_server.py" + }, + { + "backend": "oceanbase", + "configuration": "full-server-vector-code", + "suite": "tests/release_upgrades/test_full_server.py" + } + ] + } +} diff --git a/scripts/ci_legacy_upgrade_rejection.py b/scripts/ci_legacy_upgrade_rejection.py new file mode 100644 index 0000000000..d1b712ec00 --- /dev/null +++ b/scripts/ci_legacy_upgrade_rejection.py @@ -0,0 +1,277 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Reproduce unsupported 1.3.0 upgrades on actual SQLite, embedded seekDB or OceanBase Servers. + +This is a negative regression, never positive upgrade acceptance. CI retires +these assertions once the supported-path matrix becomes mandatory. +""" + +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import tempfile +from pathlib import Path +from urllib.parse import urlsplit + +import httpx +from ci_release_smoke import _available_port, _stop_process, _wait_until_ready +from upgrade_release_gate import ROOT, installed, load_policy, require, run, sha256, wheel_version + + +def console(python: Path) -> Path: + return python.with_name("powercontext.exe" if os.name == "nt" else "powercontext") + + +def database_environment(backend: str, database: Path, environment: dict[str, str]) -> dict[str, str]: + if backend == "seekdb": + return {"POWERCONTEXT_SERVER_DATABASE_KIND": "seekdb", "POWERCONTEXT_SERVER_DATABASE_PATH": str(database)} + if backend == "oceanbase": + return { + "POWERCONTEXT_SERVER_DATABASE_KIND": "oceanbase", + "POWERCONTEXT_SERVER_DATABASE_URL": environment["PC_UPGRADE_OCEANBASE_URL"], + } + return {"POWERCONTEXT_SERVER_DATABASE_URL": f"sqlite+aiosqlite:///{database}"} + + +def source_database(python: Path, directory: Path, environment: dict[str, str], log: Path) -> dict[str, str | bool]: + port = _available_port() + base_url = f"http://127.0.0.1:{port}" + with log.open("w") as stream: + process = subprocess.Popen( # noqa: S603 -- installed source CLI against a disposable database + [str(console(python)), "server", "run", "--no-env-file", "--host", "127.0.0.1", "--port", str(port)], + cwd=directory, + env=environment, + stdout=stream, + stderr=subprocess.STDOUT, + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == "nt" else 0, + start_new_session=os.name != "nt", + text=True, + ) + try: + _wait_until_ready(base_url, process, 180) + with httpx.Client(base_url=base_url, trust_env=False, timeout=30) as client: + response = client.post( + "/v1/scopes", + json={ + "title": "Upgrade fixture", + "summary": "Disposable old-release database for upgrade validation.", + "idempotency_key": "upgrade-fixture", + }, + ) + response.raise_for_status() + scope_id = response.json()["scope_id"] + response = client.post( + "/v1/memory/remember", + json={"scope_id": scope_id, "kind": "fact", "text": "Historical release migration fixture."}, + ) + response.raise_for_status() + require(response.json().get("records"), "Old Server did not persist the fixture") + response = client.post( + "/v1/memory/search", json={"scope_id": scope_id, "query": "historical migration", "mode": "fts"} + ) + response.raise_for_status() + require(response.json().get("hits"), "Old Server cannot retrieve its fixture") + return {"scope_id": scope_id, "memory_readback": True} + finally: + _stop_process(process) + + +def rejected( + command: list[str], environment: dict[str, str], directory: Path, log: Path, reasons: tuple[str, ...] +) -> None: + with log.open("w", encoding="utf-8") as stream: + process = subprocess.Popen( # noqa: S603 -- installed target CLI, isolated data only + command, + env=environment, + cwd=directory, + stdout=stream, + stderr=subprocess.STDOUT, + text=True, + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == "nt" else 0, + start_new_session=os.name != "nt", + ) + try: + process.wait(timeout=180) + finally: + _stop_process(process) + output = log.read_text(encoding="utf-8") + require( + process.returncode != 0 and any(reason in output for reason in reasons), + f"Expected rejection {reasons}; inspect {log.name}", + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--wheel", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--backend", choices=("sqlite", "seekdb", "oceanbase"), default="sqlite") + parser.add_argument( + "--respect-policy", + action="store_true", + help="Retire rejection assertions once supported-path acceptance is required", + ) + args = parser.parse_args() + wheel = args.wheel.resolve() + output = args.output.resolve() + output.mkdir(parents=True, exist_ok=False) + if args.respect_policy and load_policy(ROOT)["mode"] == "supported-upgrades": + (output / "result.json").write_text( + json.dumps( + { + "kind": "unsupported-upgrade-regression", + "backend": args.backend, + "status": "not-applicable", + "reason": "Supported upgrades are evaluated by the required acceptance gate.", + }, + indent=2, + ) + + "\n" + ) + print("Historical rejection assertions retired; the supported upgrade matrix must pass instead.") + return + if args.backend == "oceanbase": + url = urlsplit(os.environ.get("PC_UPGRADE_OCEANBASE_URL", "")) + require( + url.scheme == "mysql+aoceanbase" + and url.hostname in {"localhost", "127.0.0.1"} + and url.path == "/pc_upgrade_test", + "OceanBase regression requires the dedicated local pc_upgrade_test database", + ) + environment = { + key: value + for key, value in os.environ.items() + if not key.startswith(("POWERCONTEXT_", "PYTEST_")) and key not in {"PYTHONPATH", "PYTHONHOME", "VIRTUAL_ENV"} + } + environment["NO_PROXY"] = environment["no_proxy"] = "localhost,127.0.0.1" + # A short pathname avoids the embedded engine's Unix socket length limit. + with tempfile.TemporaryDirectory(prefix="pc-up-", dir="/tmp" if os.name != "nt" else None) as temporary: # noqa: S108 -- mkdtemp creates a private random directory + directory = Path(temporary) + interpreters = {} + extras = "cli,server,seekdb" if args.backend == "seekdb" else "cli,server" + for role, package in {"source": f"powercontext[{extras}]==1.3.0", "target": f"{wheel}[{extras}]"}.items(): + venv = directory / role + run( + ["uv", "venv", "--python", "3.12", str(venv)], + environment=environment, + cwd=directory, + log=output / f"{role}-venv.log", + ) + python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + run( + ["uv", "pip", "install", "--python", str(python), package], + environment=environment, + cwd=directory, + log=output / f"{role}-install.log", + ) + interpreters[role] = python + metadata = {role: installed(python, environment, directory) for role, python in interpreters.items()} + require(metadata["source"]["powercontext"] == "1.3.0", "Wrong source package") + require(metadata["target"]["powercontext"] == wheel_version(wheel), "Wrong target package") + database = directory / "database" + database_configuration = database_environment(args.backend, database, environment) + environment.update({ + "POWERCONTEXT_HOME": str(directory / "home"), + **database_configuration, + "POWERCONTEXT_SERVER_ACCESS_MODE": "disabled", + }) + probe = [ + "-I", + str(ROOT / "scripts/upgrade_database_probe.py"), + "--backend", + args.backend, + "--database", + str(database), + ] + if args.backend == "oceanbase": + run( + [str(interpreters["source"]), *probe, "--output", str(output / "empty-schema.json")], + environment=environment, + cwd=directory, + log=output / "empty-probe.log", + ) + empty_schema = json.loads((output / "empty-schema.json").read_text()) + require(not empty_schema["tables"], "OceanBase test database must be empty; refusing to overwrite data") + require(empty_schema["tenant_mode"] == "mysql", "OceanBase MySQL tenant required") + seed = source_database(interpreters["source"], directory, environment, output / "source.log") + for role, python in interpreters.items(): + run( + [str(python), *probe, "--output", str(output / f"{role}-schema.json")], + environment=environment, + cwd=directory, + log=output / f"{role}-probe.log", + ) + schema = json.loads((output / "source-schema.json").read_text()) + target_schema = json.loads((output / "target-schema.json").read_text()) + tables = set(schema["tables"]) + require( + {"pc_artifact_candidate_heads", "pc_artifact_candidate_versions", "pc_scopes", "pc_artifacts"} <= tables, + "Source fixture lacks historical Server objects", + ) + env_file = directory / "deployment.env" + env_file.write_text("".join(f"{key}={value}\n" for key, value in database_configuration.items())) + target = str(console(interpreters["target"])) + status_command = [target, "server", "db-migrate", "status", "--env-file", str(env_file)] + if args.backend == "oceanbase": + status_command.extend(["--evidence-dir", str(output / "maintenance"), "--lock-coordination", "single-host"]) + rejected( + status_command, + environment, + directory, + output / "status.log", + ("unknown_baseline", "incompatible_schema") if args.backend != "sqlite" else ("unknown_baseline",), + ) + rejected( + [target, "server", "run", "--no-env-file", "--host", "127.0.0.1", "--port", str(_available_port())], + environment, + directory, + output / "legacy-startup.log", + ("migration_required",), + ) + # Only after both historical checks, mutate this disposable fixture into + # a deliberately incomplete versioned state. This is not a migration. + run( + [str(interpreters["target"]), *probe, "--add-revision", "--output", str(output / "versioned-schema.json")], + environment=environment, + cwd=directory, + log=output / "versioned-probe.log", + ) + rejected( + [target, "server", "run", "--no-env-file", "--host", "127.0.0.1", "--port", str(_available_port())], + environment, + directory, + output / "versioned-startup.log", + ("migration_framework_not_ready",), + ) + evidence = { + "kind": "unsupported-upgrade-regression", + "backend": args.backend, + "source_database": {key: value for key, value in schema.items() if key != "tables"}, + "target_database": {key: value for key, value in target_schema.items() if key != "tables"}, + "installed": metadata, + "wheel_sha256": sha256(wheel), + "seed": seed, + "table_count": len(tables), + "status": "passed", + } + (output / "result.json").write_text(json.dumps(evidence, indent=2) + "\n") + print(f"Actual 1.3.0 {args.backend} database: all three unsupported-upgrade regressions reproduced.") + + +if __name__ == "__main__": + main() diff --git a/scripts/release_version.py b/scripts/release_version.py index 57bf563e31..538f094d95 100644 --- a/scripts/release_version.py +++ b/scripts/release_version.py @@ -33,6 +33,7 @@ # Match current-release contexts only: migration thresholds, release history, # independent plugin versions and third-party dependencies must retain their meaning. REFERENCE_PREFIXES = { + "release/upgrade-support.json": (r'^ "target_version": "',), "openapi/powercontext.yaml": (r"^ version: ",), "README.md": (*INSTALL_REFERENCES, r"PowerContext "), "README_CN.md": (*INSTALL_REFERENCES, r"PowerContext "), @@ -45,13 +46,13 @@ *INSTALL_REFERENCES, r"PowerContext ", r"package `", - r"^To upgrade to ", + r"^To install ", ), "docs/zh/docs/get-started/install-and-run.md": ( *INSTALL_REFERENCES, r"PowerContext ", r"Python 包版本为 `", - r"^升级到 ", + r"^安装 ", ), "integrations/dsh/plugins/powercontext/README.md": INSTALL_REFERENCES, } diff --git a/scripts/run_atomic_memory_clean_migration.py b/scripts/run_atomic_memory_clean_migration.py index fcb93a48f4..d293f393bf 100644 --- a/scripts/run_atomic_memory_clean_migration.py +++ b/scripts/run_atomic_memory_clean_migration.py @@ -57,7 +57,10 @@ def run_node(node: str, directory: Path) -> bool: "-vv", "-ra", "-o", - "faulthandler_timeout=120", + # The CPython watchdog walks other threads without the GIL and can + # itself segfault (python/cpython#116008). Keep native fault dumps; + # GNU timeout above enforces the per-process deadline independently. + "faulthandler_timeout=0", "--durations=20", "-o", f"cache_dir={directory / 'pytest-cache'}", diff --git a/scripts/run_upgrade_release_gate.py b/scripts/run_upgrade_release_gate.py new file mode 100644 index 0000000000..6dd7467d66 --- /dev/null +++ b/scripts/run_upgrade_release_gate.py @@ -0,0 +1,164 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Validate the complete distribution set and gate its publication on upgrades.""" + +import argparse +import hashlib +import json +import shutil +import subprocess +import tarfile +import tempfile +import zipfile +from email.parser import BytesParser +from pathlib import Path + +from upgrade_release_gate import ROOT, GateError, gate, require, sha256, wheel_version + + +def wheel_contents(wheel: Path) -> dict[str, str]: + """Compare installed payloads, excluding only the derived RECORD hash listing.""" + with zipfile.ZipFile(wheel) as archive: + names = [item.filename for item in archive.infolist() if not item.is_dir()] + require(len(set(names)) == len(names), "Duplicate wheel members") + return { + name: hashlib.sha256(archive.read(name)).hexdigest() + for name in names + if not name.endswith(".dist-info/RECORD") + } + + +def validate_sdist(sdist: Path, wheel: Path, work: Path) -> None: + expected = wheel_version(wheel) + require(sdist.name == f"powercontext-{expected}.tar.gz", "Source distribution version differs from the wheel") + with tarfile.open(sdist, "r:gz") as archive: + names = [item for item in archive.getmembers() if item.name == f"powercontext-{expected}/PKG-INFO"] + require(len(names) == 1 and names[0].isfile(), "Source distribution requires one root PKG-INFO") + stream = archive.extractfile(names[0]) + if stream is None: + raise GateError("Cannot read source distribution metadata") # noqa: TRY003 -- operator diagnostic + metadata = BytesParser().parsebytes(stream.read()) + require( + metadata["Name"] == "powercontext" and metadata["Version"] == expected, + "Source distribution metadata differs from the wheel", + ) + rebuilt = work / "rebuilt" + rebuilt.mkdir() + with (work / "sdist-build.log").open("w", encoding="utf-8") as log: + subprocess.run( # noqa: S603 -- reviewed local distribution and fixed build tool; no shell + ["uv", "build", "--wheel", str(sdist), "--out-dir", str(rebuilt)], # noqa: S607 -- project tool on PATH + stdout=log, + stderr=subprocess.STDOUT, + timeout=600, + check=True, + ) + wheels = list(rebuilt.glob("*.whl")) + require(len(wheels) == 1, "Source distribution must build exactly one wheel") + require(wheels[0].name == wheel.name, "Source distribution produces a different wheel filename") + require( + wheel_contents(wheels[0]) == wheel_contents(wheel), + "Source distribution builds different wheel contents; rebuild the complete publication set", + ) + + +def stage_distributions(dist: Path, staging: Path, *, release: bool) -> tuple[Path, list[Path]]: + """Freeze all publication inputs before testing; later dist changes cannot be uploaded.""" + files = sorted(path for path in dist.iterdir() if path.name != ".gitignore") + require( + all(path.is_file() and not path.is_symlink() for path in files), "Distribution inputs must be regular files" + ) + wheels = [path for path in files if path.suffix == ".whl"] + sources = [path for path in files if path.name.endswith(".tar.gz")] + require(len(wheels) == 1, "Build exactly one release wheel before running upgrade acceptance") + require(len(sources) <= 1, "Build at most one source distribution") + require(set(files) == set(wheels + sources), "Unexpected files in publication directory") + require(not release or len(sources) == 1, "Release publication requires one wheel and one source distribution") + staging.mkdir() + staged = [] + for path in files: + copied = staging / path.name + shutil.copyfile(path, copied) + staged.append(copied) + wheel = staging / wheels[0].name + if sources: + validate_sdist(staging / sources[0].name, wheel, staging.parent) + return wheel, staged + + +def accept_distributions(dist: Path, output: Path, *, release: bool, release_notes: str | None) -> list[Path]: + # A private snapshot prevents a leftover or concurrent dist write from joining + # publication after acceptance. Keep the same bytes through testing and upload. + require(not output.exists(), "Use a fresh evidence directory") + with tempfile.TemporaryDirectory(prefix="pc-release-inputs-") as temporary: + work = Path(temporary) + try: + wheel, files = stage_distributions(dist, work / "inputs", release=release) + expected = {path.name: sha256(path) for path in files} + gate(ROOT, wheel, output, release=release, release_notes=release_notes) + require({path.name: sha256(path) for path in files} == expected, "Distribution changed during acceptance") + validated = output / "validated-dist" + validated.mkdir() + accepted = [] + for path in files: + destination = validated / path.name + shutil.copyfile(path, destination) + require(sha256(destination) == expected[path.name], "Validated distribution copy changed") + accepted.append(destination) + (output / "publication.json").write_text( + json.dumps({"artifacts": expected}, indent=2) + "\n", encoding="utf-8" + ) + return accepted + finally: + if (work / "sdist-build.log").is_file(): + output.mkdir(parents=True, exist_ok=True) + shutil.copyfile(work / "sdist-build.log", output / "sdist-build.log") + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--dist", type=Path, default=Path("dist")) + parser.add_argument("--output", type=Path) + parser.add_argument("--release", action="store_true") + parser.add_argument( + "--publish", action="store_true", help="Upload only this run's validated files; requires --release" + ) + notes = parser.add_mutually_exclusive_group() + notes.add_argument("--release-event", type=Path) + notes.add_argument("--release-notes", type=Path) + args = parser.parse_args() + try: + require(not args.publish or args.release, "--publish requires --release") + release_notes = None + if args.release_event: + release_notes = json.loads(args.release_event.read_text(encoding="utf-8"))["release"]["body"] or "" + elif args.release_notes: + release_notes = args.release_notes.read_text(encoding="utf-8") + output = (args.output or Path(tempfile.mkdtemp(prefix="pc-upgrade-evidence-")) / "report").resolve() + accepted = accept_distributions(args.dist.resolve(), output, release=args.release, release_notes=release_notes) + result = json.loads((output / "result.json").read_text(encoding="utf-8")) + print( + f"Upgrade gate passed: {result['mode']}; " + f"{len(result['resolved_matrix']['candidate_cases'])} discovered candidates; " + f"{len(result['cases'])} accepted upgrade combinations. " + f"Evidence: {output / 'result.json'}" + ) + if args.publish: + subprocess.run(["uv", "publish", *map(str, accepted)], check=True) # noqa: S603, S607 -- project tool, exact validated files + except (GateError, OSError, ValueError, subprocess.SubprocessError, tarfile.TarError, zipfile.BadZipFile) as error: + raise SystemExit(f"Upgrade gate failed: {error}") from error # noqa: TRY003 -- operator diagnostic + + +if __name__ == "__main__": + main() diff --git a/scripts/upgrade_database_probe.py b/scripts/upgrade_database_probe.py new file mode 100644 index 0000000000..e591d16b3e --- /dev/null +++ b/scripts/upgrade_database_probe.py @@ -0,0 +1,196 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Inspect a disposable database using the installed release's own driver. + +Run only after the Server has exited. --add-revision creates a deliberately +incomplete control table for a negative fixture; it never represents a migration. +""" + +from __future__ import annotations + +import argparse +import asyncio +import hashlib +import json +import os +import sqlite3 +from collections.abc import Collection +from importlib import import_module +from importlib.metadata import version +from pathlib import Path +from typing import Any +from urllib.parse import unquote, urlsplit + +REVISION_DDL = "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) PRIMARY KEY)" + + +def row_digest(rows): + # The fixture contains no external data; retain only stable hashes, not payloads. + def encode(value): + if isinstance(value, bytes): + return {"bytes": value.hex()} + return {"type": type(value).__name__, "value": str(value)} + + hashes = sorted( + hashlib.sha256(json.dumps(tuple(row), default=encode, sort_keys=True).encode()).hexdigest() for row in rows + ) + return hashlib.sha256("\n".join(hashes).encode()).hexdigest() + + +def vector_snapshot(cursor: Any, tables: Collection[str], *, sqlite: bool) -> dict[str, Any]: + if not os.environ.get("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_MODEL"): + return {} + result = {} + for table in ("pc_atomic_memory_current", "pc_topic_memory_vector_topics", "pc_topic_memory_vector_chunks"): + if table not in tables: + continue + if table == "pc_atomic_memory_current": + query = ( + "SELECT scope_id,artifact_id,embedding,embedding_input_hash,profile_fingerprint " + "FROM pc_atomic_memory_current WHERE embedding IS NOT NULL" + ) + elif sqlite: + helper = { + "pc_topic_memory_vector_topics": "pc_topic_memory_topic_vec", + "pc_topic_memory_vector_chunks": "pc_topic_memory_chunk_vec", + }[table] + query = f"SELECT m.*,v.embedding FROM {table} m JOIN {helper} v ON m.vector_id=v.rowid" # noqa: S608 -- Both names come from the fixed fixture table mapping. + else: + query = f"SELECT * FROM {table} WHERE embedding IS NOT NULL" # noqa: S608 -- The three fixture table names are fixed above. + cursor.execute(query) + rows = cursor.fetchall() + result[table] = {"rows": len(rows), "sha256": row_digest(rows)} + return result + + +def sqlite_snapshot(database: Path, *, add_revision: bool) -> dict[str, Any]: + with sqlite3.connect(database) as connection: + if os.environ.get("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_MODEL"): + connection.enable_load_extension(True) + try: + import_module("sqlite_vec").load(connection) + finally: + connection.enable_load_extension(False) + if add_revision: + connection.execute(REVISION_DDL) + objects = { + plural: dict(connection.execute("SELECT name, sql FROM sqlite_schema WHERE type=? ORDER BY name", (kind,))) + for kind, plural in ( + ("table", "tables"), + ("index", "indexes"), + ("view", "views"), + ("trigger", "triggers"), + ) + } + data = { + name: row_digest(connection.execute('SELECT * FROM "' + name.replace('"', '""') + '"')) # noqa: S608 -- Reflected identifier is quoted. + for name in objects["tables"] + } + vectors = vector_snapshot(connection.cursor(), objects["tables"], sqlite=True) + return { + "data_sha256": data, + "vectors": vectors, + "engine_version": sqlite3.sqlite_version, + "driver_version": version("aiosqlite"), + **objects, + } + + +async def inspect(backend: str, database: Path, *, add_revision: bool, canonical: bool = False) -> dict[str, Any]: + if backend == "sqlite": + return sqlite_snapshot(database, add_revision=add_revision) + # Use the driver's public API, not a current-checkout Profile that could run + # startup guards or mutate an old schema before the target CLI is tested. + pymysql = import_module("pymysql") + instance = None + details = {} + if backend == "seekdb": + binding = import_module("pylibseekdb") + instance = await binding.aopen(str(database)) + options = {**instance.connection_options(), "database": "test"} + details["embedded_binding_version"] = version("pylibseekdb") + else: + url = urlsplit(os.environ["PC_UPGRADE_OCEANBASE_URL"]) + options = { + "host": url.hostname, + "port": url.port or 2881, + "user": unquote(url.username or ""), + "password": unquote(url.password or ""), + "database": url.path.removeprefix("/"), + "charset": "utf8mb4", + "connect_timeout": 15, + } + try: + with pymysql.connect(**options, autocommit=True) as connection, connection.cursor() as cursor: + if add_revision: + cursor.execute(REVISION_DDL) + cursor.execute("SELECT VERSION()") + engine = str(cursor.fetchone()[0]) + if backend == "oceanbase": + cursor.execute("SHOW VARIABLES LIKE 'ob_compatibility_mode'") + details["tenant_mode"] = str(cursor.fetchone()[1]).lower() + details["proxy_version"] = "direct" + cursor.execute("SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'") + names = sorted(row[0] for row in cursor.fetchall()) + tables = {} + data = {} + for name in names: + cursor.execute("SHOW CREATE TABLE `" + name.replace("`", "``") + "`") + tables[name] = cursor.fetchone()[1] + cursor.execute("SELECT * FROM `" + name.replace("`", "``") + "`") # noqa: S608 -- Escaped identifiers reflected from the isolated fixture. + data[name] = row_digest(cursor.fetchall()) + vectors = vector_snapshot(cursor, names, sqlite=False) + cursor.execute("SHOW FULL TABLES WHERE Table_type = 'VIEW'") + views = {} + for name in sorted(row[0] for row in cursor.fetchall()): + cursor.execute("SHOW CREATE VIEW `" + name.replace("`", "``") + "`") + views[name] = cursor.fetchone()[1] + cursor.execute("SHOW TRIGGERS") + triggers = {row[0]: list(row[1:4]) for row in cursor.fetchall()} + logical = None + if canonical: + from powercontext.builtin.persistence.migrations.mysql_schema import _parse_table + + logical = {name: _parse_table(sql, database=options["database"])[1] for name, sql in tables.items()} + return { + "logical_tables": logical, + "data_sha256": data, + "vectors": vectors, + "triggers": triggers, + "engine_version": engine, + "driver_version": version("pymysql"), + **details, + "tables": tables, + "views": views, + } + finally: + if instance is not None: + instance.close() + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--backend", choices=("sqlite", "seekdb", "oceanbase"), required=True) + parser.add_argument("--database", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--add-revision", action="store_true") + parser.add_argument("--canonical", action="store_true") + args = parser.parse_args() + result = asyncio.run(inspect(args.backend, args.database, add_revision=args.add_revision, canonical=args.canonical)) + args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/scripts/upgrade_process.py b/scripts/upgrade_process.py new file mode 100644 index 0000000000..949f97b3e4 --- /dev/null +++ b/scripts/upgrade_process.py @@ -0,0 +1,139 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Own one Linux upgrade command and reap its descendants, including new sessions.""" + +import argparse +import ctypes +import os +import signal +import subprocess +import sys +import time +from pathlib import Path + + +def own_descendants(expected_parent: int) -> None: + if sys.platform != "linux": + raise RuntimeError("Installed-release acceptance requires Linux") # noqa: TRY003 + # A separate, single-threaded supervisor cannot adopt unrelated gate/test + # children. setsid() does not escape Linux subreaper ancestry. + libc = ctypes.CDLL(None, use_errno=True) + if libc.prctl(36, 1, 0, 0, 0) != 0: # PR_SET_CHILD_SUBREAPER + error = ctypes.get_errno() + raise OSError(error, os.strerror(error)) + if libc.prctl(1, signal.SIGTERM, 0, 0, 0) != 0: # PR_SET_PDEATHSIG + error = ctypes.get_errno() + raise OSError(error, os.strerror(error)) + if os.getppid() != expected_parent: + raise RuntimeError("The gate exited before process ownership was established") # noqa: TRY003 + signal.signal(signal.SIGCHLD, signal.SIG_DFL) + + +def children() -> list[int]: + return [int(value) for value in Path(f"/proc/self/task/{os.getpid()}/children").read_text().split()] + + +def reap_finished(exclude: int | None = None) -> set[int]: + reaped = set() + for pid in children(): + if pid != exclude and os.waitpid(pid, os.WNOHANG)[0]: + reaped.add(pid) + return reaped + + +def wait_command(process: subprocess.Popen[bytes], timeout: float) -> int: + deadline = time.monotonic() + timeout + while (result := process.poll()) is None: + # A live command may wait for a double-forked daemon to disappear. + # Reap already-exited adoptees without disturbing any live descendants. + reap_finished(exclude=process.pid) + if time.monotonic() >= deadline: + raise subprocess.TimeoutExpired(process.args, timeout) + time.sleep(0.02) + return result + + +def reap_descendants(shutdown_timeout: float) -> None: + deadline = time.monotonic() + shutdown_timeout + notified = {} + while True: + for pid in reap_finished(): + notified.pop(pid, None) + owned = children() + if not owned: + return + requested = signal.SIGTERM if time.monotonic() < deadline else signal.SIGKILL + for pid in owned: + if notified.get(pid) != requested: + # These are our direct children, never a global PID scan. No + # wait/reap happens between ownership inspection and signalling, + # so even an exited child's PID cannot be reused here. + os.kill(pid, requested) + notified[pid] = requested + # Killing a parent can adopt another detached generation. Keep owning + # and reaping it before returning, rather than leaving it to init. As + # with Popen.wait(), SIGKILL still waits for kernel-level exit/reaping; + # an uninterruptible kernel wait must not admit the next case early. + time.sleep(0.02) + + +def supervise(command: list[str], timeout: float, shutdown_timeout: float, expected_parent: int) -> int: + def interrupted(_signal, _frame): + raise KeyboardInterrupt + + signal.signal(signal.SIGTERM, interrupted) + own_descendants(expected_parent) + process = None + try: + process = subprocess.Popen(command) # noqa: S603 -- fixed gate command, no shell + try: + result = wait_command(process, timeout) + except subprocess.TimeoutExpired: + print(f"Upgrade command exceeded {timeout:g} seconds; closing owned processes", file=sys.stderr) + result = 124 + except KeyboardInterrupt: + result = 130 + finally: + # A repeated cancellation must not interrupt ownership cleanup. + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + if process is not None and process.poll() is None: + # Let pytest unwind fixtures and let the Server close SQL sessions + # and seekDB normally before forcing termination. + process.send_signal(signal.SIGINT) + try: + wait_command(process, shutdown_timeout) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + reap_descendants(shutdown_timeout) + return result if result >= 0 else 128 - result + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--parent-pid", type=int, required=True) + parser.add_argument("--timeout", type=float, required=True) + parser.add_argument("--shutdown-timeout", type=float, default=30) + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + command = args.command[1:] if args.command[:1] == ["--"] else args.command + if not command or args.timeout <= 0 or args.shutdown_timeout <= 0: + parser.error("A command and positive time limits are required") + return supervise(command, args.timeout, args.shutdown_timeout, args.parent_pid) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/upgrade_release_gate.py b/scripts/upgrade_release_gate.py new file mode 100644 index 0000000000..7044273311 --- /dev/null +++ b/scripts/upgrade_release_gate.py @@ -0,0 +1,546 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Gate upgrade claims on this build's complete, installed-release acceptance.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import re +import shutil +import signal +import subprocess +import sys +import tempfile +import zipfile +from collections.abc import Iterator +from contextlib import contextmanager +from email.parser import Parser +from pathlib import Path +from typing import Any +from xml.etree import ElementTree + +from upgrade_release_matrix import STABLE, resolve_matrix + +ROOT = Path(__file__).resolve().parents[1] +MANIFEST = Path("release/upgrade-support.json") +MANAGED_INPUTS = ( + "src/powercontext/builtin/persistence", + "src/powercontext/builtin/code", + "src/powercontext/server/identity.py", + "src/powercontext/server/authz", + "src/powercontext/limits.py", +) +PHASES = frozenset({ + "test_source_release_database", + "test_public_migration_commands", + "test_legacy_startup_is_rejected", + "test_repeated_migration", + "test_target_startup", + "test_read_write_and_retrieval", + "test_authorization", + "test_candidates_and_processing", + "test_background_processing", + "test_preserved_history_and_cursors", + "test_fresh_upgrade_equivalence", +}) +VERSION = r"\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?" +NOTICE = { + "en": "Upgrades from 1.3.0 require passing full Server acceptance on SQLite, seekDB and OceanBase. This migration path is not yet verified; do not upgrade existing databases.", + "zh": "1.3.0 升级必须通过 SQLite、seekDB 和 OceanBase 的完整 Server 验收。当前迁移路径尚未验证,请勿升级已有数据库。", # noqa: RUF001 -- Chinese prose +} + + +class GateError(ValueError): + """An upgrade claim has no valid declaration or passing acceptance.""" + + +def require(condition: Any, message: str) -> None: + if not condition: + raise GateError(message) + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def managed_digest(root: Path) -> str: + """Bind the review declaration to all persistence inputs, not a new-file count. + + This deliberately over-approximates schema changes. It is a review trigger, + not a claim to prove SQL or data-format compatibility from Python syntax. + Normalize checkout line endings; artifact hashes must retain their exact bytes. + """ + inputs = [root / relative for relative in MANAGED_INPUTS] + files = { + path.relative_to(root).as_posix(): hashlib.sha256(path.read_bytes().replace(b"\r\n", b"\n")).hexdigest() + for location in inputs + for path in (location.rglob("*") if location.is_dir() else [location]) + if path.is_file() and path.suffix in {".py", ".sql", ".json"} + } + require(files, "No managed persistence inputs found") + return hashlib.sha256(json.dumps(files, sort_keys=True).encode()).hexdigest() + + +def load_policy(root: Path) -> dict[str, Any]: + policy = json.loads((root / MANIFEST).read_text(encoding="utf-8")) + require( + set(policy) == {"format", "target_version", "mode", "schema_review", "cases", "automatic_sources"}, + "Invalid policy fields", + ) + require(policy["format"] == 2, "Unsupported upgrade policy format") + contract = (root / "openapi/powercontext.yaml").read_text(encoding="utf-8") + match = re.search(rf"^ version: ({VERSION})$", contract, re.MULTILINE) + require( + match is not None and match[1] == policy["target_version"], "Upgrade policy target differs from API release" + ) + require(policy["mode"] in {"upgrade-required", "supported-upgrades"}, "Unknown upgrade policy mode") + review = policy["schema_review"] + require(set(review) == {"persistence_sha256", "rationale"}, "Invalid schema review fields") + require( + isinstance(review["rationale"], str) and review["rationale"].strip(), "Schema compatibility rationale required" + ) + require( + review["persistence_sha256"] == managed_digest(root), + "Persistence changed (file set or content; CRLF/LF normalized): update the compatibility review", + ) + cases = policy["cases"] + require(isinstance(cases, list), "Upgrade cases must be a list") + automatic = policy["automatic_sources"] + profiles = [] + if automatic is not None: + require(set(automatic) == {"strategy", "source_version", "profiles"}, "Invalid automatic source policy") + require(automatic["strategy"] == "fixed-version", "Unknown source selection strategy") + require( + isinstance(automatic["source_version"], str) and re.fullmatch(STABLE, automatic["source_version"]), + "Fixed source must be a stable version", + ) + profiles = automatic["profiles"] + require(isinstance(profiles, list) and profiles, "Automatic sources require backend profiles") + for profile in profiles: + require(set(profile) == {"backend", "configuration", "suite"}, "Invalid automatic backend profile") + require(bool(cases or profiles), "Upgrade acceptance requires a nonempty matrix") + required_backends = {"sqlite", "seekdb", "oceanbase"} + covered = {case["backend"] for case in cases if case.get("source_version") == "1.3.0"} | ( + {profile["backend"] for profile in profiles} if automatic and automatic["source_version"] == "1.3.0" else set() + ) + require(covered >= required_backends, "The 1.3.0 upgrade baseline requires SQLite, seekDB and OceanBase") + identities: set[tuple[str, str, str]] = set() + for case in cases: + require(set(case) == {"source_version", "backend", "configuration", "suite"}, "Invalid upgrade case fields") + require( + isinstance(case["source_version"], str) and re.fullmatch(VERSION, case["source_version"]), + "Pin a source release", + ) + for case in [*cases, *profiles]: + require(case["backend"] in {"sqlite", "seekdb", "oceanbase"}, "Unknown backend") + require(re.fullmatch(r"[a-z][a-z0-9-]*", case["configuration"]), "Configuration needs a stable identifier") + identity = (case.get("source_version", "automatic"), case["backend"], case["configuration"]) + require(identity not in identities, "Duplicate upgrade combination") + identities.add(identity) + require( + isinstance(case["suite"], str) and case["suite"], + "A real acceptance suite is required for every backend profile", + ) + suite = (root / case["suite"]).resolve() + require( + suite.is_relative_to((root / "tests/release_upgrades").resolve()), + "Suite must be under tests/release_upgrades", + ) + require( + suite.is_file() and suite.name.startswith("test_") and suite.suffix == ".py", "Missing acceptance suite" + ) + return policy + + +def support_text(policy: dict[str, Any], language: str) -> str: + if policy["mode"] == "upgrade-required": + return NOTICE[language] + title = ( + "Supported database upgrades (other combinations are unsupported):" + if language == "en" + else "支持的数据库升级组合(其他组合不受支持):" # noqa: RUF001 -- Chinese prose + ) + lines = [title, "", "| Source | Target | Backend | Configuration |", "| --- | --- | --- | --- |"] + lines.extend( + f"| {case['source_version']} | {policy['target_version']} | {case['backend']} | {case['configuration']} |" + for case in policy["cases"] + ) + if automatic := policy["automatic_sources"]: + lines.extend( + f"| {automatic['source_version']} | {policy['target_version']} | {profile['backend']} | {profile['configuration']} |" + for profile in automatic["profiles"] + ) + return "\n".join(lines) + + +def check_docs(root: Path, policy: dict[str, Any], *, write: bool = False) -> None: + for language in ("en", "zh"): + path = root / f"docs/{language}/docs/get-started/install-and-run.md" + content = path.read_text(encoding="utf-8") + pattern = r"(?<=\n).*?(?=\n)" + matches = list(re.finditer(pattern, content, re.DOTALL)) + require(len(matches) == 1, f"{path.name}: exactly one upgrade-support region required") + expected = support_text(policy, language) + if write: + match = matches[0] + path.write_text(content[: match.start()] + expected + content[match.end() :], encoding="utf-8") + else: + require(matches[0][0] == expected, f"{language} upgrade support instructions differ from the policy") + + +def validate_junit(report: Path) -> None: + tree = ElementTree.parse(report) # noqa: S314 -- locally produced acceptance report + cases = list(tree.iter("testcase")) + require(cases, "No acceptance tests executed") + require(not any(list(tree.iter(tag)) for tag in ("failure", "error", "skipped")), "Acceptance failed or skipped") + names = {case.attrib.get("name", "").split("[", 1)[0] for case in cases} + require(names >= PHASES, f"Missing full Server acceptance phases: {sorted(PHASES - names)}") + + +def validate_backend_evidence(path: Path, backend: str) -> dict[str, Any]: + evidence = json.loads(path.read_text(encoding="utf-8")) + require(evidence.get("backend") == backend, "Backend evidence does not match the declared case") + for key in ("engine_version", "driver_version", "source_schema", "target_schema", "fresh_schema"): + require(isinstance(evidence.get(key), str) and evidence[key].strip(), f"Missing backend evidence: {key}") + if backend == "oceanbase": + require(evidence.get("tenant_mode") == "mysql", "OceanBase MySQL tenant evidence required") + require( + isinstance(evidence.get("proxy_version"), str) and evidence["proxy_version"], + "Record proxy version or direct", + ) + return evidence + + +def run(command: list[str], *, environment: dict[str, str], cwd: Path, log: Path) -> None: + with log.open("w", encoding="utf-8") as output: + result = subprocess.run( # noqa: S603 -- fixed tools and reviewed repository test paths, no shell + command, env=environment, cwd=cwd, stdout=output, stderr=subprocess.STDOUT, timeout=1800, check=False + ) + require(result.returncode == 0, f"Command failed; inspect {log.name}") + + +def run_owned( + command: list[str], + *, + environment: dict[str, str], + cwd: Path, + log: Path, + timeout: float = 1800, + shutdown_timeout: float = 30, +) -> None: + require(sys.platform == "linux", "Installed-release acceptance requires Linux process ownership and cleanup") + supervised = [ + sys.executable, + str(Path(__file__).with_name("upgrade_process.py")), + "--parent-pid", + str(os.getpid()), + "--timeout", + str(timeout), + "--shutdown-timeout", + str(shutdown_timeout), + "--", + *command, + ] + # The supervisor enforces the deadline and reaps every owned descendant, + # including detached native engines, before workspace cleanup can run. + with ( + log.open("w", encoding="utf-8") as output, + subprocess.Popen( # noqa: S603 -- fixed gate supervisor and reviewed command, no shell + supervised, env=environment, cwd=cwd, stdout=output, stderr=subprocess.STDOUT, start_new_session=True + ) as process, + ): + try: + returncode = process.wait() + except BaseException: + # Repeated interrupts must not release the workspace before the + # owner has finished closing descendants. + previous = signal.signal(signal.SIGINT, signal.SIG_IGN) + try: + process.terminate() + process.wait() + finally: + signal.signal(signal.SIGINT, previous) + raise + require(returncode == 0, f"Command failed; inspect {log.name}") + + +def installed(python: Path, environment: dict[str, str], cwd: Path) -> dict[str, str]: + result = subprocess.run( # noqa: S603 -- isolated environment interpreter + [ + str(python), + "-I", + "-c", + "import json,platform; from importlib.metadata import version; " + "print(json.dumps({'powercontext': version('powercontext'), 'python': platform.python_version()}))", + ], + env=environment, + cwd=cwd, + capture_output=True, + text=True, + check=True, + timeout=30, + ) + return json.loads(result.stdout) + + +def wheel_version(wheel: Path) -> str: + with zipfile.ZipFile(wheel) as archive: + names = [name for name in archive.namelist() if name.endswith(".dist-info/METADATA")] + require(len(names) == 1, "Expected exactly one wheel distribution") + metadata = Parser().parsestr(archive.read(names[0]).decode()) + require(metadata["Name"] == "powercontext", "Expected a PowerContext wheel") + return str(metadata["Version"]) + + +def _preserve_seekdb_logs(directory: Path, output: Path) -> None: + """Retain bounded diagnostics from synthetic acceptance stores, excluding database files.""" + + remaining = 16 * 1024 * 1024 + files = [] + with zipfile.ZipFile(output / "seekdb-native-logs.zip", "w", compression=zipfile.ZIP_DEFLATED) as archive: + for name in ("database", "fresh"): + for path in sorted((directory / name).rglob("*.log*")): + if remaining == 0: + break + if path.is_symlink() or not path.is_file(): + continue + with path.open("rb") as source: + size = source.seek(0, os.SEEK_END) + length = min(size, 4 * 1024 * 1024, remaining) + source.seek(size - length) + content = source.read(length) + relative = path.relative_to(directory).as_posix() + archive.writestr(relative, content) + remaining -= len(content) + files.append({ + "path": relative, + "size_bytes": size, + "captured_bytes": len(content), + "truncated": len(content) < size, + }) + usage = shutil.disk_usage(directory) + manifest = { + "files": files, + "budget_exhausted": remaining == 0, + "filesystem": {"total_bytes": usage.total, "used_bytes": usage.used, "free_bytes": usage.free}, + } + (output / "seekdb-native-logs.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") + + +@contextmanager +def case_workspace(backend: str, output: Path) -> Iterator[Path]: + # Keep embedded sockets under a short private path. Native logs must leave + # this directory before cleanup, including when acceptance raises or times out. + with tempfile.TemporaryDirectory(prefix="pc-up-", dir="/tmp" if os.name != "nt" else None) as temporary: # noqa: S108 -- private mkdtemp directory + directory = Path(temporary) + try: + yield directory + finally: + if backend == "seekdb": + try: + _preserve_seekdb_logs(directory, output) + except OSError as error: + # Diagnostics must not replace the acceptance failure. + print(f"seekDB native log collection failed: {type(error).__name__}", file=sys.stderr) + + +def run_case(root: Path, wheel: Path, case: dict[str, Any], output: Path) -> dict[str, Any]: + output.mkdir(parents=True, exist_ok=False) + # Never inherit a developer's live deployment or pytest selection overrides. + environment = { + key: value + for key, value in os.environ.items() + if not key.startswith(("POWERCONTEXT_", "PYTEST_")) and key not in {"PYTHONPATH", "PYTHONHOME", "VIRTUAL_ENV"} + } + with case_workspace(case["backend"], output) as directory: + extras = "cli,server,seekdb" if case["backend"] == "seekdb" else "cli,server" + if case["configuration"] in {"full-server-code", "full-server-vector-code"}: + extras += ",code" + interpreters = {} + artifact = case["source_artifact"] + packages = { + "source": f"powercontext[{extras}] @ {artifact['url']}#sha256={artifact['sha256']}", + "target": f"{wheel}[{extras}]", + } + for role, package in packages.items(): + venv = directory / role + run_owned( + ["uv", "venv", "--python", sys.executable, str(venv)], + environment=environment, + cwd=directory, + log=output / f"{role}-venv.log", + ) + python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + run_owned( + ["uv", "pip", "install", "--python", str(python), package], + environment=environment, + cwd=directory, + log=output / f"{role}-install.log", + ) + interpreters[role] = python + metadata = {role: installed(python, environment, directory) for role, python in interpreters.items()} + require(metadata["source"]["powercontext"] == case["source_version"], "Source release mismatch") + require(metadata["target"]["powercontext"] == wheel_version(wheel), "Target wheel version mismatch") + (output / "installed.json").write_text(json.dumps(metadata, indent=2) + "\n", encoding="utf-8") + environment.update({ + "PC_UPGRADE_REPORT_DIR": str(output), + "PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1", + "PC_UPGRADE_SOURCE_PYTHON": str(interpreters["source"]), + "PC_UPGRADE_SOURCE_VERSION": case["source_version"], + "PC_UPGRADE_TARGET_PYTHON": str(interpreters["target"]), + "PC_UPGRADE_BACKEND": case["backend"], + "PC_UPGRADE_CONFIGURATION": case["configuration"], + "PC_UPGRADE_WORK": str(directory), + "PC_UPGRADE_EVIDENCE": str(output / "backend.json"), + }) + report = output / "junit.xml" + run_owned( + [ + sys.executable, + "-m", + "pytest", + "-c", + "/dev/null" if os.name != "nt" else "NUL", + "--confcutdir", + str(root / "tests/release_upgrades"), + "--strict-markers", + "-p", + "no:cacheprovider", + "-o", + "xfail_strict=true", + "-ra", + f"--junitxml={report}", + str(root / case["suite"]), + ], + environment=environment, + cwd=directory, + log=output / "acceptance.log", + ) + validate_junit(report) + backend = validate_backend_evidence(output / "backend.json", case["backend"]) + require(backend.get("configuration") == case["configuration"], "Configuration evidence differs from the case") + if case["configuration"] in {"full-server-code", "full-server-vector-code"}: + code = backend.get("code") + if not isinstance(code, dict): + raise GateError("Installed Code acceptance evidence required") # noqa: TRY003 -- operator diagnostic + require( + re.fullmatch(r"[0-9a-f]{64}", str(code.get("fingerprint", ""))) + and all( + code.get(field) is True + for field in ("immutable_cache_files_preserved", "symbol_ids_preserved", "server_query_ready") + ), + "The installed Code index must remain queryable with its original immutable identity", + ) + if case["configuration"] in {"full-server-vector", "full-server-vector-code"}: + vectors = backend.get("vectors") + if not isinstance(vectors, dict): + raise GateError("Installed vector acceptance evidence required") # noqa: TRY003 -- operator diagnostic + require( + vectors.get("dimension") == (4 if case["configuration"].endswith("-code") else 3) + and vectors.get("maintenance_embedding_requests") == 0 + and all( + vectors.get(field) is True + for field in ( + "historical_atomic_and_topic_embeddings_preserved", + "historical_and_new_vector_reads", + "fresh_vector_read", + "profile_drift_rejected", + ) + ) + and (case["backend"] != "sqlite" or vectors.get("missing_sqlite_helper_rejected") is True), + "Installed vector acceptance must preserve and retrieve the original embedding space", + ) + return {"case": case, "installed": metadata, "database": backend, "status": "passed"} + + +def gate(root: Path, wheel: Path, output: Path, *, release: bool = False, release_notes: str | None = None) -> None: + policy = load_policy(root) + check_docs(root, policy) + actual_version = wheel_version(wheel) + if release: + require(actual_version == policy["target_version"], "Wheel version differs from upgrade policy target") + require(release_notes is not None, "Published release notes must declare upgrade support") + require( + support_text(policy, "en") in (release_notes or ""), + "GitHub Release notes must include the exact upgrade support declaration", + ) + # A fresh output directory prevents accepting stale reports from another build. + output.mkdir(parents=True, exist_ok=False) + evidence: dict[str, Any] = { + "policy_sha256": sha256(root / MANIFEST), + "target_version": actual_version, + "target_wheel_sha256": sha256(wheel), + "mode": policy["mode"], + "cases": [], + "status": "failed", + } + try: + matrix = resolve_matrix(policy, actual_version) + evidence["resolved_matrix"] = matrix + require(matrix["required_cases"], "No required upgrade acceptance cases") + failures = [] + for index, case in enumerate(matrix["required_cases"]): + try: + evidence["cases"].append(run_case(root, wheel, case, output / f"case-{index}")) + except (GateError, OSError, ValueError, subprocess.SubprocessError, ElementTree.ParseError) as error: + # A broken backend must not prevent the other required backends + # from running and producing their own failure evidence. + failures.append(case["backend"]) + evidence["cases"].append({"case": case, "status": "failed", "error": str(error)}) + require(not failures, f"Required upgrade acceptance failed: {', '.join(failures)}") + if release: + require( + policy["mode"] == "supported-upgrades", + "Upgrade acceptance passed; update the reviewed support declaration before publication", + ) + evidence["status"] = "passed" + finally: + (output / "result.json").write_text(json.dumps(evidence, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--wheel", type=Path) + parser.add_argument("--output", type=Path) + parser.add_argument("--release", action="store_true") + parser.add_argument("--release-notes", type=Path) + parser.add_argument("--write-docs", action="store_true") + args = parser.parse_args() + try: + policy = load_policy(ROOT) + check_docs(ROOT, policy, write=args.write_docs) + if args.wheel: + require(args.output is not None, "--output is required with --wheel") + gate( + ROOT, + args.wheel.resolve(), + args.output.resolve(), + release=args.release, + release_notes=args.release_notes.read_text(encoding="utf-8") if args.release_notes else None, + ) + else: + require(not args.release, "--release requires the actual release wheel") + print(f"Upgrade policy: {policy['mode']}; source/backend combinations are resolved against the target wheel") + except (GateError, OSError, ValueError, subprocess.SubprocessError, ElementTree.ParseError) as error: + print(f"Upgrade release gate failed: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/upgrade_release_matrix.py b/scripts/upgrade_release_matrix.py new file mode 100644 index 0000000000..9ed8cbb731 --- /dev/null +++ b/scripts/upgrade_release_matrix.py @@ -0,0 +1,88 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Resolve a fixed source release into immutable source wheels and backend cases.""" + +from __future__ import annotations + +import hashlib +import json +import re +import urllib.request +from typing import Any +from urllib.parse import urlsplit + +from packaging.version import Version + +CATALOG_URL = "https://pypi.org/pypi/powercontext/json" +STABLE = r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)" + + +def fetch_catalog() -> dict[str, Any]: + # A discovery outage must fail closed, never reduce the matrix to zero cases. + with urllib.request.urlopen(CATALOG_URL, timeout=30) as response: # noqa: S310 -- fixed public PyPI endpoint + return json.load(response) + + +def source_artifact(catalog: dict[str, Any], version: str) -> dict[str, str]: + files = catalog["releases"].get(version, []) + wheels = [ + item + for item in files + if item.get("filename") == f"powercontext-{version}-py3-none-any.whl" + and item.get("packagetype") == "bdist_wheel" + and not item.get("yanked", False) + ] + if len(wheels) != 1: + raise ValueError(f"Required source {version} has no unique non-yanked universal wheel") # noqa: TRY003 + wheel = wheels[0] + url = urlsplit(wheel["url"]) + digest = wheel["digests"]["sha256"] + if ( + url.scheme != "https" + or url.netloc != "files.pythonhosted.org" + or url.fragment + or not re.fullmatch(r"[0-9a-f]{64}", digest) + ): + raise ValueError(f"Invalid source artifact metadata for {version}") # noqa: TRY003 + return {"filename": wheel["filename"], "url": wheel["url"], "sha256": digest} + + +def resolve_matrix(policy: dict[str, Any], target: str) -> dict[str, Any]: + automatic = policy["automatic_sources"] + explicit = policy["cases"] + if automatic is None and not explicit: + raise ValueError("Upgrade acceptance requires a nonempty matrix") # noqa: TRY003 + catalog = fetch_catalog() + if not isinstance(catalog.get("releases"), dict) or not catalog["releases"]: + raise ValueError("Empty or malformed PyPI release catalog") # noqa: TRY003 + cases = [dict(case) for case in explicit] + if automatic: + cases.extend({"source_version": automatic["source_version"], **profile} for profile in automatic["profiles"]) + identities = set() + for case in cases: + version = case["source_version"] + if Version(version) >= Version(target): + raise ValueError(f"Source {version} must precede target {target}") # noqa: TRY003 + identity = (version, case["backend"], case["configuration"]) + if identity in identities: + raise ValueError(f"Duplicate resolved upgrade case: {identity}") # noqa: TRY003 + identities.add(identity) + case["source_artifact"] = source_artifact(catalog, version) + return { + "catalog_url": CATALOG_URL, + "catalog_sha256": hashlib.sha256(json.dumps(catalog, sort_keys=True).encode()).hexdigest(), + "candidate_cases": cases, + "required_cases": cases, + } diff --git a/src/powercontext/builtin/code/seekdb.py b/src/powercontext/builtin/code/seekdb.py index 298e031e29..f7a1dce8f8 100644 --- a/src/powercontext/builtin/code/seekdb.py +++ b/src/powercontext/builtin/code/seekdb.py @@ -203,6 +203,7 @@ def initialize(self, deadline: float) -> None: with self.connection(deadline) as connection, connection.cursor() as cursor: cursor.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()") existing = {row[0] for row in cursor.fetchall()} + managed = "pc_schema_revision" in existing # Even IF NOT EXISTS acquires seekdb's DDL lock. Reopening a complete # index must not wait for unrelated internal schema initialization. for table, statement in zip( @@ -210,12 +211,16 @@ def initialize(self, deadline: float) -> None: ): check_deadline(deadline) if table not in existing: + if managed: + raise CodeError("code_storage_unavailable") cursor.execute(statement) cursor.execute( "SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() " "AND table_name = 'pc_code_nodes' AND index_name = 'ix_code_search'" ) if cursor.fetchone()[0] == 0: + if managed: + raise CodeError("code_storage_unavailable") try: cursor.execute( "CREATE FULLTEXT INDEX ix_code_search ON pc_code_nodes (searchable_text) WITH PARSER SPACE" diff --git a/src/powercontext/builtin/persistence/dream_schema.py b/src/powercontext/builtin/persistence/dream_schema.py index 41d7345af1..976708a891 100644 --- a/src/powercontext/builtin/persistence/dream_schema.py +++ b/src/powercontext/builtin/persistence/dream_schema.py @@ -31,26 +31,30 @@ def __init__(self, reason: str, detail: str) -> None: self.reason = reason super().__init__( f"{reason}: {detail}. Startup never migrates Dream storage. The versioned bundle accepts only empty " - "targets and registered historical layouts, so it cannot upgrade a complete Server database yet; see " + "targets and registered historical layouts; select the full Server bundle for supported upgrades. See " "docs/operate/database-migrations.md for the current acceptance boundary." ) -async def assert_dream_schema_ready(engine: AsyncEngine) -> None: +async def assert_dream_schema_ready(engine: AsyncEngine, *, _archive_recovery: bool = False) -> None: """Check before create_all, domain initializers, or workers; never execute DDL.""" async with engine.connect() as connection: - await connection.run_sync(check_dream_schema) + if connection.dialect.name == "sqlite": + # SQLite's legacy transaction mode does not begin a transaction for + # SELECTs. Pin one read snapshot before checking authority against + # projections, while still allowing concurrent WAL writers. + await connection.exec_driver_sql("BEGIN") + await connection.run_sync(check_dream_schema, _archive_recovery=_archive_recovery) -def check_dream_schema(connection: Connection) -> None: +def check_dream_schema(connection: Connection, *, _archive_recovery: bool = False) -> None: inspector = inspect(connection) names = set(inspector.get_table_names()) if "pc_schema_revision" in names: - # Until RFC 1771's complete Server gate is integrated, never turn the - # four-table acceptance prototype into a business DB using create_all. - raise DreamSchemaNotReadyError( - "migration_framework_not_ready", "Full Server readiness for versioned databases is not integrated" - ) + from powercontext.builtin.persistence.migrations.server_readiness import check_server_schema + + check_server_schema(connection, _archive_recovery=_archive_recovery) + return if names & {"pc_catalog_change_candidate_heads", "pc_catalog_change_candidate_versions"}: raise DreamSchemaNotReadyError("unknown_baseline", "Unpublished Catalog Change storage is unsupported") old = names & {"pc_artifact_candidate_heads", "pc_artifact_candidate_versions"} diff --git a/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/oceanbase.json b/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/oceanbase.json new file mode 100644 index 0000000000..70449250ec --- /dev/null +++ b/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/oceanbase.json @@ -0,0 +1,11144 @@ +{ + "format": "powercontext.server-baseline.v1", + "release": "1.3.0", + "backend": "oceanbase", + "source_artifact": { + "filename": "powercontext-1.3.0-py3-none-any.whl", + "sha256": "8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5", + "uploaded_at": "2026-10-12T03:55:56.682348Z" + }, + "capture": { + "python": "3.11.15", + "sqlalchemy": "2.0.51", + "driver": "aiomysql", + "engine": "5.7.25-OceanBase_CE-v4.3.5.6", + "image_digest": "sha256:31086a6900c21c479c2bcd942b6a28c53b17a51f4e9b9eb8eafcc596adfcd2e3", + "access_mode": "enforced", + "processing_role": "all", + "initialization": "Installed release Server; no model or embedding provider; direct MySQL tenant connection" + }, + "schema_fingerprint": "049414cd6208742a92fdbc1a2965bf37c397ce7e7f1823e7d89ab6445040fedb", + "inventory": { + "tables": { + "pc_access_audit": { + "columns": { + "cursor": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [ + "auto_increment" + ] + }, + "event_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "occurred_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "transport": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "actor_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "actor_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "actor_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "action": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "resource_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "deployment_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "allowed": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "role": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "expected_version": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "result_version": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "cursor", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "event_id": { + "columns": [ + [ + "event_id", + null, + "asc" + ] + ], + "unique": true + } + }, + "auto_increment_mode": "order" + }, + "pc_access_idempotency": { + "columns": { + "actor_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "payload_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "secondary_binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "actor_id", + null, + "asc" + ], + [ + "idempotency_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_access_idempotency_actor_key": { + "columns": [ + [ + "actor_id", + null, + "asc" + ], + [ + "idempotency_key_hash", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_access_owners": { + "columns": { + "owner_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "object_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "established_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "owner_kind", + null, + "asc" + ], + [ + "object_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_owners_kind": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "owner_kind" + ], + [ + "literal", + "artifact" + ] + ], + [ + "is_not_null", + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "candidate_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "established_at" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "policy_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "owner_kind" + ], + [ + "literal", + "candidate" + ] + ], + [ + "is_not_null", + [ + "column", + "candidate_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "selector_type" + ] + ] + ], + [ + "is_null", + [ + "column", + "selector_entry_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "established_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "policy_revision" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_access_relationship_heads": { + "columns": { + "name": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_relationship_heads_revision_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_access_relationships": { + "columns": { + "binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "resource_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "resource_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "deployment_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "role": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "singleton_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "expires_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_relationships_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "singleton_key": { + "columns": [ + [ + "singleton_key", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_agent_skill_targets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "display_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "agent_kind": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_scope": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "delivery_mode": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "enrollment_token_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "enrollment_expires_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "credential_subject": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "credential_verifier": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "receiver_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "environment_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "machine_hostname": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "workspace_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_seen_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_agent_skill_targets_agent_kind": { + "expression": [ + "in", + [ + "column", + "agent_kind" + ], + [ + [ + "literal", + "codex" + ], + [ + "literal", + "claude_code" + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_installation_scope": { + "expression": [ + "binary", + "=", + [ + "column", + "installation_scope" + ], + [ + "literal", + "project" + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_delivery_mode": { + "expression": [ + "binary", + "=", + [ + "column", + "delivery_mode" + ], + [ + "literal", + "agent_pull" + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_state": { + "expression": [ + "in", + [ + "column", + "state" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "active" + ], + [ + "literal", + "revoked" + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_state_payload": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_not_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "installation_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_subject" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_verifier" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "active" + ] + ], + [ + "is_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "installation_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "credential_subject" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "credential_verifier" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "revoked" + ] + ], + [ + "is_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_verifier" + ] + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_agent_skill_targets_installation": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "agent_kind", + null, + "asc" + ], + [ + "installation_scope", + null, + "asc" + ], + [ + "installation_id", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_enrollment_token": { + "columns": [ + [ + "enrollment_token_digest", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_credential_subject": { + "columns": [ + [ + "credential_subject", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_credential_verifier": { + "columns": [ + [ + "credential_verifier", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_artifact_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_artifact_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_versions_target_complete": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "target_family" + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "target_family" + ] + ], + [ + "is_not_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "target_revision" + ], + [ + "number", + 0 + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "lifecycle_state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "active" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "replacement_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "merged_into_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "governance_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_heads_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_lifecycle_state": { + "expression": [ + "in", + [ + "column", + "lifecycle_state" + ], + [ + [ + "literal", + "active" + ], + [ + "literal", + "deprecated" + ], + [ + "literal", + "retired" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_governance_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "governance_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_replacement_deprecated": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "replacement_artifact_id" + ] + ], + [ + "binary", + "=", + [ + "column", + "lifecycle_state" + ], + [ + "literal", + "deprecated" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_merge_target": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "merged_into_id" + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "lifecycle_state" + ], + [ + "literal", + "deprecated" + ] + ], + [ + "binary", + "<>", + [ + "column", + "merged_into_id" + ], + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "replacement_artifact_id" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_artifact_heads_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_artifact_lineage_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "upstream_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "upstream_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "upstream_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "is_merge_input": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "upstream_family", + "upstream_artifact_id", + "upstream_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_artifact_lineage_sources": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "source_type", + "source_id" + ], + "referred_schema": null, + "referred_table": "pc_sources", + "referred_columns": [ + "scope_id", + "source_type", + "source_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_artifact_processing_auto_wave_targets": { + "columns": { + "wave_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "completed": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "wave_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_auto_wave_target_source_positive": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_binding_states": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_auto_wave_completed_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "last_schedule_checkpoint_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "scan_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "scan_in_progress": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + }, + "scan_upper_pending_sequence": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_scan_generation": { + "expression": [ + "binary", + ">=", + [ + "column", + "scan_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_intents": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_sequence": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "clean_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "requested_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "handled_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "last_auto_scan_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_intent_sequence": { + "expression": [ + "binary", + ">", + [ + "column", + "pending_sequence" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_dirty": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "clean_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "clean_generation" + ], + [ + "column", + "dirty_generation" + ] + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_request": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "handled_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "handled_generation" + ], + [ + "column", + "requested_generation" + ] + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_scan": { + "expression": [ + "binary", + ">=", + [ + "column", + "last_auto_scan_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "pending_sequence": { + "columns": [ + [ + "pending_sequence", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_processing_intent_binding_sequence": { + "columns": [ + [ + "binding_name", + null, + "asc" + ], + [ + "pending_sequence", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_artifact_processing_leases": { + "columns": { + "supervisor_group": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "holder_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "supervisor_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "lease_expires_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "supervisor_group", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_leases_generation_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "supervisor_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_migration_receipts": { + "columns": { + "migration_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_snapshot": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requested_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "migration_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + }, + "pc_artifact_processing_pending": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "handled_flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_pending_source_positive": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 1 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_flush_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "flush_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_handled_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "handled_flush_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_handled_not_ahead": { + "expression": [ + "binary", + "<=", + [ + "column", + "handled_flush_generation" + ], + [ + "column", + "flush_generation" + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_schema": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "schema_version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": [ + "number", + 1490 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "phase": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "migration_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "config_manifest": { + "type": [ + "text", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_schema_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_sequences": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "sequence": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_sequence": { + "expression": [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + [ + "binary", + ">=", + [ + "column", + "sequence" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_publications": { + "columns": { + "target_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "target_scope_id", + null, + "asc" + ], + [ + "target_family", + null, + "asc" + ], + [ + "target_artifact_id", + null, + "asc" + ], + [ + "target_revision", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "source_scope_id", + "source_family", + "source_artifact_id", + "source_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "target_scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": { + "uq_pc_artifact_publications_request": { + "columns": [ + [ + "target_scope_id", + null, + "asc" + ], + [ + "idempotency_key", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_artifact_tags": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_type": { + "type": [ + "varchar", + [ + "12" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tag_key_hash": { + "type": [ + "binary", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tag_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tag": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "assigned_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id" + ], + "referred_schema": null, + "referred_table": "pc_artifact_heads", + "referred_columns": [ + "scope_id", + "family", + "artifact_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_tags_target": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "target_type" + ], + [ + "literal", + "artifact" + ] + ], + [ + "binary", + "=", + [ + "column", + "target_id" + ], + [ + "column", + "artifact_id" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "target_type" + ], + [ + "literal", + "memory_entry" + ] + ], + [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "memory" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_artifact_tags_family_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_artifact_tags_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + }, + "pc_atomic_memory_current": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "state_version": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "projection_format": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "tag_keys": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "embedding": { + "type": [ + "blob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "profile_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "embedding_input_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_atomic_memory_current_revision": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_atomic_memory_current_state_version": { + "expression": [ + "binary", + ">=", + [ + "column", + "state_version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_atomic_memory_current_embedding_metadata": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "embedding" + ] + ], + [ + "is_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "is_null", + [ + "column", + "embedding_input_hash" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "embedding" + ] + ], + [ + "is_not_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "embedding_input_hash" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_atomic_memory_current_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_connector_checkpoints": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "connector_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "connector_version": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "checkpoint": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + }, + "pc_decision_observations": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observation_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "consumer": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "mode": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "privacy_boundary": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "coverage": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "verdict": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "assessment_source": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "final_action": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "used_fallback": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "fallback_reason": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "provider_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "backend_model_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "model_policy_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "latency_ms": { + "type": [ + "float", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_decision_observations_requests_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_input_tokens_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "input_tokens" + ] + ], + [ + "binary", + ">=", + [ + "column", + "input_tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_output_tokens_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "output_tokens" + ] + ], + [ + "binary", + ">=", + [ + "column", + "output_tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_latency_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "latency_ms" + ] + ], + [ + "binary", + ">=", + [ + "column", + "latency_ms" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_decision_observations_scope_created": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "created_at", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_decision_observations_operation": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation_id", + null, + "asc" + ], + [ + "created_at", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_dream_runs": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "run_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "accepted_at": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_dream_generation": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_dream_idempotency": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "principal_key", + null, + "asc" + ], + [ + "idempotency_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_dream_list": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "accepted_at", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_dream_dispatch": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ], + [ + "status", + null, + "asc" + ], + [ + "request_generation", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_external_skill_registrations": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_skill_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "provider": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "agent_kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "host_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_scope": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "locator": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "locator_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "description": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "external_skill_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_external_skill_registrations_scope": { + "expression": [ + "in", + [ + "column", + "installation_scope" + ], + [ + [ + "literal", + "user" + ], + [ + "literal", + "project" + ], + [ + "literal", + "plugin" + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_external_skill_registrations_binding": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "provider", + null, + "asc" + ], + [ + "host_id", + null, + "asc" + ], + [ + "installation_scope", + null, + "asc" + ], + [ + "locator_hash", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_memory_entry_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "memory_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "head_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "memory_artifact_id", + "entry_id", + "entry_version_id" + ], + "referred_schema": null, + "referred_table": "pc_memory_entry_versions", + "referred_columns": [ + "scope_id", + "memory_artifact_id", + "entry_id", + "entry_version_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_memory_entry_heads_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "head_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_memory_entry_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "memory_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "previous_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "entry_content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "created_in_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_version_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_memory_entry_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_memory_entry_versions_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "created_in_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_memory_entry_versions_logical_version": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_memory_entry_versions_identity": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ], + [ + "entry_version_id", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_memory_source_windows": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "window_limit": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_memory_source_window_positive": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "window_limit" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_model_usage_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "purpose": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_complete": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_complete": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "purpose", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_model_usage_daily_operation": { + "expression": [ + "in", + [ + "column", + "operation" + ], + [ + [ + "literal", + "generation" + ], + [ + "literal", + "embedding" + ] + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_requests_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_input_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "input_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_output_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "output_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_profile_policies": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "generation_enabled": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "activation_mode": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "pending_candidate_id" + ], + "referred_schema": null, + "referred_table": "pc_artifact_candidate_heads", + "referred_columns": [ + "scope_id", + "candidate_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_profile_policies_activation_mode": { + "expression": [ + "in", + [ + "column", + "activation_mode" + ], + [ + [ + "literal", + "automatic" + ], + [ + "literal", + "review_required" + ] + ] + ], + "enforced": true + }, + "ck_pc_profile_policies_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_recall_effort_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "assessment": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "rounds": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "expanded_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "admission_expansions": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_floor_expansions": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "candidate_round_samples": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "candidates_assessed": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "final_candidate_pool": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "added_embeddings": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "added_generation_calls": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "truncated_items": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_items": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_below_min_bytes": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_no_fitting_truncation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "policy_id", + null, + "asc" + ], + [ + "assessment", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_recall_effort_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_rounds_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "rounds" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_expanded_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "expanded_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_admission_expansions_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "admission_expansions" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_policy_floor_expansions_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "policy_floor_expansions" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_candidate_round_samples_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "candidate_round_samples" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_candidates_assessed_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "candidates_assessed" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_final_candidate_pool_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "final_candidate_pool" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_added_embeddings_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "added_embeddings" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_added_generation_calls_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "added_generation_calls" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_truncated_items_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "truncated_items" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_items_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_items" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_below_min_bytes_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_below_min_bytes" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_no_fitting_truncation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_no_fitting_truncation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_effort_daily_drop_total": { + "expression": [ + "binary", + "=", + [ + "column", + "dropped_items" + ], + [ + "binary", + "+", + [ + "column", + "dropped_below_min_bytes" + ], + [ + "column", + "dropped_no_fitting_truncation" + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_recall_token_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "estimator_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "estimator_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ready_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "comparable_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "baseline_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "recalled_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "estimator_id", + null, + "asc" + ], + [ + "estimator_version", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_recall_token_daily_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_ready_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "ready_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_comparable_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "comparable_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_comparable_ready": { + "expression": [ + "binary", + "<=", + [ + "column", + "comparable_preparations" + ], + [ + "column", + "ready_preparations" + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_ready_total": { + "expression": [ + "binary", + "<=", + [ + "column", + "ready_preparations" + ], + [ + "column", + "preparations" + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_baseline_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "baseline_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_recalled_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "recalled_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_receipt_migration_review": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "reason": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "source_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + }, + "pc_recurrence_match": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "match_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_item_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "failure_item_index": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_item_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_set_mode": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_set_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "signature_key": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "match_key", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_recurrence_match_result": { + "expression": [ + "in", + [ + "column", + "result" + ], + [ + [ + "literal", + "matched" + ], + [ + "literal", + "unmatched" + ], + [ + "literal", + "ambiguous" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_candidate_set_mode": { + "expression": [ + "in", + [ + "column", + "candidate_set_mode" + ], + [ + [ + "literal", + "handoff_citations" + ], + [ + "literal", + "scope_heads" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_failure_item_kind": { + "expression": [ + "in", + [ + "column", + "failure_item_kind" + ], + [ + [ + "literal", + "observation" + ], + [ + "literal", + "check" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_position_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "task_outcome_position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_item_index_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "failure_item_index" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_recurrence_match_identity": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "task_outcome_source_type", + null, + "asc" + ], + [ + "task_outcome_source_id", + null, + "asc" + ], + [ + "failure_item_kind", + null, + "asc" + ], + [ + "failure_item_index", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_recurrence_observation": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observation_id": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selection_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "verdict_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "event": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "match_basis": { + "type": [ + "varchar", + [ + "8" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "signature_key": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "signature_key_hash": { + "type": [ + "binary", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "task_outcome_source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_recurrence_observation_event": { + "expression": [ + "in", + [ + "column", + "event" + ], + [ + [ + "literal", + "selected" + ], + [ + "literal", + "recurred" + ], + [ + "literal", + "avoided" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_observation_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recurrence_observation_position_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "task_outcome_position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_recurrence_observation_selection": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "selection_key", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_recurrence_observation_verdict": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "verdict_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_recurrence_observation_revision": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_recurrence_observation_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "signature_key_hash", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_recurrence_observation_outcome": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "task_outcome_source_type", + null, + "asc" + ], + [ + "task_outcome_source_id", + null, + "asc" + ], + [ + "task_outcome_position", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_scope_bindings": { + "columns": { + "integration": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_id_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "integration", + null, + "asc" + ], + [ + "kind", + null, + "asc" + ], + [ + "external_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_scope_context_references": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "referenced_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "referenced_scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "referenced_scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scope_context_references_not_self": { + "expression": [ + "binary", + "<>", + [ + "column", + "scope_id" + ], + [ + "column", + "referenced_scope_id" + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_scope_creation_requests": { + "columns": { + "idempotency_key": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "idempotency_key", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_scope_external_references": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "value": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "value_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "value_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scope_external_references_ordinal_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "ordinal" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_scope_external_references_value": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "kind", + null, + "asc" + ], + [ + "value_digest", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_scope_settings": { + "columns": { + "name": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "name", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_scopes": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "title": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "scope_id_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "title_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "parent_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "parent_scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scopes_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_server_identity": { + "columns": { + "singleton_key": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "server_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton_key", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_server_identity_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton_key" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": { + "server_id": { + "columns": [ + [ + "server_id", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_skill_packages": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "archive_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "archive_bytes": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "manifest": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "file_count": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "uncompressed_size": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "archive_size": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "tree_digest", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_skill_packages_file_count": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "file_count" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "file_count" + ], + [ + "number", + 256 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_packages_uncompressed_size": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "uncompressed_size" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "uncompressed_size" + ], + [ + "number", + 4194304 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_packages_archive_size": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "archive_size" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "archive_size" + ], + [ + "number", + 5242880 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_skill_publications": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "desired_state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "published" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "desired_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "desired_tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "observed_tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_generation": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "destination": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selected_runtime_variant": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "environment_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_error_code": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_skill_publications_desired_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "desired_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_observed_revision_positive": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "observed_revision" + ] + ], + [ + "binary", + ">", + [ + "column", + "observed_revision" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_desired_state": { + "expression": [ + "in", + [ + "column", + "desired_state" + ], + [ + [ + "literal", + "published" + ], + [ + "literal", + "unpublished" + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_state": { + "expression": [ + "in", + [ + "column", + "state" + ], + [ + [ + "literal", + "unpublished" + ], + [ + "literal", + "pending" + ], + [ + "literal", + "current" + ], + [ + "literal", + "update_available" + ], + [ + "literal", + "delivery_failed" + ], + [ + "literal", + "conflict" + ], + [ + "literal", + "drifted" + ], + [ + "literal", + "incompatible" + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_observed_generation_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "observed_generation" + ] + ], + [ + "binary", + ">=", + [ + "column", + "observed_generation" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_source_cursors": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "cursor": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_source_cursors_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_source_definition_manifests": { + "columns": { + "definition_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "definition_version": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "fingerprint": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "manifest": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "definition_name", + null, + "asc" + ], + [ + "definition_version", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_source_definition_manifest_fingerprint": { + "columns": [ + [ + "definition_name", + null, + "asc" + ], + [ + "fingerprint", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_source_journal_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_source_journal_heads_position_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_sources": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "journal_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "source_type", + null, + "asc" + ], + [ + "source_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_sources_scope_journal_position": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "journal_position", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_topic_memory_active_chunks": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "chunk_ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "start_offset": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "end_offset": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "chunk_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "policy_version": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "chunk_ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_topic_memory_revision_publications", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_active_chunks_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_ordinal_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "chunk_ordinal" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_start_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "start_offset" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_offsets": { + "expression": [ + "binary", + ">", + [ + "column", + "end_offset" + ], + [ + "column", + "start_offset" + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_topic_memory_active_chunks_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_topic_memory_active_topics": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "title": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "source_count": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_topic_memory_revision_publications", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_active_topics_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_topics_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_topics_sources_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_count" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_topic_memory_active_topics_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_topic_memory_processing_targets": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "captured_flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "observed_dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_processing_target": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "target_request_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "captured_flush_generation" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "observed_dirty_generation" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_topic_memory_retrieval_shape": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "shape": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "profile_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_memory_retrieval_shape_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_retrieval_shape_value": { + "expression": [ + "in", + [ + "column", + "shape" + ], + [ + [ + "literal", + "fts" + ], + [ + "literal", + "hybrid" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_retrieval_shape_profile": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "shape" + ], + [ + "literal", + "fts" + ] + ], + [ + "is_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "shape" + ], + [ + "literal", + "hybrid" + ] + ], + [ + "is_not_null", + [ + "column", + "profile_fingerprint" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_topic_memory_revision_publications": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "published_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_publications_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_publications_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + "views": [], + "triggers": [] + } +} diff --git a/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/seekdb.json b/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/seekdb.json new file mode 100644 index 0000000000..fa18d68aba --- /dev/null +++ b/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/seekdb.json @@ -0,0 +1,11142 @@ +{ + "format": "powercontext.server-baseline.v1", + "release": "1.3.0", + "backend": "seekdb", + "source_artifact": { + "filename": "powercontext-1.3.0-py3-none-any.whl", + "sha256": "8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5", + "uploaded_at": "2026-10-12T03:55:56.682348Z" + }, + "capture": { + "python": "3.11.15", + "sqlalchemy": "2.0.51", + "pylibseekdb": "1.4.0.post1", + "engine": "5.7.25-OceanBase seekdb-v1.4.0.0", + "access_mode": "enforced", + "processing_role": "all", + "initialization": "Installed release Server; no model or embedding provider" + }, + "schema_fingerprint": "041ee0d1cbf114ddafab1df096f9e7debbff1edf5d3a459cf7db847b640ff76c", + "inventory": { + "tables": { + "pc_access_audit": { + "columns": { + "cursor": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [ + "auto_increment" + ] + }, + "event_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "occurred_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "transport": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "actor_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "actor_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "actor_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "action": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "resource_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "deployment_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "allowed": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "role": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "expected_version": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "result_version": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "cursor", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "event_id": { + "columns": [ + [ + "event_id", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_access_idempotency": { + "columns": { + "actor_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "payload_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "secondary_binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "actor_id", + null, + "asc" + ], + [ + "idempotency_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_access_idempotency_actor_key": { + "columns": [ + [ + "actor_id", + null, + "asc" + ], + [ + "idempotency_key_hash", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_access_owners": { + "columns": { + "owner_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "object_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "established_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "owner_kind", + null, + "asc" + ], + [ + "object_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_owners_kind": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "owner_kind" + ], + [ + "literal", + "artifact" + ] + ], + [ + "is_not_null", + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "candidate_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "established_at" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "policy_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "owner_kind" + ], + [ + "literal", + "candidate" + ] + ], + [ + "is_not_null", + [ + "column", + "candidate_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "selector_type" + ] + ] + ], + [ + "is_null", + [ + "column", + "selector_entry_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "established_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "policy_revision" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_access_relationship_heads": { + "columns": { + "name": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_relationship_heads_revision_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_access_relationships": { + "columns": { + "binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "resource_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "resource_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "deployment_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "role": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "singleton_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "expires_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_relationships_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "singleton_key": { + "columns": [ + [ + "singleton_key", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_agent_skill_targets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "display_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "agent_kind": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_scope": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "delivery_mode": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "enrollment_token_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "enrollment_expires_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "credential_subject": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "credential_verifier": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "receiver_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "environment_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "machine_hostname": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "workspace_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_seen_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_agent_skill_targets_agent_kind": { + "expression": [ + "in", + [ + "column", + "agent_kind" + ], + [ + [ + "literal", + "codex" + ], + [ + "literal", + "claude_code" + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_installation_scope": { + "expression": [ + "binary", + "=", + [ + "column", + "installation_scope" + ], + [ + "literal", + "project" + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_delivery_mode": { + "expression": [ + "binary", + "=", + [ + "column", + "delivery_mode" + ], + [ + "literal", + "agent_pull" + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_state": { + "expression": [ + "in", + [ + "column", + "state" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "active" + ], + [ + "literal", + "revoked" + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_state_payload": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_not_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "installation_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_subject" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_verifier" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "active" + ] + ], + [ + "is_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "installation_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "credential_subject" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "credential_verifier" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "revoked" + ] + ], + [ + "is_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_verifier" + ] + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_agent_skill_targets_installation": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "agent_kind", + null, + "asc" + ], + [ + "installation_scope", + null, + "asc" + ], + [ + "installation_id", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_enrollment_token": { + "columns": [ + [ + "enrollment_token_digest", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_credential_subject": { + "columns": [ + [ + "credential_subject", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_credential_verifier": { + "columns": [ + [ + "credential_verifier", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_artifact_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_artifact_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_versions_target_complete": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "target_family" + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "target_family" + ] + ], + [ + "is_not_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "target_revision" + ], + [ + "number", + 0 + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "lifecycle_state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "active" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "replacement_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "merged_into_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "governance_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_heads_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_lifecycle_state": { + "expression": [ + "in", + [ + "column", + "lifecycle_state" + ], + [ + [ + "literal", + "active" + ], + [ + "literal", + "deprecated" + ], + [ + "literal", + "retired" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_governance_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "governance_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_replacement_deprecated": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "replacement_artifact_id" + ] + ], + [ + "binary", + "=", + [ + "column", + "lifecycle_state" + ], + [ + "literal", + "deprecated" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_merge_target": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "merged_into_id" + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "lifecycle_state" + ], + [ + "literal", + "deprecated" + ] + ], + [ + "binary", + "<>", + [ + "column", + "merged_into_id" + ], + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "replacement_artifact_id" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_artifact_heads_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_artifact_lineage_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "upstream_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "upstream_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "upstream_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "is_merge_input": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "upstream_family", + "upstream_artifact_id", + "upstream_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_artifact_lineage_sources": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "source_type", + "source_id" + ], + "referred_schema": null, + "referred_table": "pc_sources", + "referred_columns": [ + "scope_id", + "source_type", + "source_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_artifact_processing_auto_wave_targets": { + "columns": { + "wave_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "completed": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "wave_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_auto_wave_target_source_positive": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_binding_states": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_auto_wave_completed_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "last_schedule_checkpoint_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "scan_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "scan_in_progress": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + }, + "scan_upper_pending_sequence": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_scan_generation": { + "expression": [ + "binary", + ">=", + [ + "column", + "scan_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_intents": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_sequence": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "clean_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "requested_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "handled_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "last_auto_scan_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_intent_sequence": { + "expression": [ + "binary", + ">", + [ + "column", + "pending_sequence" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_dirty": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "clean_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "clean_generation" + ], + [ + "column", + "dirty_generation" + ] + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_request": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "handled_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "handled_generation" + ], + [ + "column", + "requested_generation" + ] + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_scan": { + "expression": [ + "binary", + ">=", + [ + "column", + "last_auto_scan_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "pending_sequence": { + "columns": [ + [ + "pending_sequence", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_processing_intent_binding_sequence": { + "columns": [ + [ + "binding_name", + null, + "asc" + ], + [ + "pending_sequence", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_artifact_processing_leases": { + "columns": { + "supervisor_group": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "holder_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "supervisor_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "lease_expires_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "supervisor_group", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_leases_generation_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "supervisor_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_migration_receipts": { + "columns": { + "migration_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_snapshot": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requested_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "migration_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + }, + "pc_artifact_processing_pending": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "handled_flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_pending_source_positive": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 1 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_flush_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "flush_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_handled_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "handled_flush_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_handled_not_ahead": { + "expression": [ + "binary", + "<=", + [ + "column", + "handled_flush_generation" + ], + [ + "column", + "flush_generation" + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_schema": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "schema_version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": [ + "number", + 1490 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "phase": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "migration_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "config_manifest": { + "type": [ + "text", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_schema_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_processing_sequences": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "sequence": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_sequence": { + "expression": [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + [ + "binary", + ">=", + [ + "column", + "sequence" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_publications": { + "columns": { + "target_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "target_scope_id", + null, + "asc" + ], + [ + "target_family", + null, + "asc" + ], + [ + "target_artifact_id", + null, + "asc" + ], + [ + "target_revision", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "source_scope_id", + "source_family", + "source_artifact_id", + "source_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "target_scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": { + "uq_pc_artifact_publications_request": { + "columns": [ + [ + "target_scope_id", + null, + "asc" + ], + [ + "idempotency_key", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_artifact_tags": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_type": { + "type": [ + "varchar", + [ + "12" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tag_key_hash": { + "type": [ + "binary", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tag_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tag": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "assigned_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id" + ], + "referred_schema": null, + "referred_table": "pc_artifact_heads", + "referred_columns": [ + "scope_id", + "family", + "artifact_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_tags_target": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "target_type" + ], + [ + "literal", + "artifact" + ] + ], + [ + "binary", + "=", + [ + "column", + "target_id" + ], + [ + "column", + "artifact_id" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "target_type" + ], + [ + "literal", + "memory_entry" + ] + ], + [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "memory" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_artifact_tags_family_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_artifact_tags_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + }, + "pc_atomic_memory_current": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "state_version": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "projection_format": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "tag_keys": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "embedding": { + "type": [ + "blob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "profile_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "embedding_input_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_atomic_memory_current_revision": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_atomic_memory_current_state_version": { + "expression": [ + "binary", + ">=", + [ + "column", + "state_version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_atomic_memory_current_embedding_metadata": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "embedding" + ] + ], + [ + "is_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "is_null", + [ + "column", + "embedding_input_hash" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "embedding" + ] + ], + [ + "is_not_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "embedding_input_hash" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_atomic_memory_current_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_connector_checkpoints": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "connector_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "connector_version": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "checkpoint": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + }, + "pc_decision_observations": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observation_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "consumer": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "mode": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "privacy_boundary": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "coverage": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "verdict": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "assessment_source": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "final_action": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "used_fallback": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "fallback_reason": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "provider_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "backend_model_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "model_policy_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "latency_ms": { + "type": [ + "float", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_decision_observations_requests_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_input_tokens_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "input_tokens" + ] + ], + [ + "binary", + ">=", + [ + "column", + "input_tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_output_tokens_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "output_tokens" + ] + ], + [ + "binary", + ">=", + [ + "column", + "output_tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_latency_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "latency_ms" + ] + ], + [ + "binary", + ">=", + [ + "column", + "latency_ms" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_decision_observations_operation": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation_id", + null, + "asc" + ], + [ + "created_at", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_decision_observations_scope_created": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "created_at", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_dream_runs": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "run_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "accepted_at": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_dream_generation": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_dream_idempotency": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "principal_key", + null, + "asc" + ], + [ + "idempotency_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_dream_list": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "accepted_at", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_dream_dispatch": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ], + [ + "status", + null, + "asc" + ], + [ + "request_generation", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_external_skill_registrations": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_skill_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "provider": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "agent_kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "host_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_scope": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "locator": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "locator_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "description": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "external_skill_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_external_skill_registrations_scope": { + "expression": [ + "in", + [ + "column", + "installation_scope" + ], + [ + [ + "literal", + "user" + ], + [ + "literal", + "project" + ], + [ + "literal", + "plugin" + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_external_skill_registrations_binding": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "provider", + null, + "asc" + ], + [ + "host_id", + null, + "asc" + ], + [ + "installation_scope", + null, + "asc" + ], + [ + "locator_hash", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_memory_entry_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "memory_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "head_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "memory_artifact_id", + "entry_id", + "entry_version_id" + ], + "referred_schema": null, + "referred_table": "pc_memory_entry_versions", + "referred_columns": [ + "scope_id", + "memory_artifact_id", + "entry_id", + "entry_version_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_memory_entry_heads_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "head_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_memory_entry_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "memory_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "previous_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "entry_content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "created_in_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_version_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_memory_entry_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_memory_entry_versions_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "created_in_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_memory_entry_versions_logical_version": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_memory_entry_versions_identity": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ], + [ + "entry_version_id", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_memory_source_windows": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "window_limit": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_memory_source_window_positive": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "window_limit" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_model_usage_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "purpose": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_complete": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_complete": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "purpose", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_model_usage_daily_operation": { + "expression": [ + "in", + [ + "column", + "operation" + ], + [ + [ + "literal", + "generation" + ], + [ + "literal", + "embedding" + ] + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_requests_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_input_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "input_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_output_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "output_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_profile_policies": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "generation_enabled": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "activation_mode": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "pending_candidate_id" + ], + "referred_schema": null, + "referred_table": "pc_artifact_candidate_heads", + "referred_columns": [ + "scope_id", + "candidate_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_profile_policies_activation_mode": { + "expression": [ + "in", + [ + "column", + "activation_mode" + ], + [ + [ + "literal", + "automatic" + ], + [ + "literal", + "review_required" + ] + ] + ], + "enforced": true + }, + "ck_pc_profile_policies_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_recall_effort_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "assessment": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "rounds": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "expanded_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "admission_expansions": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_floor_expansions": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "candidate_round_samples": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "candidates_assessed": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "final_candidate_pool": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "added_embeddings": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "added_generation_calls": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "truncated_items": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_items": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_below_min_bytes": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_no_fitting_truncation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "policy_id", + null, + "asc" + ], + [ + "assessment", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_recall_effort_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_rounds_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "rounds" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_expanded_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "expanded_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_admission_expansions_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "admission_expansions" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_policy_floor_expansions_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "policy_floor_expansions" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_candidate_round_samples_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "candidate_round_samples" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_candidates_assessed_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "candidates_assessed" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_final_candidate_pool_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "final_candidate_pool" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_added_embeddings_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "added_embeddings" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_added_generation_calls_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "added_generation_calls" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_truncated_items_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "truncated_items" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_items_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_items" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_below_min_bytes_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_below_min_bytes" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_no_fitting_truncation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_no_fitting_truncation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_effort_daily_drop_total": { + "expression": [ + "binary", + "=", + [ + "column", + "dropped_items" + ], + [ + "binary", + "+", + [ + "column", + "dropped_below_min_bytes" + ], + [ + "column", + "dropped_no_fitting_truncation" + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_recall_token_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "estimator_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "estimator_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ready_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "comparable_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "baseline_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "recalled_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "estimator_id", + null, + "asc" + ], + [ + "estimator_version", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_recall_token_daily_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_ready_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "ready_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_comparable_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "comparable_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_comparable_ready": { + "expression": [ + "binary", + "<=", + [ + "column", + "comparable_preparations" + ], + [ + "column", + "ready_preparations" + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_ready_total": { + "expression": [ + "binary", + "<=", + [ + "column", + "ready_preparations" + ], + [ + "column", + "preparations" + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_baseline_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "baseline_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_recalled_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "recalled_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_receipt_migration_review": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "reason": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "source_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + }, + "pc_recurrence_match": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "match_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_item_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "failure_item_index": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_item_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_set_mode": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_set_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "signature_key": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "match_key", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_recurrence_match_result": { + "expression": [ + "in", + [ + "column", + "result" + ], + [ + [ + "literal", + "matched" + ], + [ + "literal", + "unmatched" + ], + [ + "literal", + "ambiguous" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_candidate_set_mode": { + "expression": [ + "in", + [ + "column", + "candidate_set_mode" + ], + [ + [ + "literal", + "handoff_citations" + ], + [ + "literal", + "scope_heads" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_failure_item_kind": { + "expression": [ + "in", + [ + "column", + "failure_item_kind" + ], + [ + [ + "literal", + "observation" + ], + [ + "literal", + "check" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_position_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "task_outcome_position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_item_index_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "failure_item_index" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_recurrence_match_identity": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "task_outcome_source_type", + null, + "asc" + ], + [ + "task_outcome_source_id", + null, + "asc" + ], + [ + "failure_item_kind", + null, + "asc" + ], + [ + "failure_item_index", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_recurrence_observation": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observation_id": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selection_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "verdict_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "event": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "match_basis": { + "type": [ + "varchar", + [ + "8" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "signature_key": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "signature_key_hash": { + "type": [ + "binary", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "task_outcome_source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_recurrence_observation_event": { + "expression": [ + "in", + [ + "column", + "event" + ], + [ + [ + "literal", + "selected" + ], + [ + "literal", + "recurred" + ], + [ + "literal", + "avoided" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_observation_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recurrence_observation_position_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "task_outcome_position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_recurrence_observation_selection": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "selection_key", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_recurrence_observation_verdict": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "verdict_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_recurrence_observation_revision": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_recurrence_observation_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "signature_key_hash", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_recurrence_observation_outcome": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "task_outcome_source_type", + null, + "asc" + ], + [ + "task_outcome_source_id", + null, + "asc" + ], + [ + "task_outcome_position", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_scope_bindings": { + "columns": { + "integration": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_id_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "integration", + null, + "asc" + ], + [ + "kind", + null, + "asc" + ], + [ + "external_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_scope_context_references": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "referenced_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "referenced_scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "referenced_scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scope_context_references_not_self": { + "expression": [ + "binary", + "<>", + [ + "column", + "scope_id" + ], + [ + "column", + "referenced_scope_id" + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_scope_creation_requests": { + "columns": { + "idempotency_key": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "idempotency_key", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_scope_external_references": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "value": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "value_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "value_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scope_external_references_ordinal_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "ordinal" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_scope_external_references_value": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "kind", + null, + "asc" + ], + [ + "value_digest", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_scope_settings": { + "columns": { + "name": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "name", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + }, + "pc_scopes": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "title": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "scope_id_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "title_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "parent_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "parent_scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scopes_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_server_identity": { + "columns": { + "singleton_key": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "server_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton_key", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_server_identity_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton_key" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": { + "server_id": { + "columns": [ + [ + "server_id", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_skill_packages": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "archive_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "archive_bytes": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "manifest": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "file_count": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "uncompressed_size": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "archive_size": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "tree_digest", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_skill_packages_file_count": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "file_count" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "file_count" + ], + [ + "number", + 256 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_packages_uncompressed_size": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "uncompressed_size" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "uncompressed_size" + ], + [ + "number", + 4194304 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_packages_archive_size": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "archive_size" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "archive_size" + ], + [ + "number", + 5242880 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_skill_publications": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "desired_state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "published" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "desired_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "desired_tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "observed_tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_generation": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "destination": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selected_runtime_variant": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "environment_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_error_code": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_skill_publications_desired_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "desired_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_observed_revision_positive": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "observed_revision" + ] + ], + [ + "binary", + ">", + [ + "column", + "observed_revision" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_desired_state": { + "expression": [ + "in", + [ + "column", + "desired_state" + ], + [ + [ + "literal", + "published" + ], + [ + "literal", + "unpublished" + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_state": { + "expression": [ + "in", + [ + "column", + "state" + ], + [ + [ + "literal", + "unpublished" + ], + [ + "literal", + "pending" + ], + [ + "literal", + "current" + ], + [ + "literal", + "update_available" + ], + [ + "literal", + "delivery_failed" + ], + [ + "literal", + "conflict" + ], + [ + "literal", + "drifted" + ], + [ + "literal", + "incompatible" + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_observed_generation_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "observed_generation" + ] + ], + [ + "binary", + ">=", + [ + "column", + "observed_generation" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_source_cursors": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "cursor": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_source_cursors_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_source_definition_manifests": { + "columns": { + "definition_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "definition_version": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "fingerprint": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "manifest": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "definition_name", + null, + "asc" + ], + [ + "definition_version", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_source_definition_manifest_fingerprint": { + "columns": [ + [ + "definition_name", + null, + "asc" + ], + [ + "fingerprint", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_source_journal_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_source_journal_heads_position_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_sources": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "journal_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "source_type", + null, + "asc" + ], + [ + "source_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_sources_scope_journal_position": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "journal_position", + null, + "asc" + ] + ], + "unique": true + } + } + }, + "pc_topic_memory_active_chunks": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "chunk_ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "start_offset": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "end_offset": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "chunk_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "policy_version": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "chunk_ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_topic_memory_revision_publications", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_active_chunks_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_ordinal_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "chunk_ordinal" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_start_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "start_offset" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_offsets": { + "expression": [ + "binary", + ">", + [ + "column", + "end_offset" + ], + [ + "column", + "start_offset" + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_topic_memory_active_chunks_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_topic_memory_active_topics": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "title": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "source_count": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_topic_memory_revision_publications", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_active_topics_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_topics_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_topics_sources_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_count" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_topic_memory_active_topics_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_topic_memory_processing_targets": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "captured_flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "observed_dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_processing_target": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "target_request_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "captured_flush_generation" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "observed_dirty_generation" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_topic_memory_retrieval_shape": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "shape": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "profile_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_memory_retrieval_shape_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_retrieval_shape_value": { + "expression": [ + "in", + [ + "column", + "shape" + ], + [ + [ + "literal", + "fts" + ], + [ + "literal", + "hybrid" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_retrieval_shape_profile": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "shape" + ], + [ + "literal", + "fts" + ] + ], + [ + "is_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "shape" + ], + [ + "literal", + "hybrid" + ] + ], + [ + "is_not_null", + [ + "column", + "profile_fingerprint" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_topic_memory_revision_publications": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "published_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_publications_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_publications_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + "views": [], + "triggers": [] + } +} diff --git a/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/sqlite.json b/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/sqlite.json new file mode 100644 index 0000000000..ab00fc2b4c --- /dev/null +++ b/src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/sqlite.json @@ -0,0 +1,585 @@ +{ + "format": "powercontext.server-baseline.v1", + "release": "1.3.0", + "backend": "sqlite", + "source_artifact": { + "filename": "powercontext-1.3.0-py3-none-any.whl", + "sha256": "8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5", + "uploaded_at": "2026-10-12T03:55:56.682348Z" + }, + "capture": { + "python": "3.14.3", + "sqlite": "3.50.4", + "sqlalchemy": "2.1.6", + "access_mode": "enforced", + "processing_role": "all", + "initialization": "Installed release Server; no model or embedding provider" + }, + "schema_fingerprint": "11e8770068a5fe32dfb25681564dbbe586ba0fd0238ed01adf1b09dde8a49f22", + "inventory": [ + [ + "index", + "ix_pc_artifact_tags_family_key", + "pc_artifact_tags", + "CREATE INDEX ix_pc_artifact_tags_family_key ON pc_artifact_tags (scope_id, family, tag_key_hash, target_type, artifact_id, target_id)" + ], + [ + "index", + "ix_pc_artifact_tags_key", + "pc_artifact_tags", + "CREATE INDEX ix_pc_artifact_tags_key ON pc_artifact_tags (scope_id, tag_key_hash, family, target_type, artifact_id, target_id)" + ], + [ + "index", + "ix_pc_decision_observations_operation", + "pc_decision_observations", + "CREATE INDEX ix_pc_decision_observations_operation ON pc_decision_observations (scope_id, operation_id, created_at)" + ], + [ + "index", + "ix_pc_decision_observations_scope_created", + "pc_decision_observations", + "CREATE INDEX ix_pc_decision_observations_scope_created ON pc_decision_observations (scope_id, created_at, observation_id)" + ], + [ + "index", + "ix_pc_dream_dispatch", + "pc_dream_runs", + "CREATE INDEX ix_pc_dream_dispatch ON pc_dream_runs (scope_id, operation, status, request_generation)" + ], + [ + "index", + "ix_pc_dream_list", + "pc_dream_runs", + "CREATE INDEX ix_pc_dream_list ON pc_dream_runs (scope_id, accepted_at, run_id)" + ], + [ + "index", + "ix_pc_processing_intent_binding_sequence", + "pc_artifact_processing_intents", + "CREATE INDEX ix_pc_processing_intent_binding_sequence ON pc_artifact_processing_intents (binding_name, pending_sequence)" + ], + [ + "index", + "ix_pc_recurrence_observation_key", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_key ON pc_recurrence_observation (scope_id, signature_key_hash)" + ], + [ + "index", + "ix_pc_recurrence_observation_outcome", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_outcome ON pc_recurrence_observation (scope_id, task_outcome_source_type, task_outcome_source_id, task_outcome_position)" + ], + [ + "index", + "ix_pc_recurrence_observation_revision", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_revision ON pc_recurrence_observation (scope_id, family, artifact_id, revision)" + ], + [ + "table", + "pc_access_audit", + "pc_access_audit", + "CREATE TABLE pc_access_audit (\n\tcursor INTEGER NOT NULL, \n\tevent_id VARCHAR(64) NOT NULL, \n\toccurred_at VARCHAR(32) NOT NULL, \n\trequest_id VARCHAR(128), \n\ttransport VARCHAR(16) NOT NULL, \n\toperation VARCHAR(128) NOT NULL, \n\tprincipal_type VARCHAR(16) NOT NULL, \n\tprincipal_id VARCHAR(255) NOT NULL, \n\tprincipal_description TEXT, \n\tactor_type VARCHAR(16), \n\tactor_id VARCHAR(255), \n\tactor_description TEXT, \n\taction VARCHAR(64) NOT NULL, \n\tresource_type VARCHAR(16) NOT NULL, \n\tdeployment_id VARCHAR(128), \n\tscope_id VARCHAR(256), \n\tfamily VARCHAR(128), \n\tartifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\tallowed BOOLEAN NOT NULL, \n\treason_code VARCHAR(64) NOT NULL, \n\tpolicy_revision VARCHAR(64), \n\tmatched_subject_type VARCHAR(16), \n\tmatched_subject_id VARCHAR(255), \n\tmatched_subject_description TEXT, \n\tbinding_id VARCHAR(64), \n\ttarget_type VARCHAR(16), \n\ttarget_id VARCHAR(255), \n\ttarget_description TEXT, \n\trole VARCHAR(32), \n\texpected_version INTEGER, \n\tresult_version INTEGER, \n\tPRIMARY KEY (cursor), \n\tUNIQUE (event_id)\n)" + ], + [ + "table", + "pc_access_idempotency", + "pc_access_idempotency", + "CREATE TABLE pc_access_idempotency (\n\tactor_id VARCHAR(255) NOT NULL, \n\tidempotency_key_hash VARCHAR(64) NOT NULL, \n\toperation VARCHAR(64) NOT NULL, \n\tpayload_hash VARCHAR(64) NOT NULL, \n\tresult_binding_id VARCHAR(64), \n\tsecondary_binding_id VARCHAR(64), \n\tPRIMARY KEY (actor_id, idempotency_key_hash), \n\tCONSTRAINT uq_pc_access_idempotency_actor_key UNIQUE (actor_id, idempotency_key_hash)\n)" + ], + [ + "table", + "pc_access_owners", + "pc_access_owners", + "CREATE TABLE pc_access_owners (\n\towner_kind VARCHAR(16) NOT NULL, \n\tobject_key_hash VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128), \n\tcandidate_id VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\towner_type VARCHAR(16) NOT NULL, \n\towner_id VARCHAR(255) NOT NULL, \n\towner_description TEXT, \n\testablished_at VARCHAR(32), \n\tpolicy_revision VARCHAR(64), \n\tidempotency_key VARCHAR(255) NOT NULL, \n\tPRIMARY KEY (owner_kind, object_key_hash), \n\tCONSTRAINT ck_pc_access_owners_kind CHECK ((owner_kind = 'artifact' AND artifact_id IS NOT NULL AND candidate_id IS NULL AND target_artifact_id IS NULL AND established_at IS NOT NULL AND policy_revision IS NOT NULL) OR (owner_kind = 'candidate' AND candidate_id IS NOT NULL AND artifact_id IS NULL AND selector_type IS NULL AND selector_entry_id IS NULL AND established_at IS NULL AND policy_revision IS NULL))\n)" + ], + [ + "table", + "pc_access_relationship_heads", + "pc_access_relationship_heads", + "CREATE TABLE pc_access_relationship_heads (\n\tname VARCHAR(32) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tPRIMARY KEY (name), \n\tCONSTRAINT ck_pc_access_relationship_heads_revision_nonnegative CHECK (revision >= 0)\n)" + ], + [ + "table", + "pc_access_relationships", + "pc_access_relationships", + "CREATE TABLE pc_access_relationships (\n\tbinding_id VARCHAR(64) NOT NULL, \n\tsubject_type VARCHAR(16) NOT NULL, \n\tsubject_id VARCHAR(255) NOT NULL, \n\tsubject_description TEXT, \n\tresource_key_hash VARCHAR(64) NOT NULL, \n\tresource_type VARCHAR(16) NOT NULL, \n\tdeployment_id VARCHAR(128), \n\tscope_id VARCHAR(256), \n\tfamily VARCHAR(128), \n\tartifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\trole VARCHAR(32) NOT NULL, \n\tsingleton_key VARCHAR(64), \n\tgranted_by_type VARCHAR(16) NOT NULL, \n\tgranted_by_id VARCHAR(255) NOT NULL, \n\tgranted_by_description TEXT, \n\treason TEXT, \n\tcreated_at VARCHAR(32) NOT NULL, \n\texpires_at VARCHAR(32), \n\tstate VARCHAR(16) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tpolicy_revision VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(255) NOT NULL, \n\trevoked_at VARCHAR(32), \n\trevoked_by_type VARCHAR(16), \n\trevoked_by_id VARCHAR(255), \n\trevoked_by_description TEXT, \n\tPRIMARY KEY (binding_id), \n\tCONSTRAINT ck_pc_access_relationships_version_positive CHECK (version > 0), \n\tUNIQUE (singleton_key)\n)" + ], + [ + "table", + "pc_agent_skill_targets", + "pc_agent_skill_targets", + "CREATE TABLE pc_agent_skill_targets (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_id VARCHAR(64) NOT NULL, \n\tdisplay_name VARCHAR(128) NOT NULL, \n\tagent_kind VARCHAR(32) NOT NULL, \n\tinstallation_scope VARCHAR(16) NOT NULL, \n\tdelivery_mode VARCHAR(16) NOT NULL, \n\tinstallation_id VARCHAR(128), \n\tstate VARCHAR(16) NOT NULL, \n\tenrollment_token_digest VARCHAR(64), \n\tenrollment_expires_at DATETIME, \n\tcredential_subject VARCHAR(128), \n\tcredential_verifier VARCHAR(64), \n\treceiver_version VARCHAR(64), \n\tenvironment_fingerprint VARCHAR(64), \n\tmachine_hostname VARCHAR(255), \n\tworkspace_name VARCHAR(128), \n\tlast_seen_at DATETIME, \n\tgeneration BIGINT NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, target_id), \n\tCONSTRAINT uq_pc_agent_skill_targets_installation UNIQUE (scope_id, agent_kind, installation_scope, installation_id), \n\tCONSTRAINT uq_pc_agent_skill_targets_enrollment_token UNIQUE (enrollment_token_digest), \n\tCONSTRAINT uq_pc_agent_skill_targets_credential_subject UNIQUE (credential_subject), \n\tCONSTRAINT uq_pc_agent_skill_targets_credential_verifier UNIQUE (credential_verifier), \n\tCONSTRAINT ck_pc_agent_skill_targets_agent_kind CHECK (agent_kind IN ('codex', 'claude_code')), \n\tCONSTRAINT ck_pc_agent_skill_targets_installation_scope CHECK (installation_scope IN ('project')), \n\tCONSTRAINT ck_pc_agent_skill_targets_delivery_mode CHECK (delivery_mode = 'agent_pull'), \n\tCONSTRAINT ck_pc_agent_skill_targets_state CHECK (state IN ('pending', 'active', 'revoked')), \n\tCONSTRAINT ck_pc_agent_skill_targets_state_payload CHECK ((state = 'pending' AND enrollment_token_digest IS NOT NULL AND enrollment_expires_at IS NOT NULL AND installation_id IS NULL AND credential_subject IS NULL AND credential_verifier IS NULL) OR (state = 'active' AND enrollment_token_digest IS NULL AND enrollment_expires_at IS NULL AND installation_id IS NOT NULL AND credential_subject IS NOT NULL AND credential_verifier IS NOT NULL) OR (state = 'revoked' AND enrollment_token_digest IS NULL AND enrollment_expires_at IS NULL AND credential_verifier IS NULL)), \n\tCONSTRAINT ck_pc_agent_skill_targets_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_artifact_candidate_heads", + "pc_artifact_candidate_heads", + "CREATE TABLE pc_artifact_candidate_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\tresult_family VARCHAR(128), \n\tresult_artifact_id VARCHAR(128), \n\tresult_revision INTEGER, \n\tdecision_reason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tFOREIGN KEY(scope_id, candidate_id, version) REFERENCES pc_artifact_candidate_versions (scope_id, candidate_id, version) ON DELETE RESTRICT, \n\tFOREIGN KEY(scope_id, result_family, result_artifact_id, result_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_artifact_candidate_heads_terminal_result CHECK ((status = 'approved' AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'rejected' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)" + ], + [ + "table", + "pc_artifact_candidate_versions", + "pc_artifact_candidate_versions", + "CREATE TABLE pc_artifact_candidate_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tproposal BLOB NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\treason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_artifact_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)" + ], + [ + "table", + "pc_artifact_fts", + "pc_artifact_fts", + "CREATE VIRTUAL TABLE pc_artifact_fts USING fts5(\n scope_id UNINDEXED,\n family UNINDEXED,\n artifact_id UNINDEXED,\n revision UNINDEXED,\n searchable_text,\n tokenize='unicode61'\n)" + ], + [ + "table", + "pc_artifact_fts_config", + "pc_artifact_fts_config", + "CREATE TABLE 'pc_artifact_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_artifact_fts_content", + "pc_artifact_fts_content", + "CREATE TABLE 'pc_artifact_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4)" + ], + [ + "table", + "pc_artifact_fts_data", + "pc_artifact_fts_data", + "CREATE TABLE 'pc_artifact_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_artifact_fts_docsize", + "pc_artifact_fts_docsize", + "CREATE TABLE 'pc_artifact_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_artifact_fts_idx", + "pc_artifact_fts_idx", + "CREATE TABLE 'pc_artifact_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_artifact_heads", + "pc_artifact_heads", + "CREATE TABLE pc_artifact_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsearchable_text TEXT, \n\tlifecycle_state VARCHAR(16) DEFAULT 'active' NOT NULL, \n\treplacement_artifact_id VARCHAR(128), \n\tmerged_into_id VARCHAR(128), \n\tgovernance_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_heads_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_artifact_heads_lifecycle_state CHECK (lifecycle_state IN ('active', 'deprecated', 'retired')), \n\tCONSTRAINT ck_pc_artifact_heads_governance_generation_nonnegative CHECK (governance_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_heads_replacement_deprecated CHECK (replacement_artifact_id IS NULL OR lifecycle_state = 'deprecated'), \n\tCONSTRAINT ck_pc_artifact_heads_merge_target CHECK (merged_into_id IS NULL OR (lifecycle_state = 'deprecated' AND merged_into_id <> artifact_id AND replacement_artifact_id IS NULL))\n)" + ], + [ + "table", + "pc_artifact_lineage_artifacts", + "pc_artifact_lineage_artifacts", + "CREATE TABLE pc_artifact_lineage_artifacts (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tupstream_family VARCHAR(128) NOT NULL, \n\tupstream_artifact_id VARCHAR(128) NOT NULL, \n\tupstream_revision INTEGER NOT NULL, \n\tis_merge_input BOOLEAN DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision, ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, upstream_family, upstream_artifact_id, upstream_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_artifact_lineage_sources", + "pc_artifact_lineage_sources", + "CREATE TABLE pc_artifact_lineage_sources (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tsource_type VARCHAR(128) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision, ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, source_type, source_id) REFERENCES pc_sources (scope_id, source_type, source_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_artifact_processing_auto_wave_targets", + "pc_artifact_processing_auto_wave_targets", + "CREATE TABLE pc_artifact_processing_auto_wave_targets (\n\twave_id VARCHAR(36) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tcompleted BOOLEAN DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (wave_id, binding_name, scope_id), \n\tCONSTRAINT ck_pc_artifact_processing_auto_wave_target_source_positive CHECK (source_through >= 1)\n)" + ], + [ + "table", + "pc_artifact_processing_binding_states", + "pc_artifact_processing_binding_states", + "CREATE TABLE pc_artifact_processing_binding_states (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tlast_auto_wave_completed_at DATETIME, \n\tlast_schedule_checkpoint_at DATETIME, \n\tscan_generation BIGINT DEFAULT '0' NOT NULL, \n\tscan_in_progress BOOLEAN DEFAULT '0' NOT NULL, \n\tscan_upper_pending_sequence BIGINT, \n\tPRIMARY KEY (binding_name), \n\tCONSTRAINT ck_pc_processing_scan_generation CHECK (scan_generation >= 0)\n)" + ], + [ + "table", + "pc_artifact_processing_intents", + "pc_artifact_processing_intents", + "CREATE TABLE pc_artifact_processing_intents (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tpending_sequence BIGINT NOT NULL, \n\tdirty_generation BIGINT DEFAULT '0' NOT NULL, \n\tclean_generation BIGINT DEFAULT '0' NOT NULL, \n\trequested_generation BIGINT DEFAULT '0' NOT NULL, \n\thandled_generation BIGINT DEFAULT '0' NOT NULL, \n\tlast_auto_scan_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_processing_intent_sequence CHECK (pending_sequence > 0), \n\tCONSTRAINT ck_pc_processing_intent_dirty CHECK (clean_generation >= 0 AND clean_generation <= dirty_generation), \n\tCONSTRAINT ck_pc_processing_intent_request CHECK (handled_generation >= 0 AND handled_generation <= requested_generation), \n\tCONSTRAINT ck_pc_processing_intent_scan CHECK (last_auto_scan_generation >= 0), \n\tUNIQUE (pending_sequence)\n)" + ], + [ + "table", + "pc_artifact_processing_leases", + "pc_artifact_processing_leases", + "CREATE TABLE pc_artifact_processing_leases (\n\tsupervisor_group VARCHAR(64) NOT NULL, \n\tholder_id VARCHAR(36) NOT NULL, \n\tsupervisor_generation BIGINT NOT NULL, \n\tlease_expires_at DATETIME, \n\tPRIMARY KEY (supervisor_group), \n\tCONSTRAINT ck_pc_artifact_processing_leases_generation_positive CHECK (supervisor_generation > 0)\n)" + ], + [ + "table", + "pc_artifact_processing_migration_receipts", + "pc_artifact_processing_migration_receipts", + "CREATE TABLE pc_artifact_processing_migration_receipts (\n\tmigration_id VARCHAR(64) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_snapshot BLOB NOT NULL, \n\trequested_generation BIGINT NOT NULL, \n\tdirty_generation BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tPRIMARY KEY (migration_id, binding_name, scope_id)\n)" + ], + [ + "table", + "pc_artifact_processing_pending", + "pc_artifact_processing_pending", + "CREATE TABLE pc_artifact_processing_pending (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tflush_generation BIGINT DEFAULT '0' NOT NULL, \n\thandled_flush_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_artifact_processing_pending_source_positive CHECK (source_through >= 1), \n\tCONSTRAINT ck_pc_artifact_processing_pending_flush_nonnegative CHECK (flush_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_processing_pending_handled_nonnegative CHECK (handled_flush_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_processing_pending_handled_not_ahead CHECK (handled_flush_generation <= flush_generation)\n)" + ], + [ + "table", + "pc_artifact_processing_schema", + "pc_artifact_processing_schema", + "CREATE TABLE pc_artifact_processing_schema (\n\tsingleton INTEGER NOT NULL, \n\tschema_version INTEGER NOT NULL, \n\tsource_version INTEGER DEFAULT '1490' NOT NULL, \n\tphase VARCHAR(32) NOT NULL, \n\tmigration_id VARCHAR(64) NOT NULL, \n\tconfig_manifest TEXT NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_processing_schema_singleton CHECK (singleton = 1)\n)" + ], + [ + "table", + "pc_artifact_processing_sequences", + "pc_artifact_processing_sequences", + "CREATE TABLE pc_artifact_processing_sequences (\n\tsingleton INTEGER NOT NULL, \n\tsequence BIGINT NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_processing_sequence CHECK (singleton = 1 AND sequence >= 0)\n)" + ], + [ + "table", + "pc_artifact_publications", + "pc_artifact_publications", + "CREATE TABLE pc_artifact_publications (\n\ttarget_scope_id VARCHAR(256) NOT NULL, \n\ttarget_family VARCHAR(128) NOT NULL, \n\ttarget_artifact_id VARCHAR(128) NOT NULL, \n\ttarget_revision INTEGER NOT NULL, \n\tsource_scope_id VARCHAR(256) NOT NULL, \n\tsource_family VARCHAR(128) NOT NULL, \n\tsource_artifact_id VARCHAR(128) NOT NULL, \n\tsource_revision INTEGER NOT NULL, \n\tcontent_digest VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (target_scope_id, target_family, target_artifact_id, target_revision), \n\tFOREIGN KEY(target_scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tFOREIGN KEY(source_scope_id, source_family, source_artifact_id, source_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT uq_pc_artifact_publications_request UNIQUE (target_scope_id, idempotency_key)\n)" + ], + [ + "table", + "pc_artifact_tags", + "pc_artifact_tags", + "CREATE TABLE pc_artifact_tags (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\ttarget_type VARCHAR(12) NOT NULL, \n\ttarget_id VARCHAR(128) NOT NULL, \n\ttag_key_hash BLOB NOT NULL, \n\ttag_key VARCHAR(128) NOT NULL, \n\ttag VARCHAR(64) NOT NULL, \n\tassigned_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, target_type, target_id, tag_key_hash), \n\tFOREIGN KEY(scope_id, family, artifact_id) REFERENCES pc_artifact_heads (scope_id, family, artifact_id) ON DELETE CASCADE, \n\tCONSTRAINT ck_pc_artifact_tags_target CHECK ((target_type = 'artifact' AND target_id = artifact_id) OR (target_type = 'memory_entry' AND family = 'memory'))\n)" + ], + [ + "table", + "pc_artifacts", + "pc_artifacts", + "CREATE TABLE pc_artifacts (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tcontent BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision)\n)" + ], + [ + "table", + "pc_atomic_memory_current", + "pc_atomic_memory_current", + "CREATE TABLE pc_atomic_memory_current (\n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision BIGINT NOT NULL, \n\tstate_version BIGINT NOT NULL, \n\tcontent_hash VARCHAR(64) NOT NULL, \n\tprojection_format VARCHAR(64) NOT NULL, \n\tkind TEXT NOT NULL, \n\ttext TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\ttag_keys TEXT NOT NULL, \n\tembedding BLOB, \n\tprofile_fingerprint VARCHAR(64), \n\tembedding_input_hash VARCHAR(64), \n\tPRIMARY KEY (scope_id, artifact_id), \n\tCONSTRAINT ck_pc_atomic_memory_current_revision CHECK (revision > 0), \n\tCONSTRAINT ck_pc_atomic_memory_current_state_version CHECK (state_version >= 0), \n\tCONSTRAINT ck_pc_atomic_memory_current_embedding_metadata CHECK ((embedding IS NULL AND profile_fingerprint IS NULL AND embedding_input_hash IS NULL) OR (embedding IS NOT NULL AND profile_fingerprint IS NOT NULL AND embedding_input_hash IS NOT NULL))\n)" + ], + [ + "table", + "pc_atomic_memory_current_fts", + "pc_atomic_memory_current_fts", + "CREATE VIRTUAL TABLE pc_atomic_memory_current_fts USING fts5(scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED, state_version UNINDEXED, content_hash UNINDEXED, projection_format UNINDEXED, kind UNINDEXED, text UNINDEXED, searchable_text, tag_keys UNINDEXED, identity_token, tokenize='unicode61')" + ], + [ + "table", + "pc_atomic_memory_current_fts_config", + "pc_atomic_memory_current_fts_config", + "CREATE TABLE 'pc_atomic_memory_current_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_atomic_memory_current_fts_content", + "pc_atomic_memory_current_fts_content", + "CREATE TABLE 'pc_atomic_memory_current_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)" + ], + [ + "table", + "pc_atomic_memory_current_fts_data", + "pc_atomic_memory_current_fts_data", + "CREATE TABLE 'pc_atomic_memory_current_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_atomic_memory_current_fts_docsize", + "pc_atomic_memory_current_fts_docsize", + "CREATE TABLE 'pc_atomic_memory_current_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_atomic_memory_current_fts_idx", + "pc_atomic_memory_current_fts_idx", + "CREATE TABLE 'pc_atomic_memory_current_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_connector_checkpoints", + "pc_connector_checkpoints", + "CREATE TABLE pc_connector_checkpoints (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_id VARCHAR(256) NOT NULL, \n\tconnector_name VARCHAR(128) NOT NULL, \n\tconnector_version VARCHAR(128) NOT NULL, \n\tcheckpoint BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, binding_id)\n)" + ], + [ + "table", + "pc_decision_observations", + "pc_decision_observations", + "CREATE TABLE pc_decision_observations (\n\tscope_id VARCHAR(256) NOT NULL, \n\tobservation_id VARCHAR(128) NOT NULL, \n\toperation_id VARCHAR(256) NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tconsumer VARCHAR(128) NOT NULL, \n\tpolicy_id VARCHAR(256) NOT NULL, \n\tpolicy_version VARCHAR(64) NOT NULL, \n\tmode VARCHAR(16) NOT NULL, \n\tprivacy_boundary VARCHAR(32) NOT NULL, \n\tcoverage VARCHAR(16) NOT NULL, \n\tverdict VARCHAR(16) NOT NULL, \n\tassessment_source VARCHAR(32) NOT NULL, \n\tfinal_action VARCHAR(128) NOT NULL, \n\tused_fallback BOOLEAN NOT NULL, \n\tfallback_reason VARCHAR(128), \n\tprovider_id VARCHAR(128), \n\tbackend_model_id VARCHAR(256), \n\tmodel_policy_id VARCHAR(256), \n\trequests BIGINT NOT NULL, \n\tinput_tokens BIGINT, \n\toutput_tokens BIGINT, \n\tlatency_ms FLOAT, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, observation_id), \n\tCONSTRAINT ck_pc_decision_observations_requests_nonnegative CHECK (requests >= 0), \n\tCONSTRAINT ck_pc_decision_observations_input_tokens_nonnegative CHECK (input_tokens IS NULL OR input_tokens >= 0), \n\tCONSTRAINT ck_pc_decision_observations_output_tokens_nonnegative CHECK (output_tokens IS NULL OR output_tokens >= 0), \n\tCONSTRAINT ck_pc_decision_observations_latency_nonnegative CHECK (latency_ms IS NULL OR latency_ms >= 0)\n)" + ], + [ + "table", + "pc_dream_runs", + "pc_dream_runs", + "CREATE TABLE pc_dream_runs (\n\tscope_id VARCHAR(256) NOT NULL, \n\trun_id VARCHAR(64) NOT NULL, \n\tprincipal_key VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(128) NOT NULL, \n\trequest_digest VARCHAR(71) NOT NULL, \n\toperation VARCHAR(32) NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\taccepted_at BIGINT NOT NULL, \n\tgeneration INTEGER NOT NULL, \n\trequest_generation BIGINT NOT NULL, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, run_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_dream_idempotency UNIQUE (scope_id, principal_key, idempotency_key), \n\tCONSTRAINT ck_pc_dream_generation CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_external_skill_registrations", + "pc_external_skill_registrations", + "CREATE TABLE pc_external_skill_registrations (\n\tscope_id VARCHAR(256) NOT NULL, \n\texternal_skill_id VARCHAR(128) NOT NULL, \n\tprovider VARCHAR(128) NOT NULL, \n\tagent_kind VARCHAR(128) NOT NULL, \n\thost_id VARCHAR(128) NOT NULL, \n\tinstallation_scope VARCHAR(16) NOT NULL, \n\tlocator VARCHAR(2000) NOT NULL, \n\tlocator_hash VARCHAR(64) NOT NULL, \n\tfingerprint VARCHAR(64) NOT NULL, \n\tname VARCHAR(128) NOT NULL, \n\tdescription VARCHAR(2000) NOT NULL, \n\tPRIMARY KEY (scope_id, external_skill_id), \n\tCONSTRAINT uq_pc_external_skill_registrations_binding UNIQUE (scope_id, provider, host_id, installation_scope, locator_hash), \n\tCONSTRAINT ck_pc_external_skill_registrations_scope CHECK (installation_scope IN ('user', 'project', 'plugin'))\n)" + ], + [ + "table", + "pc_memory_entry_heads", + "pc_memory_entry_heads", + "CREATE TABLE pc_memory_entry_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tmemory_artifact_id VARCHAR(128) NOT NULL, \n\thead_revision INTEGER NOT NULL, \n\tentry_id VARCHAR(128) NOT NULL, \n\tentry_version_id VARCHAR(128) NOT NULL, \n\tentry_content_hash VARCHAR(64) NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tPRIMARY KEY (scope_id, memory_artifact_id, entry_id), \n\tFOREIGN KEY(scope_id, memory_artifact_id, entry_id, entry_version_id) REFERENCES pc_memory_entry_versions (scope_id, memory_artifact_id, entry_id, entry_version_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_memory_entry_heads_revision_positive CHECK (head_revision > 0)\n)" + ], + [ + "table", + "pc_memory_entry_versions", + "pc_memory_entry_versions", + "CREATE TABLE pc_memory_entry_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tmemory_artifact_id VARCHAR(128) NOT NULL, \n\tentry_id VARCHAR(128) NOT NULL, \n\tentry_version_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tprevious_version_id VARCHAR(128), \n\tkind VARCHAR(128) NOT NULL, \n\ttext TEXT NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\tentry_content_hash VARCHAR(64) NOT NULL, \n\tcreated_in_revision INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, memory_artifact_id, entry_version_id), \n\tCONSTRAINT uq_pc_memory_entry_versions_logical_version UNIQUE (scope_id, memory_artifact_id, entry_id, version), \n\tCONSTRAINT uq_pc_memory_entry_versions_identity UNIQUE (scope_id, memory_artifact_id, entry_id, entry_version_id), \n\tCONSTRAINT ck_pc_memory_entry_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_memory_entry_versions_revision_positive CHECK (created_in_revision > 0)\n)" + ], + [ + "table", + "pc_memory_source_windows", + "pc_memory_source_windows", + "CREATE TABLE pc_memory_source_windows (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\twindow_limit BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tCONSTRAINT ck_pc_memory_source_window_positive CHECK (source_through > 0 AND window_limit > 0)\n)" + ], + [ + "table", + "pc_model_usage_daily", + "pc_model_usage_daily", + "CREATE TABLE pc_model_usage_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\tpurpose VARCHAR(64) NOT NULL, \n\toperation VARCHAR(16) NOT NULL, \n\trequests BIGINT NOT NULL, \n\tinput_tokens BIGINT NOT NULL, \n\toutput_tokens BIGINT NOT NULL, \n\tinput_complete BOOLEAN NOT NULL, \n\toutput_complete BOOLEAN NOT NULL, \n\tPRIMARY KEY (scope_id, usage_date, purpose, operation), \n\tCONSTRAINT ck_pc_model_usage_daily_operation CHECK (operation IN ('generation', 'embedding')), \n\tCONSTRAINT ck_pc_model_usage_daily_requests_nonnegative CHECK (requests >= 0), \n\tCONSTRAINT ck_pc_model_usage_daily_input_nonnegative CHECK (input_tokens >= 0), \n\tCONSTRAINT ck_pc_model_usage_daily_output_nonnegative CHECK (output_tokens >= 0)\n)" + ], + [ + "table", + "pc_profile_policies", + "pc_profile_policies", + "CREATE TABLE pc_profile_policies (\n\tscope_id VARCHAR(256) NOT NULL, \n\tgeneration_enabled BOOLEAN NOT NULL, \n\tactivation_mode VARCHAR(32) NOT NULL, \n\tpending_candidate_id VARCHAR(128), \n\tversion BIGINT NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, pending_candidate_id) REFERENCES pc_artifact_candidate_heads (scope_id, candidate_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_profile_policies_activation_mode CHECK (activation_mode IN ('automatic', 'review_required')), \n\tCONSTRAINT ck_pc_profile_policies_version_positive CHECK (version > 0)\n)" + ], + [ + "table", + "pc_recall_effort_daily", + "pc_recall_effort_daily", + "CREATE TABLE pc_recall_effort_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\tpolicy_id VARCHAR(128) NOT NULL, \n\tassessment VARCHAR(64) NOT NULL, \n\tpreparations BIGINT NOT NULL CONSTRAINT ck_recall_effort_preparations_nonnegative CHECK (preparations >= 0), \n\trounds BIGINT NOT NULL CONSTRAINT ck_recall_effort_rounds_nonnegative CHECK (rounds >= 0), \n\texpanded_preparations BIGINT NOT NULL CONSTRAINT ck_recall_effort_expanded_preparations_nonnegative CHECK (expanded_preparations >= 0), \n\tadmission_expansions BIGINT NOT NULL CONSTRAINT ck_recall_effort_admission_expansions_nonnegative CHECK (admission_expansions >= 0), \n\tpolicy_floor_expansions BIGINT NOT NULL CONSTRAINT ck_recall_effort_policy_floor_expansions_nonnegative CHECK (policy_floor_expansions >= 0), \n\tcandidate_round_samples BIGINT NOT NULL CONSTRAINT ck_recall_effort_candidate_round_samples_nonnegative CHECK (candidate_round_samples >= 0), \n\tcandidates_assessed BIGINT NOT NULL CONSTRAINT ck_recall_effort_candidates_assessed_nonnegative CHECK (candidates_assessed >= 0), \n\tfinal_candidate_pool BIGINT NOT NULL CONSTRAINT ck_recall_effort_final_candidate_pool_nonnegative CHECK (final_candidate_pool >= 0), \n\tadded_embeddings BIGINT NOT NULL CONSTRAINT ck_recall_effort_added_embeddings_nonnegative CHECK (added_embeddings >= 0), \n\tadded_generation_calls BIGINT NOT NULL CONSTRAINT ck_recall_effort_added_generation_calls_nonnegative CHECK (added_generation_calls >= 0), \n\ttruncated_items BIGINT NOT NULL CONSTRAINT ck_recall_effort_truncated_items_nonnegative CHECK (truncated_items >= 0), \n\tdropped_items BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_items_nonnegative CHECK (dropped_items >= 0), \n\tdropped_below_min_bytes BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_below_min_bytes_nonnegative CHECK (dropped_below_min_bytes >= 0), \n\tdropped_no_fitting_truncation BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_no_fitting_truncation_nonnegative CHECK (dropped_no_fitting_truncation >= 0), \n\tPRIMARY KEY (scope_id, usage_date, policy_id, assessment), \n\tCONSTRAINT ck_pc_recall_effort_daily_drop_total CHECK (dropped_items = dropped_below_min_bytes + dropped_no_fitting_truncation)\n)" + ], + [ + "table", + "pc_recall_token_daily", + "pc_recall_token_daily", + "CREATE TABLE pc_recall_token_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\testimator_id VARCHAR(128) NOT NULL, \n\testimator_version VARCHAR(64) NOT NULL, \n\tpreparations BIGINT NOT NULL, \n\tready_preparations BIGINT NOT NULL, \n\tcomparable_preparations BIGINT NOT NULL, \n\tbaseline_tokens BIGINT NOT NULL, \n\trecalled_tokens BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, usage_date, estimator_id, estimator_version), \n\tCONSTRAINT ck_pc_recall_token_daily_preparations_nonnegative CHECK (preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_ready_nonnegative CHECK (ready_preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_comparable_nonnegative CHECK (comparable_preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_comparable_ready CHECK (comparable_preparations <= ready_preparations), \n\tCONSTRAINT ck_pc_recall_token_daily_ready_total CHECK (ready_preparations <= preparations), \n\tCONSTRAINT ck_pc_recall_token_daily_baseline_nonnegative CHECK (baseline_tokens >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_recalled_nonnegative CHECK (recalled_tokens >= 0)\n)" + ], + [ + "table", + "pc_receipt_migration_review", + "pc_receipt_migration_review", + "CREATE TABLE pc_receipt_migration_review (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\treason VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, source_id)\n)" + ], + [ + "table", + "pc_recurrence_match", + "pc_recurrence_match", + "CREATE TABLE pc_recurrence_match (\n\tscope_id VARCHAR(256) NOT NULL, \n\tmatch_key VARCHAR(71) NOT NULL, \n\ttask_outcome_source_type VARCHAR(128) NOT NULL, \n\ttask_outcome_source_id VARCHAR(256) NOT NULL, \n\ttask_outcome_position BIGINT NOT NULL, \n\tfailure_item_kind VARCHAR(16) NOT NULL, \n\tfailure_item_index INTEGER NOT NULL, \n\tfailure_item_digest VARCHAR(71) NOT NULL, \n\tcandidate_set_mode VARCHAR(32) NOT NULL, \n\tcandidate_set_digest VARCHAR(71) NOT NULL, \n\tresult VARCHAR(16) NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\tsignature_key TEXT, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, match_key), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_recurrence_match_identity UNIQUE (scope_id, task_outcome_source_type, task_outcome_source_id, failure_item_kind, failure_item_index), \n\tCONSTRAINT ck_pc_recurrence_match_result CHECK (result IN ('matched', 'unmatched', 'ambiguous')), \n\tCONSTRAINT ck_pc_recurrence_match_candidate_set_mode CHECK (candidate_set_mode IN ('handoff_citations', 'scope_heads')), \n\tCONSTRAINT ck_pc_recurrence_match_failure_item_kind CHECK (failure_item_kind IN ('observation', 'check')), \n\tCONSTRAINT ck_pc_recurrence_match_position_positive CHECK (task_outcome_position > 0), \n\tCONSTRAINT ck_pc_recurrence_match_item_index_nonnegative CHECK (failure_item_index >= 0)\n)" + ], + [ + "table", + "pc_recurrence_observation", + "pc_recurrence_observation", + "CREATE TABLE pc_recurrence_observation (\n\tscope_id VARCHAR(256) NOT NULL, \n\tobservation_id VARCHAR(71) NOT NULL, \n\tselection_key VARCHAR(71) NOT NULL, \n\tverdict_key VARCHAR(71) NOT NULL, \n\tevent VARCHAR(16) NOT NULL, \n\tmatch_basis VARCHAR(8) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsignature_key TEXT NOT NULL, \n\tsignature_key_hash BLOB NOT NULL, \n\ttask_outcome_source_type VARCHAR(128) NOT NULL, \n\ttask_outcome_source_id VARCHAR(256) NOT NULL, \n\ttask_outcome_position BIGINT NOT NULL, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, observation_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_recurrence_observation_selection UNIQUE (scope_id, selection_key), \n\tCONSTRAINT uq_pc_recurrence_observation_verdict UNIQUE (scope_id, verdict_key), \n\tCONSTRAINT ck_pc_recurrence_observation_event CHECK (event IN ('selected', 'recurred', 'avoided')), \n\tCONSTRAINT ck_pc_recurrence_observation_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_recurrence_observation_position_positive CHECK (task_outcome_position > 0)\n)" + ], + [ + "table", + "pc_scope_bindings", + "pc_scope_bindings", + "CREATE TABLE pc_scope_bindings (\n\tintegration VARCHAR(128) NOT NULL, \n\tkind VARCHAR(64) NOT NULL, \n\texternal_id VARCHAR(256) NOT NULL, \n\texternal_id_search TEXT NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (integration, kind, external_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scope_context_references", + "pc_scope_context_references", + "CREATE TABLE pc_scope_context_references (\n\tscope_id VARCHAR(256) NOT NULL, \n\treferenced_scope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (scope_id, referenced_scope_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(referenced_scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_scope_context_references_not_self CHECK (scope_id <> referenced_scope_id)\n)" + ], + [ + "table", + "pc_scope_creation_requests", + "pc_scope_creation_requests", + "CREATE TABLE pc_scope_creation_requests (\n\tidempotency_key VARCHAR(256) NOT NULL, \n\trequest_digest VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (idempotency_key), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scope_external_references", + "pc_scope_external_references", + "CREATE TABLE pc_scope_external_references (\n\tscope_id VARCHAR(256) NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tkind VARCHAR(128) NOT NULL, \n\tvalue VARCHAR(2000) NOT NULL, \n\tvalue_search TEXT NOT NULL, \n\tvalue_digest VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, ordinal), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_scope_external_references_value UNIQUE (scope_id, kind, value_digest), \n\tCONSTRAINT ck_pc_scope_external_references_ordinal_nonnegative CHECK (ordinal >= 0)\n)" + ], + [ + "table", + "pc_scope_settings", + "pc_scope_settings", + "CREATE TABLE pc_scope_settings (\n\tname VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (name), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scopes", + "pc_scopes", + "CREATE TABLE pc_scopes (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttitle VARCHAR(256) NOT NULL, \n\tsummary VARCHAR(2000) NOT NULL, \n\tscope_id_search TEXT NOT NULL, \n\ttitle_search TEXT NOT NULL, \n\tsummary_search TEXT NOT NULL, \n\tparent_scope_id VARCHAR(256), \n\tversion INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tFOREIGN KEY(parent_scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_scopes_version_positive CHECK (version > 0)\n)" + ], + [ + "table", + "pc_server_identity", + "pc_server_identity", + "CREATE TABLE pc_server_identity (\n\tsingleton_key INTEGER NOT NULL, \n\tserver_id VARCHAR(36) NOT NULL, \n\tPRIMARY KEY (singleton_key), \n\tCONSTRAINT ck_pc_server_identity_singleton CHECK (singleton_key = 1), \n\tUNIQUE (server_id)\n)" + ], + [ + "table", + "pc_skill_packages", + "pc_skill_packages", + "CREATE TABLE pc_skill_packages (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttree_digest VARCHAR(64) NOT NULL, \n\tarchive_digest VARCHAR(64) NOT NULL, \n\tarchive_bytes BLOB NOT NULL, \n\tmanifest BLOB NOT NULL, \n\tfile_count INTEGER NOT NULL, \n\tuncompressed_size BIGINT NOT NULL, \n\tarchive_size BIGINT NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, tree_digest), \n\tCONSTRAINT ck_pc_skill_packages_file_count CHECK (file_count > 0 AND file_count <= 256), \n\tCONSTRAINT ck_pc_skill_packages_uncompressed_size CHECK (uncompressed_size > 0 AND uncompressed_size <= 4194304), \n\tCONSTRAINT ck_pc_skill_packages_archive_size CHECK (archive_size > 0 AND archive_size <= 5242880)\n)" + ], + [ + "table", + "pc_skill_publications", + "pc_skill_publications", + "CREATE TABLE pc_skill_publications (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_id VARCHAR(64) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\tdesired_state VARCHAR(16) DEFAULT 'published' NOT NULL, \n\tdesired_revision INTEGER NOT NULL, \n\tdesired_tree_digest VARCHAR(64) NOT NULL, \n\tobserved_revision INTEGER, \n\tobserved_tree_digest VARCHAR(64), \n\tobserved_generation BIGINT, \n\tdestination TEXT, \n\tstate VARCHAR(32) NOT NULL, \n\tselected_runtime_variant VARCHAR(128), \n\tenvironment_fingerprint VARCHAR(64), \n\tlast_error_code VARCHAR(128), \n\tobserved_at DATETIME, \n\tgeneration BIGINT NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, target_id, artifact_id), \n\tCONSTRAINT ck_pc_skill_publications_desired_revision_positive CHECK (desired_revision > 0), \n\tCONSTRAINT ck_pc_skill_publications_observed_revision_positive CHECK (observed_revision IS NULL OR observed_revision > 0), \n\tCONSTRAINT ck_pc_skill_publications_desired_state CHECK (desired_state IN ('published', 'unpublished')), \n\tCONSTRAINT ck_pc_skill_publications_state CHECK (state IN ('unpublished', 'pending', 'current', 'update_available', 'delivery_failed', 'conflict', 'drifted', 'incompatible')), \n\tCONSTRAINT ck_pc_skill_publications_observed_generation_nonnegative CHECK (observed_generation IS NULL OR observed_generation >= 0), \n\tCONSTRAINT ck_pc_skill_publications_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_source_cursors", + "pc_source_cursors", + "CREATE TABLE pc_source_cursors (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tcursor BLOB NOT NULL, \n\tgeneration BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, binding_name), \n\tCONSTRAINT ck_pc_source_cursors_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_source_definition_manifests", + "pc_source_definition_manifests", + "CREATE TABLE pc_source_definition_manifests (\n\tdefinition_name VARCHAR(128) NOT NULL, \n\tdefinition_version VARCHAR(128) NOT NULL, \n\tfingerprint VARCHAR(71) NOT NULL, \n\tmanifest BLOB NOT NULL, \n\tPRIMARY KEY (definition_name, definition_version), \n\tCONSTRAINT uq_pc_source_definition_manifest_fingerprint UNIQUE (definition_name, fingerprint)\n)" + ], + [ + "table", + "pc_source_journal_heads", + "pc_source_journal_heads", + "CREATE TABLE pc_source_journal_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tposition BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tCONSTRAINT ck_pc_source_journal_heads_position_nonnegative CHECK (position >= 0)\n)" + ], + [ + "table", + "pc_sources", + "pc_sources", + "CREATE TABLE pc_sources (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_type VARCHAR(128) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\tpayload BLOB NOT NULL, \n\tjournal_position BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, source_type, source_id), \n\tCONSTRAINT uq_pc_sources_scope_journal_position UNIQUE (scope_id, journal_position)\n)" + ], + [ + "table", + "pc_topic_memory_active_chunks", + "pc_topic_memory_active_chunks", + "CREATE TABLE pc_topic_memory_active_chunks (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tchunk_ordinal INTEGER NOT NULL, \n\tstart_offset INTEGER NOT NULL, \n\tend_offset INTEGER NOT NULL, \n\tchunk_text TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tpolicy_version VARCHAR(32) NOT NULL, \n\tPRIMARY KEY (scope_id, artifact_id, chunk_ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_topic_memory_revision_publications (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_ordinal_nonnegative CHECK (chunk_ordinal >= 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_start_nonnegative CHECK (start_offset >= 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_offsets CHECK (end_offset > start_offset)\n)" + ], + [ + "table", + "pc_topic_memory_active_topics", + "pc_topic_memory_active_topics", + "CREATE TABLE pc_topic_memory_active_topics (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\ttitle TEXT NOT NULL, \n\tsummary TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tsource_count INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_topic_memory_revision_publications (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_active_topics_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_active_topics_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_topic_memory_active_topics_sources_nonnegative CHECK (source_count >= 0)\n)" + ], + [ + "table", + "pc_topic_memory_chunk_fts", + "pc_topic_memory_chunk_fts", + "CREATE VIRTUAL TABLE pc_topic_memory_chunk_fts USING fts5(\n scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED,\n chunk_ordinal UNINDEXED, start_offset UNINDEXED,\n title UNINDEXED, summary UNINDEXED, chunk_text UNINDEXED,\n searchable_text, tokenize='unicode61'\n)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_config", + "pc_topic_memory_chunk_fts_config", + "CREATE TABLE 'pc_topic_memory_chunk_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_chunk_fts_content", + "pc_topic_memory_chunk_fts_content", + "CREATE TABLE 'pc_topic_memory_chunk_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5, c6, c7, c8)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_data", + "pc_topic_memory_chunk_fts_data", + "CREATE TABLE 'pc_topic_memory_chunk_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_docsize", + "pc_topic_memory_chunk_fts_docsize", + "CREATE TABLE 'pc_topic_memory_chunk_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_idx", + "pc_topic_memory_chunk_fts_idx", + "CREATE TABLE 'pc_topic_memory_chunk_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_fts_index", + "pc_topic_memory_fts_index", + "CREATE TABLE pc_topic_memory_fts_index (\n\tsingleton INTEGER NOT NULL, \n\tschema_version INTEGER NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_topic_memory_fts_index_singleton CHECK (singleton = 1)\n)" + ], + [ + "table", + "pc_topic_memory_processing_targets", + "pc_topic_memory_processing_targets", + "CREATE TABLE pc_topic_memory_processing_targets (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_request_generation BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tcaptured_flush_generation BIGINT NOT NULL, \n\tobserved_dirty_generation BIGINT NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_topic_processing_target CHECK (target_request_generation > 0 AND source_through >= 0 AND captured_flush_generation >= 0 AND observed_dirty_generation >= 0)\n)" + ], + [ + "table", + "pc_topic_memory_retrieval_shape", + "pc_topic_memory_retrieval_shape", + "CREATE TABLE pc_topic_memory_retrieval_shape (\n\tsingleton INTEGER NOT NULL, \n\tshape VARCHAR(16) NOT NULL, \n\tprofile_fingerprint VARCHAR(64), \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_singleton CHECK (singleton = 1), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_value CHECK (shape IN ('fts', 'hybrid')), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_profile CHECK ((shape = 'fts' AND profile_fingerprint IS NULL) OR (shape = 'hybrid' AND profile_fingerprint IS NOT NULL))\n)" + ], + [ + "table", + "pc_topic_memory_revision_publications", + "pc_topic_memory_revision_publications", + "CREATE TABLE pc_topic_memory_revision_publications (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tpublished_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_publications_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_publications_revision_positive CHECK (revision > 0)\n)" + ], + [ + "table", + "pc_topic_memory_topic_fts", + "pc_topic_memory_topic_fts", + "CREATE VIRTUAL TABLE pc_topic_memory_topic_fts USING fts5(\n scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED,\n title UNINDEXED, summary UNINDEXED, searchable_text, tokenize='unicode61'\n)" + ], + [ + "table", + "pc_topic_memory_topic_fts_config", + "pc_topic_memory_topic_fts_config", + "CREATE TABLE 'pc_topic_memory_topic_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_topic_fts_content", + "pc_topic_memory_topic_fts_content", + "CREATE TABLE 'pc_topic_memory_topic_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5)" + ], + [ + "table", + "pc_topic_memory_topic_fts_data", + "pc_topic_memory_topic_fts_data", + "CREATE TABLE 'pc_topic_memory_topic_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_topic_memory_topic_fts_docsize", + "pc_topic_memory_topic_fts_docsize", + "CREATE TABLE 'pc_topic_memory_topic_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_topic_memory_topic_fts_idx", + "pc_topic_memory_topic_fts_idx", + "CREATE TABLE 'pc_topic_memory_topic_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_work_budgets", + "pc_topic_memory_work_budgets", + "CREATE TABLE pc_topic_memory_work_budgets (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tsource_after BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tattempt_id VARCHAR(36) NOT NULL, \n\tattempts INTEGER NOT NULL, \n\trequests BIGINT NOT NULL, \n\ttokens BIGINT NOT NULL, \n\tfailure_code VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, binding_name, source_after), \n\tCONSTRAINT ck_pc_topic_budget_window CHECK (source_after >= 0 AND source_through > source_after), \n\tCONSTRAINT ck_pc_topic_budget_usage CHECK (attempts > 0 AND requests >= 0 AND tokens >= 0)\n)" + ], + [ + "trigger", + "pc_atomic_memory_fts_delete", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_delete AFTER DELETE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_fts WHERE identity_token MATCH ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); END" + ], + [ + "trigger", + "pc_atomic_memory_fts_insert", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_insert AFTER INSERT ON pc_atomic_memory_current BEGIN INSERT INTO pc_atomic_memory_current_fts (scope_id, artifact_id, revision, state_version, content_hash, projection_format, kind, text, searchable_text, tag_keys, identity_token) VALUES (new.scope_id, new.artifact_id, new.revision, new.state_version, new.content_hash, new.projection_format, new.kind, new.text, new.searchable_text, new.tag_keys, 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id))); END" + ], + [ + "trigger", + "pc_atomic_memory_fts_update", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_update AFTER UPDATE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_fts WHERE identity_token MATCH ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); INSERT INTO pc_atomic_memory_current_fts (scope_id, artifact_id, revision, state_version, content_hash, projection_format, kind, text, searchable_text, tag_keys, identity_token) VALUES (new.scope_id, new.artifact_id, new.revision, new.state_version, new.content_hash, new.projection_format, new.kind, new.text, new.searchable_text, new.tag_keys, 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id))); END" + ] + ] +} diff --git a/src/powercontext/builtin/persistence/migration_resources/server_baselines/README.md b/src/powercontext/builtin/persistence/migration_resources/server_baselines/README.md new file mode 100644 index 0000000000..5cf57a22b1 --- /dev/null +++ b/src/powercontext/builtin/persistence/migration_resources/server_baselines/README.md @@ -0,0 +1,76 @@ +# Complete Server source baselines + +Each resource records the complete backend schema inventory produced by a +specific installed public wheel. Inventories include indexes, constraints, +triggers, virtual tables, and SQLite shadow tables where applicable. They contain schema +definitions and artifact provenance, with no business rows or deployment paths. + +Recognition identifies a source schema. It does not validate its business data, +adopt a schema revision, authorize writes, establish Server readiness, or promise +an executable upgrade path. The maintenance runner keeps an identified source +blocked until the registered migration chain covers that complete source and its +domain preconditions. + +## SQLite 1.3.0 + +`1.3.0/sqlite.json` was captured from the public +`powercontext-1.3.0-py3-none-any.whl`, SHA-256 +`8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5`. +The wheel was installed with its `cli,server` extras in an isolated environment. +A real Server initialized a new file with enforced Access, all Processing roles, +and no model or embedding provider. Public HTTP operations created Scopes, +Atomic Memory, a Source, pending and approved Candidates, and an Access binding +with idempotency receipts. After stopping the Server, SQLite's backup API +produced the database used for schema capture. + +The resource records the Python, SQLite, and SQLAlchemy capture versions. Its +94 explicit objects comprise 81 tables, 10 indexes, and 3 triggers. The complete +schema fingerprint is +`11e8770068a5fe32dfb25681564dbbe586ba0fd0238ed01adf1b09dde8a49f22`. +This shape differs from the historical four-table migration prototype. + +The recognizer uses exact definitions. A different dependency version, optional +feature, missing FTS helper, manually altered constraint, or extra persistent +object may produce an unregistered shape. Such a shape remains unknown; a +semantically similar subset is never sufficient for adoption. Additional source +shapes require their own installed-release capture and review. + +## seekDB 1.3.0 + +`1.3.0/seekdb.json` comes from the same public wheel with its `cli,server,seekdb` +extras, `pylibseekdb==1.4.0.post1`, and engine +`5.7.25-OceanBase seekdb-v1.4.0.0`. A real installed Server initialized the +database with enforced Access, all Processing roles, and no model or embedding +provider. The same public HTTP fixture populated historical records. After the +Server exited, a maintenance connection captured all 56 tables. Their raw +`SHOW CREATE TABLE` definitions and row counts remained unchanged during the +corrected reflection. + +The normalized inventory includes all four FULLTEXT indexes, their `space` +parser, and `min_token_size=3,max_token_size=84` properties. Its fingerprint is +`041ee0d1cbf114ddafab1df096f9e7debbff1edf5d3a459cf7db847b640ff76c`. +Generated foreign-key names and physical storage options are normalized by the +MySQL reflector; index identity and search semantics are retained. Recognition +requires the complete matching inventory. It does not cover optional embedding +profiles or establish an OceanBase baseline. + +## OceanBase 1.3.0 + +`1.3.0/oceanbase.json` was captured independently from an installed public +1.3.0 Server using a direct MySQL tenant connection to +`5.7.25-OceanBase_CE-v4.3.5.6`. The container image digest is +`sha256:31086a6900c21c479c2bcd942b6a28c53b17a51f4e9b9eb8eafcc596adfcd2e3`. +The source wheel, enforced Access, all Processing roles, disabled model and +embedding providers, and public HTTP fixture match the other source captures. + +All 56 tables and four FULLTEXT indexes participate in its fingerprint +`049414cd6208742a92fdbc1a2965bf37c397ce7e7f1823e7d89ab6445040fedb`. +The native audit table's `AUTO_INCREMENT_MODE='ORDER'` is retained as a schema +property, distinct from the changing auto-increment counter. Unknown or +duplicate mode declarations reject. This source differs from the seekDB +inventory and is registered only for OceanBase. + +The stopped Server's table definitions, counts and data hashes were unchanged +after reflection and `plan/verify/apply` rejection. This is complete source +recognition with `server_migration_unavailable`, not a successful upgrade, +runtime-readiness check, ODP test or multi-node acceptance. diff --git a/src/powercontext/builtin/persistence/migrations/atomic_projection.py b/src/powercontext/builtin/persistence/migrations/atomic_projection.py new file mode 100644 index 0000000000..13108742dd --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/atomic_projection.py @@ -0,0 +1,164 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Registered current-row repair without changing managed schema or authority.""" + +from __future__ import annotations + +import hashlib +from datetime import datetime +from pathlib import Path + +from sqlalchemy import Connection, select, tuple_ +from typing_extensions import override + +from powercontext.builtin.persistence.oceanbase.atomic_memory_index import OceanBaseAtomicMemoryIndex +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.persistence.sqlite.atomic_memory_index import SQLiteAtomicMemoryIndex +from powercontext.builtin.persistence.tables import ( + ARTIFACT_HEADS_TABLE, + ARTIFACT_LINEAGE_ARTIFACTS_TABLE, + ARTIFACT_LINEAGE_SOURCES_TABLE, + ARTIFACT_TAGS_TABLE, + ARTIFACTS_TABLE, +) +from powercontext.builtin.runtime import atomic_memory_rebuild + +from .bundle import sqlite_inventory +from .connections import BackendIdentity +from .maintenance import ManagedMaintenanceRunner +from .models import MigrationError, digest +from .mysql_schema import mysql_inventory +from .pinned import finish, pinned +from .server_readiness import check_server_layout + + +def _sqlite_helper_digest(connection, batch_size): + snapshot = hashlib.sha256() + after = None + while True: + query = "SELECT rowid,* FROM pc_atomic_memory_current_fts" + parameters = {"batch": batch_size} + if after is not None: + query += " WHERE rowid > :after" + parameters["after"] = after + rows = connection.exec_driver_sql(query + " ORDER BY rowid LIMIT :batch", parameters).all() + if not rows: + return snapshot.hexdigest() + for row in rows: + snapshot.update(digest(tuple(row)).encode()) + after = rows[-1][0] + + +class AtomicProjectionMigrationRunner(ManagedMaintenanceRunner): + operation = "atomic_projection" + affected_objects: tuple[str, ...] = ("pc_atomic_memory_current", "pc_atomic_memory_current_fts") + + def __init__(self, *args, **kwargs): + super().__init__( + *args, + implementation_checksum=digest({ + "owner": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "rebuild": hashlib.sha256(Path(atomic_memory_rebuild.__file__).read_bytes()).hexdigest(), + }), + **kwargs, + ) + self.affected_objects = ( + ("pc_atomic_memory_current", "pc_atomic_memory_current_fts") + if isinstance(self.connections.config, SQLiteConfig) + else ("pc_atomic_memory_current",) + ) + + def _index(self, connection): + return SQLiteAtomicMemoryIndex() if connection.dialect.name == "sqlite" else OceanBaseAtomicMemoryIndex() + + @override + def _state(self, connection: Connection, identity: BackendIdentity) -> tuple[str, bool, bool]: + check_server_layout(connection) + index = self._index(connection) + try: + report = finish( + atomic_memory_rebuild.verify_atomic_memory_current( + pinned(connection), index, batch_size=self.batch_size + ) + ) + except Exception as error: + raise MigrationError("invalid_authority", "Repair requires valid Atomic Memory authority.") from error + required = not report.ready + if isinstance(index, SQLiteAtomicMemoryIndex): + required |= not finish(index.fts_matches_current(pinned(connection))) + try: + self.bundle.domains.validate( + connection, self.bundle.head, backend=identity.product, configuration=self.configuration + ) + except MigrationError as error: + # Only the current projection/head or helper mismatch can be + # admitted here. Full validation must pass before repair commits. + if error.code != "projection_verification_failed" or not required: + raise + fingerprint = digest({ + "schema": sqlite_inventory(connection) if identity.product == "sqlite" else mysql_inventory(connection), + "rows": self._rows_digest(connection, index.table), + }) + return fingerprint, required, not required + + def _rows_digest(self, connection, current): + snapshot = hashlib.sha256() + for table in ( + ARTIFACTS_TABLE, + ARTIFACT_HEADS_TABLE, + ARTIFACT_TAGS_TABLE, + ARTIFACT_LINEAGE_SOURCES_TABLE, + ARTIFACT_LINEAGE_ARTIFACTS_TABLE, + current, + ): + snapshot.update(table.name.encode()) + keys = tuple(table.primary_key.columns) + statement = select(table).order_by(*keys).limit(self.batch_size) + if "family" in table.c: + statement = statement.where(table.c.family == "atomic-memory") + after = None + while True: + query = statement if after is None else statement.where(tuple_(*keys) > after) + rows = connection.execute(query).mappings().all() + if not rows: + break + for row in rows: + snapshot.update( + digest([ + {"bytes": value.hex()} + if isinstance(value, bytes) + else value.isoformat() + if isinstance(value, datetime) + else value + for value in row.values() + ]).encode() + ) + after = tuple(rows[-1][key.name] for key in keys) + if connection.dialect.name == "sqlite": + snapshot.update(_sqlite_helper_digest(connection, self.batch_size).encode()) + return snapshot.hexdigest() + + @override + def _apply_operation(self, connection: Connection, guard) -> None: + index = self._index(connection) + report = finish( + atomic_memory_rebuild.rebuild_atomic_memory_current( + pinned(connection), index, batch_size=self.batch_size, guard=guard + ) + ) + if not report.ready: + raise MigrationError("projection_verification_failed", "Atomic current repair failed validation.") + if isinstance(index, SQLiteAtomicMemoryIndex): + finish(index.rebuild_fts(pinned(connection))) diff --git a/src/powercontext/builtin/persistence/migrations/bundle.py b/src/powercontext/builtin/persistence/migrations/bundle.py index c38bc2671e..f3cb808288 100644 --- a/src/powercontext/builtin/persistence/migrations/bundle.py +++ b/src/powercontext/builtin/persistence/migrations/bundle.py @@ -18,6 +18,7 @@ import hashlib import json +import re import threading from pathlib import Path from tempfile import TemporaryDirectory @@ -27,7 +28,11 @@ from alembic.script import ScriptDirectory from sqlalchemy import Connection +from powercontext.builtin.artifacts.memory import EmbeddingProfile + +from .domains import DomainRegistry from .models import MigrationError, digest +from .vector_profile import ServerVectorProfile ALEMBIC_CONTEXT_LOCK = threading.RLock() @@ -40,7 +45,7 @@ class MigrationBundle: partially applied DDL cannot be mistaken for a supported baseline. """ - def __init__(self, directory: Path) -> None: + def __init__(self, directory: Path, *, embedding_profile: EmbeddingProfile | None = None) -> None: # noqa: C901 -- Keep ordered admission checks with their operation. self.directory = directory.resolve() # Execute exactly the reviewed bytes, even if the installed resources # change during a long backup. Alembic reloads scripts on every command. @@ -67,6 +72,33 @@ def __init__(self, directory: Path) -> None: self.revisions = tuple(item.revision for item in revisions) self.head = self.revisions[-1] manifest = json.loads(resources["manifest.json"]) + self.vector_profile = ( + ServerVectorProfile(self.snapshot_directory, embedding_profile) if embedding_profile is not None else None + ) + if self.vector_profile is not None: + layouts = self.vector_profile.sqlite_layouts(self.snapshot_directory) + manifest["sqlite_fingerprints"] = {revision: [digest(rows)] for revision, rows in layouts.items()} + manifest["sqlite_baselines"] = {digest(layouts["s1300"]): "s1300"} + manifest["affected_objects"] = list( + dict.fromkeys([ + *manifest["affected_objects"], + *(row[1] for row in self.vector_profile.layout["sqlite"]["target"]), + ]) + ) + for step in manifest["domain_steps"]: + if step["id"] == "server_upgrade": + step["module"] = "server_vector_domain.py" + step["resources"].append(ServerVectorProfile.resource) + mysql_layouts = manifest.get("mysql_layouts", []) + if ( + not isinstance(mysql_layouts, list) + or any(not isinstance(name, str) or name not in resources for name in mysql_layouts) + or len(set(mysql_layouts)) != len(mysql_layouts) + ): + raise MigrationError("invalid_bundle", "MySQL layout registration is outside the frozen bundle.") + self.mysql_layouts: tuple[str, ...] = tuple(mysql_layouts) + self.readiness_scope = manifest.get("readiness_scope", "registered_bundle") + self.mysql_wrappers = self._mysql_wrappers(manifest, resources) self.affected_objects = tuple(manifest.get("affected_objects", ())) self.retained_objects = tuple(manifest.get("retained_objects", ())) self.retained_fingerprints = manifest.get("retained_fingerprints", {}) @@ -82,6 +114,7 @@ def __init__(self, directory: Path) -> None: if any(fingerprint not in self.fingerprints[revision] for fingerprint, revision in self.baselines.items()): raise MigrationError("invalid_bundle", "A baseline does not match its declared revision.") self.resources = {name: hashlib.sha256(content).hexdigest() for name, content in resources.items()} + self.domains = DomainRegistry(manifest, self.revisions, self.snapshot_directory, self.resources) self.checksums = {} for item in revisions: dependencies = manifest["revision_resources"][item.revision] @@ -89,9 +122,28 @@ def __init__(self, directory: Path) -> None: inputs[Path(item.path).relative_to(self.snapshot_directory).as_posix()] = hashlib.sha256( Path(item.path).read_bytes() ).hexdigest() + domain_inputs = self.domains.checksum_inputs(item.revision, self.resources) + if domain_inputs: + inputs["domain_contract"] = digest(domain_inputs) + if self.vector_profile is not None: + inputs["server_vector_profile"] = self.vector_profile.checksum self.checksums[item.revision] = digest(inputs) self.checksum = digest(self.resources) + def _mysql_wrappers(self, manifest: dict[str, Any], resources: dict[str, bytes]) -> dict[str, str]: + wrappers = manifest.get("mysql_wrappers", {}) + if not isinstance(wrappers, dict) or any( + revision not in self.revisions + or re.fullmatch(r"[A-Za-z0-9_-]+", revision) is None + or not isinstance(name, str) + or name not in resources + or not name.endswith(".py") + or name not in manifest["revision_resources"][revision] + for revision, name in wrappers.items() + ): + raise MigrationError("invalid_bundle", "MySQL execution registration is outside its frozen revision.") + return dict(wrappers) + def assert_unchanged(self) -> None: resources = { path.relative_to(self.directory).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() diff --git a/src/powercontext/builtin/persistence/migrations/connections.py b/src/powercontext/builtin/persistence/migrations/connections.py index ef85571df8..83b93c39e0 100644 --- a/src/powercontext/builtin/persistence/migrations/connections.py +++ b/src/powercontext/builtin/persistence/migrations/connections.py @@ -18,13 +18,16 @@ import asyncio import socket +import sqlite3 from collections.abc import Callable +from contextlib import nullcontext from dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import Literal, TypeVar -from sqlalchemy import Connection +from sqlalchemy import Connection, create_engine +from sqlalchemy.engine import make_url from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine from sqlalchemy.pool import NullPool @@ -33,6 +36,7 @@ from powercontext.builtin.persistence.oceanbase import profile as oceanbase_profile from powercontext.builtin.persistence.seekdb import SeekDBConfig from powercontext.builtin.persistence.seekdb import profile as seekdb_profile +from powercontext.builtin.persistence.sqlite import SQLiteConfig from .locking import local_migration_lock from .models import MigrationError, digest @@ -61,7 +65,7 @@ class MaintenanceConnections: def __init__( self, - config: SeekDBConfig | OceanBaseConfig, + config: SQLiteConfig | SeekDBConfig | OceanBaseConfig, *, evidence_directory: Path | None = None, lock_coordination: Literal["single-host"] | None = None, @@ -73,6 +77,8 @@ def __init__( self.acknowledge_previous_run = acknowledge_previous_run def coordination(self, identity: BackendIdentity) -> dict[str, str]: + if isinstance(self.config, SQLiteConfig): + return {"mode": "database-file"} if isinstance(self.config, SeekDBConfig): return {"mode": "engine-directory", "directory": str(self.config.path.expanduser().resolve())} coordination = { @@ -92,6 +98,8 @@ def run( *, writable: bool = False, ) -> _T: + if isinstance(self.config, SQLiteConfig): + return self._sqlite(operation, self.config, writable=writable) if isinstance(self.config, SeekDBConfig): path = self.config.path.expanduser().resolve() identity = BackendIdentity( @@ -115,6 +123,45 @@ def run( return asyncio.run(self._seekdb(operation, self.config, path, identity, initial_inode)) return asyncio.run(self._oceanbase(operation, self.config, writable=writable)) + @staticmethod + def _sqlite( + operation: Callable[[Connection | None, BackendIdentity, Callable[[], None]], _T], + config: SQLiteConfig, + *, + writable: bool, + ) -> _T: + url = make_url(config.url) + if ( + not config.is_persistent + or url.query + or not url.database + or url.username + or url.password + or url.host + or url.port + ): + raise MigrationError("unsupported_target", "Maintenance requires a regular persistent SQLite file URL.") + target = Path(url.database).absolute().resolve() + identity = BackendIdentity("sqlite", digest({"kind": "sqlite", "path": str(target)}), target.name) + if not target.exists(): + return operation(None, identity, lambda: None) + if not target.is_file() or target.stat().st_nlink != 1: + raise MigrationError("unsupported_target", "Use a regular SQLite file without hard-link aliases.") + with local_migration_lock(target) if writable else nullcontext(): + uri = target.as_uri() + ("?mode=rw" if writable else "?mode=ro") + engine = create_engine( + "sqlite://", creator=lambda: sqlite3.connect(uri, uri=True, isolation_level=None, timeout=0) + ) + try: + with engine.connect() as connection: + connection.exec_driver_sql("PRAGMA foreign_keys=ON") + if not writable: + connection.exec_driver_sql("PRAGMA query_only=ON") + connection.exec_driver_sql("BEGIN IMMEDIATE" if writable else "BEGIN") + return operation(connection, identity, lambda: None) + finally: + engine.dispose() + async def _seekdb( self, operation: Callable[[Connection | None, BackendIdentity, Callable[[], None]], _T], diff --git a/src/powercontext/builtin/persistence/migrations/deployment.py b/src/powercontext/builtin/persistence/migrations/deployment.py index 86977dfaa4..9c194fa86a 100644 --- a/src/powercontext/builtin/persistence/migrations/deployment.py +++ b/src/powercontext/builtin/persistence/migrations/deployment.py @@ -18,18 +18,23 @@ import os from pathlib import Path -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Any, Literal from sqlalchemy.engine import make_url +from powercontext.builtin.artifacts.memory import EmbeddingProfile from powercontext.builtin.persistence.oceanbase import OceanBaseConfig from powercontext.builtin.persistence.seekdb import SeekDBConfig from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.runtime.config import BuiltinConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest +from .atomic_projection import AtomicProjectionMigrationRunner from .bundle import MigrationBundle from .connections import MaintenanceConnections from .models import MigrationError from .mysql import MySQLMigrationRunner +from .processing_configuration import ProcessingConfigurationMigrationRunner from .sqlite import SQLiteMigrationRunner from .topic_memory_recovery import TopicMemoryRecoveryMigrationRunner @@ -37,24 +42,90 @@ from powercontext.server.settings import ServerSettings +def server_bundle(*, embedding_profile: EmbeddingProfile | None = None) -> MigrationBundle: + """The complete, shared Server revision chain.""" + return MigrationBundle(Path(__file__).parent / "server_resources", embedding_profile=embedding_profile) + + def production_bundle() -> MigrationBundle: """Load installed resources; readiness is scoped to this acceptance bundle.""" return MigrationBundle(Path(__file__).parent / "resources") -MigrationRunner = SQLiteMigrationRunner | MySQLMigrationRunner | TopicMemoryRecoveryMigrationRunner +MigrationRunner = ( + SQLiteMigrationRunner + | MySQLMigrationRunner + | TopicMemoryRecoveryMigrationRunner + | ProcessingConfigurationMigrationRunner + | AtomicProjectionMigrationRunner +) + + +def _migration_configuration(settings: ServerSettings) -> dict[str, Any]: + config = BuiltinConfig( + code=settings.code, + runtime=settings.runtime, + database=settings.database, + handoff_report=settings.handoff_report, + inference=settings.inference, + external_skills=settings.external_skills, + ) + return { + "database": settings.database.model_dump(mode="json", exclude={"url", "echo"}), + "processing_manifest": canonical_processing_manifest(config), + } + + +def configured_embedding_profile(settings: ServerSettings) -> EmbeddingProfile | None: + inference = settings.inference + if inference.embedding_model is None: + return None + if inference.embedding_profile_id is None or inference.embedding_dimension is None: + raise MigrationError( + "embedding_profile_mismatch", "Embedding configuration needs its exact profile and dimension." + ) + return EmbeddingProfile( + profile_id=inference.embedding_profile_id, + model=inference.embedding_model, + dimension=inference.embedding_dimension, + normalization=inference.embedding_normalization, + ) -def deployment_runner( +def deployment_runner( # noqa: C901 -- Ordered backend admission checks. settings: ServerSettings, *, evidence_dir: Path | None = None, lock_coordination: Literal["single-host"] | None = None, acknowledge_previous_run: str | None = None, - bundle: Literal["artifacts", "topic-memory-recovery"] = "artifacts", + bundle: Literal["server", "artifacts", "topic-memory-recovery", "processing", "atomic-projection"] = "server", + migration_id: str = "rfc1515", + batch_size: int = 100, + embedding_profile: EmbeddingProfile | None = None, ) -> MigrationRunner: """Use the real configured identity without opening a business profile.""" database = settings.database + configuration = _migration_configuration(settings) + profile = embedding_profile or configured_embedding_profile(settings) + if bundle in {"server", "processing", "atomic-projection"}: + if profile is not None and bundle != "server": + raise MigrationError( + "unsupported_upgrade_configuration", + "This partial maintenance bundle has no registered vector configuration path.", + ) + configuration["embedding_enabled"] = profile is not None + if profile is not None: + configuration["embedding_profile"] = profile.model_dump(mode="json") + if settings.code.enabled and isinstance(database, SeekDBConfig): + configuration["embedded_code_enabled"] = True + selected = ( + server_bundle(embedding_profile=profile) + if bundle in {"server", "processing", "atomic-projection"} + else production_bundle() + ) + maintenance_runner = ( + AtomicProjectionMigrationRunner if bundle == "atomic-projection" else ProcessingConfigurationMigrationRunner + ) if bundle == "topic-memory-recovery": if not isinstance(database, (SQLiteConfig, SeekDBConfig, OceanBaseConfig)): raise MigrationError("unsupported_backend", "The database has no Topic recovery migration adapter.") @@ -80,16 +151,26 @@ def deployment_runner( "coordination_required", "OceanBase requires --lock-coordination single-host; run all migrators on one fixed host and evidence directory.", ) - return MySQLMigrationRunner( - MaintenanceConnections( - database, + connections = MaintenanceConnections( + database, + evidence_directory=evidence_dir, + lock_coordination=lock_coordination, + acknowledge_previous_run=acknowledge_previous_run, + ) + if bundle in {"processing", "atomic-projection"}: + return maintenance_runner( + connections, + selected, + configuration=configuration, evidence_directory=evidence_dir, - lock_coordination=lock_coordination, - acknowledge_previous_run=acknowledge_previous_run, - ), - production_bundle(), + migration_id=migration_id, + batch_size=batch_size, + ) + return MySQLMigrationRunner( + connections, + selected, evidence_directory=evidence_dir, - configuration=database.model_dump(mode="json", exclude={"url", "echo"}), + configuration=configuration, ) if not isinstance(database, SQLiteConfig): raise MigrationError("unsupported_backend", "The database has no registered migration adapter.") @@ -100,9 +181,19 @@ def deployment_runner( ) # Match SQLite's URL normalization before the runner expands standalone Path inputs. target = Path(os.path.abspath(url.database)) + if bundle in {"processing", "atomic-projection"}: + directory = evidence_dir or target.with_name(target.name + ".pc-migration-state") + return maintenance_runner( + MaintenanceConnections(database), + selected, + configuration=configuration, + evidence_directory=directory, + migration_id=migration_id, + batch_size=batch_size, + ) return SQLiteMigrationRunner( target, - production_bundle(), - configuration=database.model_dump(exclude={"url", "echo"}), + selected, + configuration=configuration, evidence_directory=evidence_dir, ) diff --git a/src/powercontext/builtin/persistence/migrations/domains.py b/src/powercontext/builtin/persistence/migrations/domains.py new file mode 100644 index 0000000000..01dc9f2d28 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/domains.py @@ -0,0 +1,209 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Frozen domain transformations attached to the shared schema revision chain.""" + +from __future__ import annotations + +import copy +import runpy +from collections.abc import Callable, Mapping +from pathlib import Path +from typing import Any, Literal + +from pydantic import BaseModel, ConfigDict, Field, ValidationError +from sqlalchemy import Connection + +from .models import MigrationError + +Backend = Literal["sqlite", "seekdb", "oceanbase"] + + +class DomainStep(BaseModel): + """Run after this revision's schema expansion and before advancing its version.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + id: str = Field(pattern=r"^[a-z][a-z0-9_]*$") + revision: str + kind: Literal["data", "projection"] + module: str + resources: tuple[str, ...] = () + depends_on: tuple[str, ...] = () + backends: tuple[Backend, ...] = Field(min_length=1) + + +class _Registrations(BaseModel): + domain_steps: tuple[DomainStep, ...] = () + domain_readiness: dict[str, tuple[str, ...]] = Field(default_factory=dict) + + +class DomainRegistry: + """Plan and execute domain-owned steps without a separate task journal. + + Modules export preflight/apply/verify/validate(connection, *, configuration). + preflight and validate are read-only. verify checks migration-copy invariants; + validate checks live invariants and must not compare mutable data to archives. + apply must be idempotent and must not commit, change schema, or open connections. + The backend runner owns transactions, locking, recovery and version advancement. + """ + + def __init__( + self, + manifest: Mapping[str, Any], + revisions: tuple[str, ...], + directory: Path, + resources: Mapping[str, str], + ) -> None: + self.revisions = revisions + self.directory = directory + try: + registered = _Registrations.model_validate(manifest) + except ValidationError as error: + raise MigrationError("invalid_bundle", "Domain registration has an invalid shape.") from error + steps = registered.domain_steps + self.readiness = registered.domain_readiness + self._by_id = {step.id: step for step in steps} + self._validate_steps(steps, resources) + self.steps = self._ordered(steps) + self._validate_readiness() + self._modules: dict[str, dict[str, Any]] = {} + for step in self.steps: + if step.module not in self._modules: + module = runpy.run_path(str(self.directory / step.module)) + if not all(callable(module.get(name)) for name in ("preflight", "apply", "verify", "validate")): + raise MigrationError( + "invalid_bundle", "Domain modules must implement all four maintenance operations." + ) + self._modules[step.module] = module + + def _validate_steps(self, steps: tuple[DomainStep, ...], resources: Mapping[str, str]) -> None: + if len(self._by_id) != len(steps): + raise MigrationError("invalid_bundle", "Domain step identifiers must be unique.") + for step in steps: + if step.revision not in self.revisions: + raise MigrationError("invalid_bundle", "A domain step requires an unknown schema revision.") + if not step.module.endswith(".py") or any(name not in resources for name in (step.module, *step.resources)): + raise MigrationError("invalid_bundle", "Domain resources must be frozen inside the migration bundle.") + for dependency in step.depends_on: + required = self._by_id.get(dependency) + if required is None or self.revisions.index(required.revision) > self.revisions.index(step.revision): + raise MigrationError("invalid_bundle", "A domain dependency is missing or requires a later schema.") + if not set(step.backends) <= set(required.backends): + raise MigrationError("invalid_bundle", "A domain dependency is unavailable on a declared backend.") + + def _ordered(self, steps: tuple[DomainStep, ...]) -> tuple[DomainStep, ...]: + ordered: list[DomainStep] = [] + done: set[str] = set() + for revision in self.revisions: + remaining = [step for step in steps if step.revision == revision] + while remaining: + available = next((step for step in remaining if set(step.depends_on) <= done), None) + if available is None: + raise MigrationError("invalid_bundle", "Domain dependencies contain a cycle.") + ordered.append(available) + done.add(available.id) + remaining.remove(available) + return tuple(ordered) + + def _validate_readiness(self) -> None: + if self.steps: + first = min(self.revisions.index(step.revision) for step in self.steps) + if not set(self.revisions[first:]) <= self.readiness.keys(): + raise MigrationError("invalid_bundle", "Every domain-managed revision needs explicit live validators.") + for revision, ids in self.readiness.items(): + if revision not in self.revisions or len(ids) != len(set(ids)): + raise MigrationError("invalid_bundle", "A live validation registration is invalid.") + for step_id in ids: + step = self._by_id.get(step_id) + if step is None or self.revisions.index(step.revision) > self.revisions.index(revision): + raise MigrationError("invalid_bundle", "A live validator requires a missing or future domain step.") + + def checksum_inputs(self, revision: str, resources: Mapping[str, str]) -> dict[str, Any]: + steps = [step for step in self.steps if step.revision == revision] + if not steps and revision not in self.readiness: + return {} + ids = self.readiness.get(revision, ()) + used = [*steps, *(self._by_id[step_id] for step_id in ids)] + return { + "steps": [step.model_dump(mode="json") for step in steps], + "readiness": ids, + "resources": {name: resources[name] for step in used for name in (step.module, *step.resources)}, + } + + def pending(self, revision: str | None, backend: Backend) -> tuple[DomainStep, ...]: + if revision is not None and revision not in self.revisions: + raise MigrationError("incompatible_schema", "Database revision is unknown to this bundle.") + position = -1 if revision is None else self.revisions.index(revision) + pending = tuple(step for step in self.steps if self.revisions.index(step.revision) > position) + for step in pending: + self._require_backend(step, backend) + return pending + + def preflight( + self, connection: Connection, revision: str | None, *, backend: Backend, configuration: Mapping[str, Any] + ) -> None: + for step in self.pending(revision, backend): + self._invoke(step, "preflight", connection, configuration) + + def apply( + self, + connection: Connection, + revision: str, + *, + backend: Backend, + configuration: Mapping[str, Any], + guard: Callable[[], None] = lambda: None, + ) -> None: + for step in self.steps: + if step.revision == revision: + self._require_backend(step, backend) + guard() + self._invoke(step, "preflight", connection, configuration) + guard() + self._invoke(step, "apply", connection, configuration) + guard() + self._invoke(step, "verify", connection, configuration) + guard() + + def validate( + self, connection: Connection, revision: str | None, *, backend: Backend, configuration: Mapping[str, Any] + ) -> None: + if revision is None: + return + for step_id in self.readiness.get(revision, ()): + step = self._by_id[step_id] + self._require_backend(step, backend) + self._invoke(step, "validate", connection, configuration) + + @staticmethod + def _require_backend(step: DomainStep, backend: Backend) -> None: + if backend not in step.backends: + raise MigrationError( + "unsupported_upgrade_path", "A required domain step has no registered backend support." + ) + + def _invoke( + self, step: DomainStep, operation: str, connection: Connection, configuration: Mapping[str, Any] + ) -> None: + module = self._modules[step.module] + try: + module[operation](connection, configuration=copy.deepcopy(dict(configuration))) + except MigrationError: + raise + except Exception as error: + raise MigrationError( + "domain_validation_failed", + f"Domain {step.id} failed during {operation}; stored data requires maintenance.", + ) from error diff --git a/src/powercontext/builtin/persistence/migrations/maintenance.py b/src/powercontext/builtin/persistence/migrations/maintenance.py new file mode 100644 index 0000000000..d21b3d243c --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/maintenance.py @@ -0,0 +1,276 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Shared locked, backed-up transactions for registered Server maintenance.""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from typing import Any, Literal +from uuid import uuid4 + +from sqlalchemy import Connection +from sqlalchemy.engine import make_url + +from powercontext.builtin.persistence.sqlite import SQLiteConfig + +from .backup import BackupContext, ForkBackupProvider, SQLiteBackupProvider +from .bundle import MigrationBundle +from .connections import BackendIdentity, MaintenanceConnections +from .models import MigrationError, MigrationPlan, MigrationResult, digest +from .mysql import _Evidence, _Journal +from .server_readiness import check_server_schema +from .sqlite import SQLiteMigrationRunner + + +class ManagedMaintenanceRunner: + """Own the common transaction, recovery point and external maintenance journal.""" + + operation: str + affected_objects: tuple[str, ...] + + def __init__( + self, + connections: MaintenanceConnections, + bundle: MigrationBundle, + *, + configuration: dict[str, Any], + evidence_directory: Path, + implementation_checksum: str, + migration_id: str = "rfc1515", + batch_size: int = 100, + ) -> None: + self.connections = connections + self.bundle = bundle + self.configuration = json.loads(json.dumps(configuration)) + self.directory = evidence_directory.expanduser().resolve() + self.migration_id = migration_id + self.batch_size = batch_size + self.configuration_digest = digest({"configuration": configuration, "migration_id": migration_id}) + self.checksum = digest({ + "bundle": bundle.checksum, + "operation": self.operation, + "lifecycle": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "implementation": implementation_checksum, + }) + + def _journal(self, identity: BackendIdentity) -> _Journal: + return _Journal(self.directory / self.operation, identity) + + def _inspect(self, connection: Connection | None, identity: BackendIdentity, **options) -> MigrationPlan: + self.bundle.assert_unchanged() + if connection is None: + raise MigrationError("migration_required", "Initialize or upgrade the full Server bundle first.") + self._check_previous_maintenance(identity) + fingerprint, required, completed = self._state(connection, identity) + evidence = self._journal(identity).read() + active = evidence is not None and evidence.state != "complete" + if active and ( + evidence.bundle_checksum != self.checksum + or evidence.configuration_digest != self.configuration_digest + or evidence.source_revision != self.bundle.head + ): + raise MigrationError( + "recovery_required", "Resume with the original registered maintenance operation and configuration." + ) + policy = options.get("backup_policy", "auto") + if policy not in {"auto", "manual", "skip"}: + raise MigrationError("invalid_backup_policy", "Choose auto, manual or skip.") + if active and ( + evidence.backup_policy != policy + or (fingerprint != evidence.source_fingerprint and (required or not completed)) + ): + raise MigrationError( + "recovery_required", "Keep the original registered maintenance recovery point and backup policy." + ) + available = isinstance(self.connections.config, SQLiteConfig) + coordination = self.connections.coordination(identity) + recovering = active or "pending_run_id" in coordination + fields = { + "database_id": identity.database_id, + "source_revision": self.bundle.head, + "target_revision": self.bundle.head, + "schema_fingerprint": fingerprint, + "bundle_checksum": self.checksum, + "configuration_digest": self.configuration_digest, + "state": "recovery_required" if recovering else "migration_required" if required else "ready", + "revisions": (), + "domain_steps": (self.operation,) if required or active else (), + "domain_validators": ("server_upgrade",), + "backup_policy": policy, + "backup_available": available, + "backup_reason": () + if available + else ("Native Fork recovery acceptance is unavailable; choose manual or skip.",), + "affected_objects": self.affected_objects, + "shared_database": options.get("shared_database", False), + "service": options.get("service"), + "coordination": coordination, + "readiness_scope": "server", + } + return MigrationPlan.model_validate({"plan_id": digest(fields), **fields}) + + def plan(self, **options) -> MigrationPlan: + return self.connections.run(lambda connection, identity, guard: self._inspect(connection, identity, **options)) + + def verify(self) -> MigrationResult: + plan = self.plan() + if plan.state != "ready": + raise MigrationError(plan.state, "Apply the registered maintenance operation first.") + return MigrationResult(revision=self.bundle.head, changed=False, readiness_scope="server") + + def _provider(self, connection: Connection, identity: BackendIdentity): + config = self.connections.config + if isinstance(config, SQLiteConfig): + return SQLiteBackupProvider(Path(str(make_url(config.url).database))) + return ForkBackupProvider( + connection, + database_name=identity.database_name, + expected_product="seekdb" if identity.product == "seekdb" else "oceanbase_ai", + directory=self.directory, + ) + + def apply( # noqa: C901 -- One transaction owns backup, domain maintenance and recovery evidence. + self, + *, + plan_id: str | None = None, + accepted: bool = False, + maintenance_confirmed: bool = False, + backup_policy: Literal["auto", "manual", "skip"] = "auto", + backup_confirmed: bool = False, + accept_no_backup: bool = False, + backup_ref: str | None = None, + **options, + ) -> MigrationResult: + initial = self.plan(backup_policy=backup_policy, **options) + pending = (initial.coordination or {}).get("pending_run_id") + acknowledged = self.connections.acknowledge_previous_run + if pending is not None and pending != acknowledged: + raise MigrationError("recovery_required", "Acknowledge the exact previous remote maintenance run.") + if pending is None and acknowledged is not None: + raise MigrationError("stale_ack", "There is no pending run matching this acknowledgment.") + if initial.state == "ready": + return self.verify() + if options.get("service") is not None: + raise MigrationError("service_unsupported", "Use full Server maintenance to manage a local service.") + SQLiteMigrationRunner._require_acceptance( + initial, plan_id, accepted, maintenance_confirmed, backup_confirmed, accept_no_backup + ) + + def execute(connection, identity, guard): + current = self._inspect(connection, identity, backup_policy=backup_policy, **options) + if current != initial: + raise MigrationError("plan_changed", "Maintenance state changed while acquiring the shared lock.") + if not current.domain_steps: + guard() + return MigrationResult(revision=self.bundle.head, changed=False, readiness_scope="server") + journal = self._journal(identity) + evidence = journal.read() + if evidence is None or evidence.state == "complete": + evidence = _Evidence( + state="preparing", + database_id=identity.database_id, + source_revision=self.bundle.head, + source_fingerprint=initial.schema_fingerprint, + bundle_checksum=self.checksum, + configuration_digest=self.configuration_digest, + last_revision=self.bundle.head, + maintenance_window_id=uuid4().hex, + backup_policy=backup_policy, + backup_state="pending", + ) + journal.write(evidence) + context = BackupContext( + database_id=identity.database_id, + source_revision=self.bundle.head, + bundle_checksum=self.checksum, + maintenance_window_id=evidence.maintenance_window_id, + objects=self.bundle.affected_objects, + writers_stopped=True, + ) + provider = self._provider(connection, identity) + # Backup must see committed state. The shared OS/engine lock remains held. + connection.commit() + if evidence.state == "preparing": + if backup_policy == "auto": + evidence.backup = provider.create_backup(context) + evidence.backup_state = "completed" + else: + evidence.backup_state = "user_confirmed" if backup_policy == "manual" else "skipped" + evidence.manual_ref = backup_ref + evidence.state = "active" + journal.write(evidence) + if evidence.backup is not None and provider.inspect_backup(evidence.backup).state != "completed": + raise MigrationError( + "backup_failed", "The original registered maintenance recovery point is unavailable." + ) + guard() + connection.exec_driver_sql( + "BEGIN IMMEDIATE" if connection.dialect.name == "sqlite" else "START TRANSACTION" + ) + locked = self._inspect(connection, identity, backup_policy=backup_policy, **options) + if locked.schema_fingerprint != initial.schema_fingerprint: + raise MigrationError("plan_changed", "Maintenance state changed before the write transaction.") + self._apply_operation(connection, guard) + guard() + check_server_schema(connection) + self.bundle.domains.validate( + connection, self.bundle.head, backend=identity.product, configuration=self.configuration + ) + connection.commit() + evidence.state = "complete" + journal.write(evidence) + backup_state = evidence.backup_state + if backup_state == "pending": + raise MigrationError("backup_failed", "The registered maintenance recovery point has not completed.") + return MigrationResult( + revision=self.bundle.head, + changed=True, + readiness_scope="server", + backup_state=backup_state, + backup_ref=evidence.backup.location if evidence.backup else evidence.manual_ref, + ) + + return self.connections.run(execute, writable=True) + + def _state(self, connection: Connection, identity: BackendIdentity) -> tuple[str, bool, bool]: + raise NotImplementedError + + def _apply_operation(self, connection: Connection, guard) -> None: + raise NotImplementedError + + def _check_previous_maintenance(self, identity: BackendIdentity) -> None: + for operation in ("processing_configuration", "atomic_projection"): + if operation != self.operation: + previous = _Journal(self.directory / operation, identity).read() + if previous is not None and previous.state != "complete": + raise MigrationError( + "recovery_required", "Complete the previous registered maintenance operation first." + ) + main_path = ( + self.directory / "maintenance.json" + if isinstance(self.connections.config, SQLiteConfig) + else self.directory / identity.database_id / "maintenance.json" + ) + if main_path.exists(): + try: + main = json.loads(main_path.read_text(encoding="utf-8")) + except (OSError, ValueError) as error: + raise MigrationError( + "recovery_required", "The original full Server evidence cannot be read." + ) from error + if not isinstance(main, dict) or main.get("state") != "complete": + raise MigrationError("recovery_required", "Complete the original full Server maintenance first.") diff --git a/src/powercontext/builtin/persistence/migrations/models.py b/src/powercontext/builtin/persistence/migrations/models.py index abbbd7af57..3f4e5f00a1 100644 --- a/src/powercontext/builtin/persistence/migrations/models.py +++ b/src/powercontext/builtin/persistence/migrations/models.py @@ -48,6 +48,8 @@ class MigrationPlan(BaseModel): configuration_digest: str state: Literal["uninitialized", "migration_required", "ready", "recovery_required"] revisions: tuple[str, ...] + domain_steps: tuple[str, ...] = () + domain_validators: tuple[str, ...] = () adopt_baseline: bool = False backup_policy: Literal["auto", "manual", "skip"] = "auto" backup_available: bool = True @@ -58,7 +60,7 @@ class MigrationPlan(BaseModel): coordination: dict[str, str] | None = None service: dict[str, Any] | None = None execution_mode: Literal["offline"] = "offline" - readiness_scope: Literal["registered_bundle"] = "registered_bundle" + readiness_scope: Literal["registered_bundle", "server"] = "registered_bundle" class MigrationResult(BaseModel): @@ -69,4 +71,4 @@ class MigrationResult(BaseModel): changed: bool backup_ref: str | None = None backup_state: Literal["completed", "user_confirmed", "skipped", "not_required"] = "not_required" - readiness_scope: Literal["registered_bundle"] = "registered_bundle" + readiness_scope: Literal["registered_bundle", "server"] = "registered_bundle" diff --git a/src/powercontext/builtin/persistence/migrations/mysql.py b/src/powercontext/builtin/persistence/migrations/mysql.py index abfccff3a6..35c7728f18 100644 --- a/src/powercontext/builtin/persistence/migrations/mysql.py +++ b/src/powercontext/builtin/persistence/migrations/mysql.py @@ -17,6 +17,7 @@ from __future__ import annotations import hashlib +import json import os import socket from collections.abc import Callable, Mapping @@ -27,6 +28,9 @@ from alembic import command from alembic.config import Config +from alembic.script import ScriptDirectory +from alembic.script.revision import RevisionError +from alembic.util.exc import CommandError from pydantic import BaseModel, ConfigDict, model_validator from sqlalchemy import Connection @@ -35,6 +39,7 @@ from .connections import BackendIdentity, MaintenanceConnections from .models import MigrationError, MigrationPlan, MigrationResult, digest from .mysql_schema import MySQLSchema, mysql_inventory, read_revision +from .server_baselines import recognize_mysql_server_baseline BackupPolicy = Literal["auto", "manual", "skip"] BackupFactory = Callable[[Connection, BackendIdentity, Path], BackupProvider] @@ -150,9 +155,13 @@ def __init__( ) -> None: self.connections = connections self.bundle = bundle - self.schema = MySQLSchema(bundle) self.evidence_directory = evidence_directory.expanduser().resolve() - self.configuration_digest = digest(configuration or {}) + self.configuration = json.loads(json.dumps(configuration or {})) + embedded_code = self.configuration.get("embedded_code_enabled", False) + if embedded_code and connections.config.kind != "seekdb": + raise MigrationError("unsupported_upgrade_configuration", "Embedded Code storage requires seekDB.") + self.schema = MySQLSchema(bundle, embedded_code=embedded_code) + self.configuration_digest = digest(self.configuration) if connections.config.kind == "oceanbase": if ( connections.lock_coordination != "single-host" @@ -163,34 +172,62 @@ def __init__( "Use single-host coordination and the same evidence directory for this runner.", ) self.configuration_digest = digest({ - "configuration": configuration or {}, + "configuration": self.configuration, "coordination": "single-host", "host": socket.gethostname(), "evidence_directory": str(self.evidence_directory), }) self.backup_factory = backup_factory or _fork_provider - self._history_directory = Path(__file__).parent / "mysql_resources" + self._history_directory = ( + bundle.directory / "mysql" + if bundle.readiness_scope == "server" + else Path(__file__).parent / "mysql_resources" + ) resources = { path.relative_to(self._history_directory).as_posix(): path.read_bytes() for path in sorted(self._history_directory.rglob("*.py")) } - self._history_bytes = resources + self._history_bytes = resources.copy() + for revision, resource in bundle.mysql_wrappers.items(): + if any(name.startswith(f"versions/{revision}_") for name in resources): + raise MigrationError("invalid_bundle", "A registered MySQL wrapper overrides historical execution.") + # Prefix the filename with its revision to keep the existing + # per-revision checksum contract, including contributed wrappers. + resources[f"versions/{revision}_registered.py"] = (bundle.snapshot_directory / resource).read_bytes() self._snapshot = TemporaryDirectory(prefix="pc-mysql-migrations-") for name, content in resources.items(): target = Path(self._snapshot.name) / name target.parent.mkdir(parents=True, exist_ok=True) target.write_bytes(content) + self._validate_history() hashes = {name: hashlib.sha256(content).hexdigest() for name, content in resources.items()} self.checksum = digest({"bundle": bundle.checksum, "mysql_history": hashes}) self.checksums = { revision: digest({ "revision": checksum, "env": hashes["env.py"], - "wrapper": {name: value for name, value in hashes.items() if name.startswith(f"versions/{revision}_")}, + "wrapper": { + name: value + for name, value in hashes.items() + if name.startswith(f"versions/{revision}_") or name == f"versions/{revision}.py" + }, }) for revision, checksum in bundle.checksums.items() } + def _validate_history(self) -> None: + try: + history = list(reversed(list(ScriptDirectory(self._snapshot.name).walk_revisions()))) + except (CommandError, RevisionError, KeyError) as error: + raise MigrationError("invalid_bundle", "MySQL execution history is incomplete or ambiguous.") from error + previous = None + if tuple(item.revision for item in history) != self.bundle.revisions: + raise MigrationError("invalid_bundle", "MySQL execution history does not cover the registered chain.") + for item in history: + if item.down_revision != previous or item.dependencies: + raise MigrationError("invalid_bundle", "MySQL execution history differs from the registered chain.") + previous = item.revision + def _assert_unchanged(self) -> None: self.bundle.assert_unchanged() resources = { @@ -225,7 +262,9 @@ def verify( raise MigrationError( plan.state, "Inspect and explicitly apply the registered bundle before verification." ) - return MigrationResult(revision=self.bundle.head, changed=False) + return MigrationResult( + revision=self.bundle.head, changed=False, readiness_scope=self.bundle.readiness_scope + ) self._assert_unchanged() return self.connections.run(verify) @@ -259,7 +298,9 @@ def apply( if previous_run is None and acknowledgement is not None: raise MigrationError("stale_ack", "There is no pending run matching this acknowledgement.") if initial.state == "ready": - return MigrationResult(revision=self.bundle.head, changed=False) + return MigrationResult( + revision=self.bundle.head, changed=False, readiness_scope=self.bundle.readiness_scope + ) self._check_consent( initial, plan_id=plan_id, @@ -334,13 +375,19 @@ def _apply_locked( # interrupted connection close. Acknowledgement still requires # the lock and full schema/data reinspection, but no new backup. guard() - return MigrationResult(revision=self.bundle.head, changed=False) + return MigrationResult( + revision=self.bundle.head, changed=False, readiness_scope=self.bundle.readiness_scope + ) provider = self.backup_factory(connection, identity, journal.path.parent / "backups") evidence = self._prepare_evidence(initial, journal, provider, manual_ref=manual_ref) guard() self._assert_unchanged() if self.schema.fingerprint(mysql_inventory(connection)) != initial.schema_fingerprint: raise MigrationError("plan_changed", "The source schema changed during backup.") + if not self._initializing(initial.source_revision, evidence): + self.bundle.domains.preflight( + connection, initial.source_revision, backend=identity.product, configuration=self.configuration + ) config = self._config(connection) if initial.adopt_baseline: self._adopt(connection, config, journal, evidence, initial.source_revision, guard) @@ -359,6 +406,9 @@ def _apply_locked( current = target self.schema.verify(mysql_inventory(connection), self.bundle.head) self.schema.validate_data(connection, self.bundle.head) + self.bundle.domains.validate( + connection, self.bundle.head, backend=identity.product, configuration=self.configuration + ) guard() evidence.state = "complete" evidence.last_revision = self.bundle.head @@ -371,6 +421,7 @@ def _apply_locked( return MigrationResult( revision=self.bundle.head, changed=True, + readiness_scope=self.bundle.readiness_scope, backup_state=evidence.backup_state, backup_ref=evidence.backup.location if evidence.backup else evidence.manual_ref, ) @@ -416,12 +467,22 @@ def upgrade(revision: str) -> None: raise MigrationError("recovery_required", "The stored intent does not match the schema transition.") for statement in recovery.pending_statements: guard() - connection.exec_driver_sql(statement) + self.schema.execute(connection, statement, self.configuration) # DDL may already have committed; rollback cannot undo it. guard() self.schema.recovery(mysql_inventory(connection), source) self.schema.verify(mysql_inventory(connection), revision) + self.bundle.domains.apply( + connection, + revision, + backend=self.connections.config.kind, + configuration=self.configuration, + guard=guard, + ) self.schema.validate_data(connection, revision) + self.bundle.domains.validate( + connection, revision, backend=self.connections.config.kind, configuration=self.configuration + ) guard() config.attributes["execute_revision"] = upgrade @@ -440,7 +501,7 @@ def _config(self, connection: Connection) -> Config: config.attributes["connection"] = connection return config - def _inspect( + def _inspect( # noqa: C901 -- Keep ordered admission checks with their operation. self, connection: Connection | None, identity: BackendIdentity, @@ -456,14 +517,42 @@ def _inspect( inventory = ( mysql_inventory(connection) if connection is not None else {"tables": {}, "views": [], "triggers": []} ) - initialized, current = read_revision(connection, inventory) if connection is not None else (False, None) + initialized, current = ( + read_revision(connection, inventory, revisions=self.bundle.revisions) + if connection is not None + else (False, None) + ) self._validate_evidence(evidence) + backend = identity.product + if ( + self.bundle.readiness_scope != "server" + and not initialized + and (backend == "seekdb" or backend == "oceanbase") + ): + source = recognize_mysql_server_baseline(inventory, backend=backend) + if source is not None: + raise MigrationError( + "server_migration_unavailable", + f"The complete {source.release} Server schema is recognized, but its full upgrade is unavailable.", + ) current, adopt = self._recognize_source(inventory, initialized, current, evidence) if initialized and self.bundle.pending(current) and evidence is None: raise MigrationError("recovery_required", "The original external maintenance evidence is missing.") self._validate_position(evidence, current) if connection is not None and current is not None: self.schema.validate_data(connection, current) + if evidence is None or evidence.pending_revision is None: + self.bundle.domains.validate( + connection, current, backend=identity.product, configuration=self.configuration + ) + domain_steps = self.bundle.domains.pending(current, identity.product) + # A recognized first-revision DDL prefix may not contain its seed data + # yet. The revision executor checks each domain step after its registered + # schema completes, before domain work or that revision's advancement. + if connection is not None and not self._initializing(current, evidence): + self.bundle.domains.preflight( + connection, current, backend=identity.product, configuration=self.configuration + ) fingerprint = self.schema.fingerprint(inventory) state = "migration_required" if self.bundle.pending(current) or adopt else "ready" if not initialized and current is None: @@ -492,23 +581,34 @@ def _inspect( "database_id": identity.database_id, "source_revision": current, "target_revision": self.bundle.head, + "readiness_scope": self.bundle.readiness_scope, "schema_fingerprint": fingerprint, "bundle_checksum": self.checksum, "configuration_digest": self.configuration_digest, "state": state, "revisions": self.bundle.pending(current), + "domain_steps": tuple(step.id for step in domain_steps), + "domain_validators": self.bundle.domains.readiness.get(self.bundle.head, ()), "adopt_baseline": adopt, "backup_policy": backup_policy, "backup_available": available, "backup_reason": reasons, "affected_objects": self.bundle.affected_objects, - "retained_objects": (), + "retained_objects": self.bundle.retained_objects, "service": service, "shared_database": shared_database, "coordination": coordination, } return MigrationPlan.model_validate({"plan_id": digest(fields), **fields}) + def _initializing(self, current: str | None, evidence: _Evidence | None) -> bool: + return ( + current is None + and evidence is not None + and evidence.state == "active" + and evidence.pending_revision == self.bundle.revisions[0] + ) + def _validate_evidence(self, evidence: _Evidence | None) -> None: if evidence is None: return @@ -608,7 +708,7 @@ def _backup_context(self, database_id: str, revision: str | None, window: str) - source_revision=revision, bundle_checksum=self.checksum, maintenance_window_id=window, - objects=self.bundle.affected_objects, + objects=self.schema.affected_objects, writers_stopped=True, ) diff --git a/src/powercontext/builtin/persistence/migrations/mysql_schema.py b/src/powercontext/builtin/persistence/migrations/mysql_schema.py index 95c5792974..e6b52dfeb3 100644 --- a/src/powercontext/builtin/persistence/migrations/mysql_schema.py +++ b/src/powercontext/builtin/persistence/migrations/mysql_schema.py @@ -14,7 +14,7 @@ """Recognize the frozen MySQL layouts without importing application metadata. -Only the reviewed linear p0001--p0004 history is supported. DDL recovery accepts +Registered resources describe the complete linear history. DDL recovery accepts an exact prefix of the next revision, never an arbitrary collection of objects. The caller owns maintenance evidence, backup, the pinned database lock and the version row; this module never writes or stamps a revision. @@ -37,20 +37,7 @@ _LEXEME = re.compile( r"\s+|`(?:``|[^`])*`|'(?:''|\\.|[^'\\])*'|[A-Za-z_][A-Za-z_0-9$]*|[0-9]+|>=|<=|<>|!=|[(),.=<>+*/%\^&|~!?:@-]" ) -_REVISIONS = ("p0001", "p0002", "p0003", "p0004") -_CITATION_TABLES = ("pc_artifacts", "pc_artifact_candidate_versions") -_FAMILY_CHECK = "ck_pc_artifact_tags_family" _CONTROL_TABLE = "pc_schema_revision" -_ARTIFACT_HANDLES_DDL = """CREATE TABLE pc_artifact_handles ( - scope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - handle_key VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - revision INTEGER NOT NULL, - PRIMARY KEY (scope_id, family, handle_key), - FOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, - CONSTRAINT ck_pc_artifact_handles_revision_positive CHECK (revision > 0) -)""" _PHYSICAL_VALUES = frozenset({ "row_format", "compression", @@ -137,13 +124,15 @@ def _reflect_tables(connection: Connection, database: str, exclude: frozenset[st inventory["tables"][name] = table -def read_revision(connection: Connection, inventory: dict[str, Any]) -> tuple[bool, str | None]: +def read_revision( + connection: Connection, inventory: dict[str, Any], *, revisions: tuple[str, ...] +) -> tuple[bool, str | None]: """Validate Alembic's only control table before reading its zero/one head.""" _business_inventory(inventory) if _CONTROL_TABLE not in inventory["tables"]: return False, None rows = connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision LIMIT 2").all() - if len(rows) > 1 or (rows and rows[0][0] not in _REVISIONS): + if len(rows) > 1 or (rows and rows[0][0] not in revisions): raise MigrationError("incompatible_schema", "The database has multiple or unknown migration heads.") return True, rows[0][0] if rows else None @@ -151,52 +140,224 @@ def read_revision(connection: Connection, inventory: dict[str, Any]) -> tuple[bo class MySQLSchema: """Validate source, intermediate and post-DDL shapes from frozen resources.""" - def __init__(self, bundle: MigrationBundle) -> None: - if bundle.revisions != _REVISIONS: - raise MigrationError("invalid_bundle", "MySQL schema recognition does not cover this revision chain.") + def __init__(self, bundle: MigrationBundle, *, embedded_code: bool = False) -> None: # noqa: C901 -- Keep ordered admission checks with their operation. self.bundle = bundle - baseline = json.loads((bundle.snapshot_directory / "schemas/pre_dream.json").read_text())["mysql"] - current = json.loads((bundle.snapshot_directory / "schemas/v1_1_0.json").read_text())["mysql"] - self._statements = { - "p0001": tuple(baseline), - "p0002": tuple(f"ALTER TABLE {table} ADD COLUMN memory_citations MEDIUMBLOB" for table in _CITATION_TABLES), - "p0003": (f"ALTER TABLE pc_artifact_tags DROP CHECK {_FAMILY_CHECK}",), - "p0004": (_ARTIFACT_HANDLES_DDL,), - } + self.revisions = bundle.revisions + manifest = json.loads((bundle.snapshot_directory / "manifest.json").read_text()) + self.optional_table_options = {} + self.affected_objects = bundle.affected_objects + if "mysql_state_layouts" in manifest: + self._load_state_layouts(manifest["mysql_state_layouts"]) + if embedded_code: + self._extend_embedded_code(manifest) + if bundle.vector_profile is not None: + self._extend_vectors() + return + if embedded_code: + raise MigrationError("invalid_bundle", "Embedded Code needs a registered complete Server layout.") + self.bundle = bundle + if not bundle.mysql_layouts: + raise MigrationError("invalid_bundle", "MySQL layout steps are not registered in this bundle.") + self._statements: dict[str, tuple[str, ...]] = {} + checkpoints: dict[str, str] = {} + for resource in bundle.mysql_layouts: + layouts = json.loads((bundle.snapshot_directory / resource).read_text(encoding="utf-8")) + if ( + not isinstance(layouts, dict) + or layouts.get("format") != "powercontext.mysql-layouts.v1" + or not isinstance(layouts.get("revisions"), dict) + or not isinstance(layouts.get("checkpoints"), dict) + or set(layouts["revisions"]) & self._statements.keys() + or set(layouts["checkpoints"]) & checkpoints.keys() + or any( + not isinstance(steps, list) or any(not isinstance(step, str) or not step.strip() for step in steps) + for steps in layouts["revisions"].values() + ) + or any( + not isinstance(name, str) or name not in bundle.resources + for name in layouts["checkpoints"].values() + ) + ): + raise MigrationError("invalid_bundle", "MySQL layout registration is invalid or duplicated.") + self._statements.update({revision: tuple(steps) for revision, steps in layouts["revisions"].items()}) + checkpoints.update(layouts["checkpoints"]) + if set(self._statements) != set(bundle.revisions) or not set(checkpoints) <= set(bundle.revisions): + raise MigrationError("invalid_bundle", "MySQL layout registration does not cover the revision chain.") inventory = _empty_inventory() self._prefixes: dict[str, list[dict[str, Any]]] = {} self._schemas: dict[str, dict[str, Any]] = {} - for revision in _REVISIONS: + for revision in bundle.revisions: prefixes = [copy.deepcopy(inventory)] for statement in self._statements[revision]: _apply_statement(inventory, statement) prefixes.append(copy.deepcopy(inventory)) self._prefixes[revision] = prefixes self._schemas[revision] = copy.deepcopy(inventory) - frozen_current = _empty_inventory() - for statement in current: - _apply_statement(frozen_current, statement) - if frozen_current != self._schemas["p0004"]: - raise MigrationError("invalid_bundle", "Frozen MySQL layouts disagree with the registered revision steps.") + for revision, resource in checkpoints.items(): + frozen = _empty_inventory() + for statement in json.loads((bundle.snapshot_directory / resource).read_text(encoding="utf-8"))["mysql"]: + _apply_statement(frozen, statement) + if frozen != self._schemas[revision]: + raise MigrationError( + "invalid_bundle", "Frozen MySQL layouts disagree with the registered revision steps." + ) + + def _load_state_layouts(self, resources: list[str]) -> None: # noqa: C901 -- Keep ordered admission checks with their operation. + registered = {"revisions": {}} + for resource in resources: + if resource not in self.bundle.resources: + raise MigrationError("invalid_bundle", "A MySQL state layout is outside the frozen bundle.") + fragment = json.loads((self.bundle.snapshot_directory / resource).read_text()) + if ( + fragment.get("format") != "powercontext.mysql-state-layouts.v1" + or set(fragment["revisions"]) & registered["revisions"].keys() + ): + raise MigrationError("invalid_bundle", "A MySQL state layout is invalid or duplicated.") + options = fragment.get("optional_table_options", {}) + if self.optional_table_options.keys() & options.keys(): + raise MigrationError("invalid_bundle", "A MySQL layout overrides an earlier option registration.") + self.optional_table_options.update(options) + registered["revisions"].update(fragment["revisions"]) + if set(registered["revisions"]) != set(self.bundle.revisions): + raise MigrationError("invalid_bundle", "MySQL state layouts must cover exactly the shared revision chain.") + inventory = _empty_inventory() + self._statements = {} + self._prefixes = {} + self._schemas = {} + for revision in self.bundle.revisions: + entry = registered["revisions"][revision] + statements, changes = entry["statements"], entry["changes"] + if ( + not statements + or len(statements) != len(changes) + or any(not isinstance(sql, str) or not sql.strip() for sql in statements) + ): + raise MigrationError("invalid_bundle", "Each MySQL operation requires its exact schema transition.") + prefixes = [copy.deepcopy(inventory)] + for change in changes: + for name, table in change.items(): + if table is None: + if name not in inventory["tables"]: + raise MigrationError("invalid_bundle", "A transition removes an unknown table.") + del inventory["tables"][name] + else: + inventory["tables"][name] = table + prefixes.append(copy.deepcopy(inventory)) + self._statements[revision] = tuple(statements) + self._prefixes[revision] = prefixes + self._schemas[revision] = copy.deepcopy(inventory) + + def execute(self, connection: Connection, statement: str, configuration: dict[str, Any]) -> None: + if statement.startswith("@"): + import runpy + + resource, name = statement.removeprefix("@").split(":", 1) + if resource not in self.bundle.resources: + raise MigrationError("invalid_bundle", "A conditional MySQL operation needs a frozen implementation.") + operation = runpy.run_path(str(self.bundle.snapshot_directory / resource))["execute"] + operation(connection, "@" + name, configuration) + else: + connection.exec_driver_sql(statement, execution_options={"no_parameters": True}) + + def _extend_embedded_code(self, manifest: dict[str, Any]) -> None: + resource = manifest.get("mysql_schema_extensions", {}).get("seekdb_code") + if resource not in self.bundle.resources: + raise MigrationError("invalid_bundle", "Embedded Code has no frozen schema registration.") + profile = json.loads((self.bundle.snapshot_directory / resource).read_text(encoding="utf-8")) + revision = profile["source_revision"] + statements = profile["statements"] + tables = profile["tables"] + if ( + profile.get("format") != "powercontext.mysql-schema-extension.v1" + or profile.get("backend") != "seekdb" + or revision != self.revisions[0] + or not statements + or dict(_parse_table(sql) for sql in statements) != tables + or any(set(tables) & set(state["tables"]) for state in self._schemas.values()) + ): + raise MigrationError("invalid_bundle", "The embedded Code schema registration is inconsistent.") + # Preserve the released Code tables through every core transition. A new + # database creates the same registered extension in the baseline revision. + state = copy.deepcopy(self._schemas[revision]) + for statement in statements: + name, table = _parse_table(statement) + state["tables"][name] = table + self._prefixes[revision].append(copy.deepcopy(state)) + self._statements[revision] += tuple(statements) + self._schemas[revision] = state + for later in self.revisions[1:]: + for prefix in self._prefixes[later]: + prefix["tables"].update(copy.deepcopy(tables)) + self._schemas[later]["tables"].update(copy.deepcopy(tables)) + self.affected_objects = (*self.affected_objects, *tables) @staticmethod def fingerprint(inventory: dict[str, Any]) -> str: return digest(inventory) + def _extend_vectors(self) -> None: + profile = self.bundle.vector_profile + if profile is None: + raise MigrationError("invalid_bundle", "Vector schema selection needs an exact profile.") + definitions = profile.mysql_tables() + self.optional_table_options.update(profile.layout["mysql_optional_table_options"]) + tables = dict(_parse_table(sql) for sql in definitions.values()) + atomic_name = "pc_atomic_memory_current" + atomic = tables.pop(atomic_name) + source, target, *later = self.revisions + for revision in self.revisions: + for state in [*self._prefixes[revision], self._schemas[revision]]: + if atomic_name in state["tables"]: + state["tables"][atomic_name] = copy.deepcopy(atomic) + if revision != source: + state["tables"].update(copy.deepcopy(tables)) + self._statements[source] = tuple( + definitions[atomic_name] if sql.startswith(f"CREATE TABLE `{atomic_name}` (") else sql + for sql in self._statements[source] + ) + source_state = self._schemas[source] + for name, table in tables.items(): + source_state["tables"][name] = copy.deepcopy(table) + self._prefixes[source].append(copy.deepcopy(source_state)) + self._statements[source] += (definitions[name],) + declaration_sql = profile.layout["mysql_profile_table"] + declaration_name, declaration = _parse_table(declaration_sql) + self._schemas[target]["tables"][declaration_name] = copy.deepcopy(declaration) + self._prefixes[target].append(copy.deepcopy(self._schemas[target])) + self._statements[target] += (declaration_sql,) + for revision in later: + for state in [*self._prefixes[revision], self._schemas[revision]]: + state["tables"][declaration_name] = copy.deepcopy(declaration) + self.affected_objects = tuple(dict.fromkeys([*self.affected_objects, *tables, declaration_name])) + + def _matches(self, inventory: dict[str, Any], expected: dict[str, Any]) -> bool: + # Captured backends may expose an explicit default that another backend + # omits. Only registered values may be absent; retain it in fingerprints. + actual = copy.deepcopy(inventory) + for name, options in self.optional_table_options.items(): + table = actual["tables"].get(name) + target = expected["tables"].get(name) + if table is None or target is None: + continue + for option, allowed in options.items(): + if option not in target and table.get(option) in allowed: + del table[option] + return _matches(actual, expected) + def identify(self, inventory: dict[str, Any]) -> str | None: """Only complete known shapes may be adopted as an unversioned source.""" inventory = _business_inventory(inventory) if inventory == _empty_inventory(): return None for revision, expected in self._schemas.items(): - if _matches(inventory, expected): + if self._matches(inventory, expected): return revision raise MigrationError("incompatible_schema", "Actual MySQL schema is not a registered complete layout.") def verify(self, inventory: dict[str, Any], revision: str | None) -> None: inventory = _business_inventory(inventory) expected = _empty_inventory() if revision is None else self._schemas.get(revision) - if expected is None or not _matches(inventory, expected): + if expected is None or not self._matches(inventory, expected): raise MigrationError("incompatible_schema", "Actual MySQL schema does not match its recorded revision.") def recovery(self, inventory: dict[str, Any], revision: str | None) -> MySQLRecovery: @@ -207,14 +368,12 @@ def recovery(self, inventory: dict[str, Any], revision: str | None) -> MySQLReco for advancing the version row, and is not itself recovery authorization. """ inventory = _business_inventory(inventory) - if revision is None: - target = _REVISIONS[0] - elif revision not in _REVISIONS or revision == _REVISIONS[-1]: + pending = self.bundle.pending(revision) + if not pending: raise MigrationError("incompatible_schema", "There is no registered next MySQL revision to recover.") - else: - target = _REVISIONS[_REVISIONS.index(revision) + 1] + target = pending[0] for position, expected in enumerate(self._prefixes[target]): - if _matches(inventory, expected): + if self._matches(inventory, expected): return MySQLRecovery(target, self._statements[target][position:]) raise MigrationError("incompatible_schema", "Actual MySQL schema is not a controlled next-revision DDL state.") @@ -228,6 +387,8 @@ def validate_data(self, connection: Connection, revision: str) -> None: expected = self._schemas.get(revision) if expected is None: raise MigrationError("incompatible_schema", "Cannot validate data for an unknown MySQL revision.") + if self.bundle.readiness_scope == "server": + expected = _business_inventory(mysql_inventory(connection)) for name, table in expected["tables"].items(): for check in table["checks"].values(): expression = _expression_sql(check["expression"]) @@ -366,6 +527,25 @@ def _validate_physical_value(option: str, value: str) -> None: raise MigrationError("incompatible_schema", "An unsupported table engine or index organization was discovered.") +def _table_options(tokens: list[str]) -> tuple[dict[str, str | None], str | None]: + positions = [position for position, option in enumerate(tokens) if option == "auto_increment_mode"] + if not positions: + return _physical(tokens), None + if len(positions) != 1: + raise MigrationError("incompatible_schema", "Duplicate AUTO_INCREMENT_MODE declarations were discovered.") + start = positions[0] + position = start + 1 + if tokens[position : position + 1] == ["="]: + position += 1 + if position >= len(tokens): + raise MigrationError("incompatible_schema", "AUTO_INCREMENT_MODE has no value.") + value = tokens[position] + mode = (_literal(value) if value.startswith("'") else value).lower() + if mode not in {"order", "noorder"}: + raise MigrationError("incompatible_schema", "An unsupported AUTO_INCREMENT_MODE was discovered.") + return _physical(tokens[:start] + tokens[position + 1 :]), mode + + def _column(tokens: list[str], defaults: dict[str, str | None]) -> dict[str, Any]: if len(tokens) < 2: raise MigrationError("incompatible_schema", "A column definition is incomplete.") @@ -519,8 +699,10 @@ def _parse_table(statement: str, *, database: str | None = None) -> tuple[str, d if schema is not None: raise MigrationError("incompatible_schema", "A table belongs to an unexpected database.") body, position = _group(tokens, position) - defaults = _physical(tokens[position:]) + defaults, auto_increment_mode = _table_options(tokens[position:]) table: dict[str, Any] = {"columns": {}, "primary_key": [], "foreign_keys": [], "checks": {}, "indexes": {}} + if auto_increment_mode is not None: + table["auto_increment_mode"] = auto_increment_mode for part in _parts(body): _table_clause(table, part, defaults, database) table["foreign_keys"].sort(key=digest) @@ -560,6 +742,69 @@ def _index_definition(part: list[str], constraint: str | None = None) -> tuple[s return index_name, {"columns": _key_columns(columns), "unique": unique} +def _fulltext_definition(part: list[str]) -> tuple[str, dict[str, Any]]: + if part[1:2] not in (["key"], ["index"]): + raise MigrationError("incompatible_schema", "An unsupported FULLTEXT index definition was discovered.") + columns, end = _group(part, 3) + parser = None + properties: dict[str, int] = {} + if part[end : end + 2] == ["with", "parser"]: + end += 2 + if end >= len(part) or not re.fullmatch(r"[a-z_][a-z_0-9$]*", part[end]): + raise MigrationError("incompatible_schema", "A FULLTEXT parser has no valid identity.") + parser = part[end] + end += 1 + if part[end : end + 2] == ["parser_properties", "="]: + arguments, end = _group(part, end + 2) + for option in _parts(arguments): + if ( + len(option) != 3 + or option[0] not in {"min_token_size", "max_token_size"} + or option[0] in properties + or option[1] != "=" + or not option[2].isdigit() + ): + raise MigrationError( + "incompatible_schema", "An unsupported FULLTEXT parser property was discovered." + ) + properties[option[0]] = int(option[2]) + _physical(part[end:]) + return part[2], { + "columns": _key_columns(columns), + "unique": False, + "kind": "fulltext", + "parser": parser, + "parser_properties": properties, + } + + +def _vector_definition(part: list[str]) -> tuple[str, dict[str, Any]]: + columns, end = _group(part, 3) + if part[end : end + 1] != ["with"]: + raise MigrationError("incompatible_schema", "A VECTOR index is missing its parameter declaration.") + arguments, end = _group(part, end + 1) + parameters: dict[str, str | int] = {} + for option in _parts(arguments): + if len(option) != 3 or option[1] != "=" or option[0] in parameters: + raise MigrationError("incompatible_schema", "A VECTOR index parameter is invalid or duplicated.") + name, _, value = option + if name in {"distance", "type", "lib"} and re.fullmatch(r"[a-z][a-z_0-9]*", value): + parameters[name] = value + elif name in {"m", "ef_construction", "ef_search"} and value.isdigit() and int(value) > 0: + parameters[name] = int(value) + else: + raise MigrationError("incompatible_schema", "An unsupported VECTOR index parameter was discovered.") + if not {"distance", "type"} <= parameters.keys(): + raise MigrationError("incompatible_schema", "A VECTOR index has no complete algorithm declaration.") + _physical(part[end:]) + return part[2], { + "columns": _key_columns(columns), + "unique": False, + "kind": "vector", + "parameters": parameters, + } + + def _table_clause( table: dict[str, Any], part: list[str], defaults: dict[str, str | None], database: str | None ) -> None: @@ -581,6 +826,12 @@ def _table_clause( elif part[0] in {"key", "index", "unique"}: index_name, definition = _index_definition(part, constraint) _put_named(table["indexes"], index_name, definition) + elif part[0] == "fulltext": + index_name, definition = _fulltext_definition(part) + _put_named(table["indexes"], index_name, definition) + elif part[0] == "vector" and part[1:2] in (["key"], ["index"]): + index_name, definition = _vector_definition(part) + _put_named(table["indexes"], index_name, definition) elif constraint is None: _put_named(table["columns"], part[0], _column(part, defaults)) else: @@ -627,7 +878,7 @@ def _matches(actual: dict[str, Any], expected: dict[str, Any]) -> bool: specification[option] = None automatic_keys: set[tuple[str, ...]] = set() for index_name, index in tuple(table["indexes"].items()): - if index_name in expected_table["indexes"] or index["unique"]: + if index_name in expected_table["indexes"] or index["unique"] or index.get("kind") is not None: continue columns = tuple(key[0] for key in index["columns"]) foreign_columns = {tuple(key["columns"]) for key in table["foreign_keys"]} diff --git a/src/powercontext/builtin/persistence/migrations/pinned.py b/src/powercontext/builtin/persistence/migrations/pinned.py new file mode 100644 index 0000000000..23729fd144 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/pinned.py @@ -0,0 +1,61 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Run domain database coroutines on the executor's pinned synchronous connection. + +Only immediate database operations are supported. An attempted external await, +transaction boundary, or connection acquisition fails instead of escaping the +maintenance connection and its lock. +""" + +from collections.abc import Coroutine +from typing import Any, cast + +from sqlalchemy import Connection +from sqlalchemy.ext.asyncio import AsyncConnection + + +class _PinnedConnection: + def __init__(self, connection: Connection): + self.connection = connection + self.dialect = connection.dialect + + async def execute(self, *args, **kwargs): + return self.connection.execute(*args, **kwargs) + + async def exec_driver_sql(self, *args, **kwargs): + return self.connection.exec_driver_sql(*args, **kwargs) + + async def scalar(self, *args, **kwargs): + return self.connection.scalar(*args, **kwargs) + + async def scalars(self, *args, **kwargs): + return self.connection.scalars(*args, **kwargs) + + async def run_sync(self, function, *args, **kwargs): + return function(self.connection, *args, **kwargs) + + +def pinned(connection: Connection) -> AsyncConnection: + return cast(AsyncConnection, _PinnedConnection(connection)) + + +def finish(coroutine: Coroutine[Any, Any, Any]) -> Any: + try: + coroutine.send(None) + except StopIteration as result: + return result.value + else: + coroutine.close() + raise RuntimeError("A migration domain attempted asynchronous work outside the pinned database connection") # noqa: TRY003 diff --git a/src/powercontext/builtin/persistence/migrations/processing_configuration.py b/src/powercontext/builtin/persistence/migrations/processing_configuration.py new file mode 100644 index 0000000000..ff233a318c --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/processing_configuration.py @@ -0,0 +1,99 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Registered offline Processing configuration changes on current Server storage.""" + +from __future__ import annotations + +import hashlib +from pathlib import Path + +from sqlalchemy import Connection, text +from typing_extensions import override + +from powercontext.builtin.persistence import processing_migration +from powercontext.builtin.persistence.processing_migration import ProcessingSchemaNotReadyError + +from .bundle import sqlite_inventory +from .connections import BackendIdentity +from .maintenance import ManagedMaintenanceRunner +from .models import MigrationError, digest +from .mysql_schema import mysql_inventory +from .pinned import finish, pinned +from .server_readiness import check_server_schema + + +class ProcessingConfigurationMigrationRunner(ManagedMaintenanceRunner): + """Publish mode changes and lease fencing in the same shared transaction.""" + + operation = "processing_configuration" + affected_objects = ( + "pc_artifact_processing_schema", + "pc_artifact_processing_leases", + "pc_artifact_processing_auto_wave_targets", + ) + + def __init__(self, *args, **kwargs): + super().__init__( + *args, + implementation_checksum=digest({ + "owner": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "processing": hashlib.sha256(Path(processing_migration.__file__).read_bytes()).hexdigest(), + }), + **kwargs, + ) + + @override + def _state(self, connection: Connection, identity: BackendIdentity) -> tuple[str, bool, bool]: + check_server_schema(connection) + marker = dict( + connection + .execute( + text( + "SELECT schema_version,source_version,phase,migration_id,config_manifest " + "FROM pc_artifact_processing_schema WHERE singleton=1" + ) + ) + .mappings() + .one() + ) + required = finish( + processing_migration.plan_processing_migration( + pinned(connection), config_manifest=self.configuration["processing_manifest"] + ) + ).required + if required and marker["migration_id"] == self.migration_id: + raise MigrationError( + "migration_id_reused", "A Processing configuration switch requires a new migration ID." + ) + fingerprint = digest({ + "schema": sqlite_inventory(connection) if identity.product == "sqlite" else mysql_inventory(connection), + "processing": marker, + }) + return fingerprint, required, marker["migration_id"] == self.migration_id + + @override + def _apply_operation(self, connection: Connection, guard) -> None: + try: + while not finish( + processing_migration.apply_processing_migration( + pinned(connection), + config_manifest=self.configuration["processing_manifest"], + migration_id=self.migration_id, + batch_size=self.batch_size, + ) + ).complete: + guard() + except ProcessingSchemaNotReadyError as error: + raise MigrationError("incompatible_processing_configuration", str(error)) from error diff --git a/src/powercontext/builtin/persistence/migrations/resources/manifest.json b/src/powercontext/builtin/persistence/migrations/resources/manifest.json index 16364daa4f..92f6ba7340 100644 --- a/src/powercontext/builtin/persistence/migrations/resources/manifest.json +++ b/src/powercontext/builtin/persistence/migrations/resources/manifest.json @@ -45,5 +45,8 @@ }, "retained_revisions": { "pc_retained_p0002_artifact_tags": "p0003" - } + }, + "mysql_layouts": [ + "mysql_layouts.json" + ] } diff --git a/src/powercontext/builtin/persistence/migrations/resources/mysql_layouts.json b/src/powercontext/builtin/persistence/migrations/resources/mysql_layouts.json new file mode 100644 index 0000000000..6d6bda0e94 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/resources/mysql_layouts.json @@ -0,0 +1,26 @@ +{ + "format": "powercontext.mysql-layouts.v1", + "revisions": { + "p0001": [ + "CREATE TABLE pc_artifacts (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tartifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\trevision INTEGER NOT NULL, \n\tcontent MEDIUMBLOB NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision)\n)", + "CREATE TABLE pc_artifact_heads (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tartifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsearchable_text MEDIUMTEXT, \n\tlifecycle_state VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'active', \n\treplacement_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tgovernance_generation BIGINT NOT NULL DEFAULT '0', \n\tPRIMARY KEY (scope_id, family, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_heads_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_artifact_heads_lifecycle_state CHECK (lifecycle_state IN ('active', 'deprecated', 'retired')), \n\tCONSTRAINT ck_pc_artifact_heads_governance_generation_nonnegative CHECK (governance_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_heads_replacement_deprecated CHECK (replacement_artifact_id IS NULL OR lifecycle_state = 'deprecated')\n)", + "CREATE TABLE pc_artifact_candidate_versions (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tproposal MEDIUMBLOB NOT NULL, \n\tsource_refs MEDIUMBLOB NOT NULL, \n\tartifact_refs MEDIUMBLOB NOT NULL, \n\ttarget_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_revision INTEGER, \n\treason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_artifact_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)", + "CREATE TABLE pc_artifact_tags (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tartifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\ttarget_type VARCHAR(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\ttarget_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\ttag_key_hash BINARY(32) NOT NULL, \n\ttag_key VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\ttag VARCHAR(64) NOT NULL, \n\tassigned_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, target_type, target_id, tag_key_hash), \n\tFOREIGN KEY(scope_id, family, artifact_id) REFERENCES pc_artifact_heads (scope_id, family, artifact_id) ON DELETE CASCADE, \n\tCONSTRAINT ck_pc_artifact_tags_family CHECK (family IN ('memory', 'experience', 'skill', 'handoff')), \n\tCONSTRAINT ck_pc_artifact_tags_target CHECK ((target_type = 'artifact' AND target_id = artifact_id) OR (target_type = 'memory_entry' AND family = 'memory'))\n)", + "CREATE INDEX ix_pc_artifact_tags_family_key ON pc_artifact_tags (scope_id, family, tag_key_hash, target_type, artifact_id, target_id)", + "CREATE INDEX ix_pc_artifact_tags_key ON pc_artifact_tags (scope_id, tag_key_hash, family, target_type, artifact_id, target_id)" + ], + "p0002": [ + "ALTER TABLE pc_artifacts ADD COLUMN memory_citations MEDIUMBLOB", + "ALTER TABLE pc_artifact_candidate_versions ADD COLUMN memory_citations MEDIUMBLOB" + ], + "p0003": [ + "ALTER TABLE pc_artifact_tags DROP CHECK ck_pc_artifact_tags_family" + ], + "p0004": [ + "CREATE TABLE pc_artifact_handles (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n\thandle_key VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n\tartifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n\trevision INTEGER NOT NULL,\n\tPRIMARY KEY (scope_id, family, handle_key),\n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT,\n\tCONSTRAINT ck_pc_artifact_handles_revision_positive CHECK (revision > 0)\n)" + ] + }, + "checkpoints": { + "p0004": "schemas/v1_1_0.json" + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_baselines.py b/src/powercontext/builtin/persistence/migrations/server_baselines.py new file mode 100644 index 0000000000..e7736c34f2 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_baselines.py @@ -0,0 +1,75 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Recognize complete installed-release schema shapes, independently of readiness.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Literal + +from sqlalchemy import Connection + +from .bundle import sqlite_inventory +from .models import MigrationError, digest + +_BASELINES = Path(__file__).parents[1] / "migration_resources/server_baselines" + + +@dataclass(frozen=True) +class ServerBaseline: + """A recognized schema source, not a validated data state or executable path.""" + + release: str + schema_fingerprint: str + + +def recognize_sqlite_server_baseline(connection: Connection) -> ServerBaseline | None: + """Match the complete schema, including FTS shadows and optional-feature objects. + + No objects are excluded: in particular, an existing control table cannot be + ignored to stamp a partially migrated database as a released source. + """ + return _recognize(digest(sqlite_inventory(connection)), backend="sqlite") + + +def recognize_mysql_server_baseline( + inventory: dict[str, Any], *, backend: Literal["seekdb", "oceanbase"] +) -> ServerBaseline | None: + """Match a complete native inventory against captures of the same backend. + + FULLTEXT parser configuration participates in the fingerprint. A capture + from embedded seekDB does not establish an OceanBase source baseline. + """ + return _recognize(digest(inventory), backend=backend) + + +def _recognize(fingerprint: str, *, backend: Literal["sqlite", "seekdb", "oceanbase"]) -> ServerBaseline | None: + matches: list[ServerBaseline] = [] + for path in sorted(_BASELINES.glob(f"*/{backend}.json")): + source = json.loads(path.read_text(encoding="utf-8")) + if ( + source["format"] != "powercontext.server-baseline.v1" + or source["backend"] != backend + or source["release"] != path.parent.name + or digest(source["inventory"]) != source["schema_fingerprint"] + ): + raise MigrationError("invalid_bundle", "The frozen Server baseline resource is inconsistent.") + if fingerprint == source["schema_fingerprint"]: + matches.append(ServerBaseline(release=source["release"], schema_fingerprint=fingerprint)) + if len(matches) > 1: + raise MigrationError("invalid_bundle", "Multiple releases claim the same complete Server baseline.") + return matches[0] if matches else None diff --git a/src/powercontext/builtin/persistence/migrations/server_readiness.py b/src/powercontext/builtin/persistence/migrations/server_readiness.py new file mode 100644 index 0000000000..1d3344f4e9 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_readiness.py @@ -0,0 +1,152 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Full Server bootstrap and read-only admission for versioned storage.""" + +from sqlalchemy import inspect + +from .bundle import sqlite_inventory +from .models import MigrationError, digest + + +def check_server_schema(connection, *, _archive_recovery=False): + bundle = check_server_layout(connection) + pending_restore = connection.exec_driver_sql( + "SELECT 1 FROM pc_portable_restore_receipts WHERE projections_ready = false LIMIT 1" + ).first() + if pending_restore is not None: + if _archive_recovery: + # Only the explicit archive restore path may open an incomplete + # projection set. Its registered schema is still checked above. + return + from powercontext.builtin.portability import BundleRecoveryRequiredError + + raise BundleRecoveryRequiredError() + backend = "sqlite" if connection.dialect.name == "sqlite" else "seekdb" + configuration = ( + {"embedding_profile": bundle.vector_profile.profile.model_dump(mode="json")} + if bundle.vector_profile is not None + else {} + ) + bundle.domains.validate(connection, bundle.head, backend=backend, configuration=configuration) + + +def check_server_layout(connection): + """Validate the exact managed layout before a registered domain repair.""" + from .deployment import server_bundle + from .mysql_schema import MySQLSchema, mysql_inventory + from .vector_profile import load_sqlite_vector_extension, read_embedding_profile + + bundle = server_bundle(embedding_profile=read_embedding_profile(connection)) + if connection.dialect.name == "sqlite" and bundle.vector_profile is not None: + load_sqlite_vector_extension(connection) + revisions = connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalars().all() + if revisions != [bundle.head]: + raise MigrationError("migration_required", "The full Server revision chain is incomplete.") + if connection.dialect.name == "sqlite": + inventory = sqlite_inventory(connection) + for name in bundle.retained_objects: + objects = [row for row in inventory if row[1] == name or row[2] == name] + if objects and digest(objects) not in bundle.retained_fingerprints[name]: + raise MigrationError("incompatible_schema", "A retained migration object has changed.") + bundle.verify( + bundle.head, + digest(sqlite_inventory(connection, exclude=frozenset({"pc_schema_revision", *bundle.retained_objects}))), + ) + if connection.exec_driver_sql("PRAGMA foreign_key_check").first(): + raise MigrationError("verification_failed", "A live foreign key is invalid.") + else: + inventory = mysql_inventory(connection) + embedded_code = connection.engine.url.drivername == "mysql+aseekdb" and any( + name.startswith("pc_code_") for name in inventory["tables"] + ) + schema = MySQLSchema(bundle, embedded_code=embedded_code) + schema.verify(inventory, bundle.head) + return bundle + + +def prepare_server_storage(settings, *, injected_embedding=False, embedding_profile=None): + from .deployment import configured_embedding_profile, deployment_runner + + database = settings.database + if database.kind == "sqlite" and database.is_in_memory: + return + profile = embedding_profile or configured_embedding_profile(settings) + if injected_embedding and profile is None: + _check_unmanaged_optional_profile(database) + return + if database.kind == "oceanbase": + from .connections import MaintenanceConnections + + def verify(connection, identity, guard): + if connection is None or "pc_schema_revision" not in inspect(connection).get_table_names(): + raise MigrationError( + "migration_required", "Initialize the remote database with an explicit db-migrate job." + ) + bundle = check_server_layout(connection) + stored = bundle.vector_profile.profile if bundle.vector_profile is not None else None + if stored != profile: + raise MigrationError("embedding_profile_mismatch", "Server configuration differs from managed storage.") + check_server_schema(connection) + + MaintenanceConnections(database).run(verify) + return + runner = deployment_runner(settings, embedding_profile=profile) + plan = runner.plan() + if plan.state == "uninitialized" and database.kind in {"sqlite", "seekdb"}: + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + elif plan.state != "ready": + raise MigrationError(plan.state, "Stop writers and run powercontext server db-migrate apply before startup.") + + +def _check_unmanaged_optional_profile(database): + """Preserve existing optional-profile bootstrap without adopting managed storage. + + Optional layouts need separate full-bundle profiles. Existing domain startup + checks still reject old Candidate layouts; this is not an upgrade fallback. + """ + from powercontext.builtin.persistence.dream_schema import check_dream_schema + + from .connections import MaintenanceConnections + + def check(connection): + if "pc_schema_revision" in inspect(connection).get_table_names(): + raise MigrationError( + "unsupported_upgrade_configuration", + "This managed database has no registered vector/Code configuration transition.", + ) + check_dream_schema(connection) + + if database.kind != "sqlite": + MaintenanceConnections(database).run( + lambda connection, identity, guard: check(connection) if connection is not None else None + ) + return + from pathlib import Path + from urllib.parse import quote + + from sqlalchemy import create_engine + from sqlalchemy.engine import make_url + + url = make_url(database.url) + # Unmanaged URI profiles retain their existing domain admission behavior. + if url.query or not url.database or not Path(url.database).is_file(): + return + path = quote(str(Path(url.database).absolute()), safe="/") + engine = create_engine(f"sqlite:///file:{path}?mode=ro&uri=true") + try: + with engine.connect() as connection: + check(connection) + finally: + engine.dispose() diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/atomic_memory_readiness_1300.py b/src/powercontext/builtin/persistence/migrations/server_resources/atomic_memory_readiness_1300.py new file mode 100644 index 0000000000..ff12a47d4b --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/atomic_memory_readiness_1300.py @@ -0,0 +1,128 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Read-only Atomic Memory readiness checks over public rows and schema metadata. + +No archived payload or legacy entry is loaded here. Full historical validation +belongs to the explicitly invoked offline migration. +""" + +from __future__ import annotations + +from typing import Any + +from sqlalchemy import inspect, text +from sqlalchemy.ext.asyncio import AsyncConnection + +from powercontext.builtin.persistence.errors import PersistenceError + +LEGACY_CITATION_COLUMN = "memory_citations" +_LEGACY_CITATION_TABLES = ("pc_artifacts", "pc_artifact_candidate_versions", "pc_candidate_versions") +LEGACY_ENTRY_FOREIGN_KEYS = { + "pc_memory_entry_versions": ("scope_id", "family", "memory_artifact_id", "created_in_revision"), + "pc_memory_entry_heads": ("scope_id", "family", "memory_artifact_id", "head_revision"), +} + + +class AtomicMemoryMigrationError(PersistenceError): + def __init__(self, errors: tuple[str, ...]) -> None: + self.errors = errors + super().__init__("Atomic Memory offline migration is not ready: " + "; ".join(errors)) + + +async def ensure_supported_atomic_memory_schema(connection: AsyncConnection) -> None: + """Reject unreleased split authority without changing its evidence.""" + + tables = await connection.run_sync(lambda value: inspect(value).get_table_names()) + if "pc_atomic_memory_states" in tables: + raise AtomicMemoryMigrationError(( + "unsupported unreleased schema pc_atomic_memory_states; preserve the database and use a supported backup", + )) + + +async def legacy_citation_columns(connection: AsyncConnection, tables: set[str]) -> list[str]: + """Return public tables that still declare the retired citation column.""" + + def read(value: Any) -> list[str]: + inspector = inspect(value) + return [ + table + for table in _LEGACY_CITATION_TABLES + if table in tables + and any(column["name"] == LEGACY_CITATION_COLUMN for column in inspector.get_columns(table)) + ] + + return await connection.run_sync(read) + + +async def legacy_entry_foreign_key_names(connection: AsyncConnection, table: str) -> list[str | None]: + columns = LEGACY_ENTRY_FOREIGN_KEYS[table] + + def read(value: Any) -> list[str | None]: + return [ + item.get("name") + for item in inspect(value).get_foreign_keys(table) + if item["referred_table"] == "pc_artifacts" and tuple(item["constrained_columns"]) == columns + ] + + return await connection.run_sync(read) + + +async def legacy_foreign_keys(connection: AsyncConnection, tables: set[str]) -> list[str]: + return [ + table + for table in LEGACY_ENTRY_FOREIGN_KEYS + if table in tables and await legacy_entry_foreign_key_names(connection, table) + ] + + +async def atomic_memory_readiness_issues( + connection: AsyncConnection, tables: set[str] | None = None, *, collections_removed: bool = True +) -> list[str]: + """Check public migration completion, allowing collections only during offline pre-removal acceptance.""" + + if tables is None: + tables = set(await connection.run_sync(lambda value: inspect(value).get_table_names())) + issues: list[str] = [] + if ( + collections_removed + and "pc_artifacts" in tables + and await connection.scalar(text("SELECT COUNT(*) FROM pc_artifacts WHERE family = 'memory'")) + ): + issues.append("legacy Memory collections remain in public Artifact tables; run apply") + referrers = "" if collections_removed else " AND family <> 'memory'" + if "pc_artifact_lineage_artifacts" in tables and await connection.scalar( + text( + "SELECT COUNT(*) FROM pc_artifact_lineage_artifacts WHERE upstream_family = 'memory'" + referrers # noqa: S608 + ) + ): + issues.append("public lineage still references legacy Memory collections; run apply") + foreign_keys = await legacy_foreign_keys(connection, tables) + if foreign_keys: + issues.append("legacy entry tables still reference public Artifacts: " + ", ".join(foreign_keys)) + citation_tables = await legacy_citation_columns(connection, tables) + if collections_removed and citation_tables: + issues.append( + "public tables still declare legacy memory_citations: " + ", ".join(citation_tables) + "; run apply" + ) + return issues + + +async def assert_atomic_memory_migration_ready(connection: AsyncConnection) -> None: + """Reject public migration residuals without reading frozen historical records.""" + + await ensure_supported_atomic_memory_schema(connection) + errors = await atomic_memory_readiness_issues(connection) + if errors: + raise AtomicMemoryMigrationError(tuple(errors)) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/dream/manifest.json b/src/powercontext/builtin/persistence/migrations/server_resources/dream/manifest.json new file mode 100644 index 0000000000..445f144be7 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/dream/manifest.json @@ -0,0 +1,98 @@ +{ + "feature": "dream_expansion", + "rfc": 1715, + "migration_rfc": 1771, + "framework_implementation_pr": 1772, + "registration": { + "state": "registered", + "revision": "s1400", + "down_revision": "s1300", + "server_ready": false + }, + "execution_mode": "maintenance", + "mixed_binaries": false, + "frozen_resources": [ + "upgrade.py", + "schema.json", + "manifest.json" + ], + "affected_objects": [ + "pc_artifact_candidate_heads", + "pc_artifact_candidate_versions", + "pc_candidate_heads", + "pc_candidate_versions", + "pc_profile_policies.pending_candidate_id", + "pc_dream_runs.proposal_fingerprint", + "ix_pc_dream_runs_proposal_fingerprint", + "pc_artifact_processing_intents.consecutive_dream_attempts", + "pc_artifact_processing_schema.config_manifest" + ], + "retained_objects": [ + { + "source": "pc_artifact_candidate_heads", + "name": "pc_retained_dream_candidate_heads", + "purpose": "Pre-upgrade Candidate heads without live foreign keys", + "cleanup": "Separate framework revision after recovery obligations expire" + }, + { + "source": "pc_artifact_candidate_versions", + "name": "pc_retained_dream_candidate_versions", + "purpose": "Pre-upgrade immutable proposals without live foreign keys", + "cleanup": "Separate framework revision after recovery obligations expire" + } + ], + "api_changes": { + "removed": [ + "/v1/artifact-candidates/*" + ], + "replacements": [ + "/v1/candidates/list", + "/v1/candidates/get", + "/v1/candidates/history", + "/v1/candidates/revise", + "/v1/candidates/approve", + "/v1/candidates/reject" + ], + "deprecation_policy": "Explicit direct-removal exception in RFC 1715; coordinated client upgrade", + "aliases": false, + "removal_release": "1.4.0", + "minimum_client_version": "1.4.0" + }, + "task_formats": { + "dream_runs": { + "operations": [ + "refine_experience", + "derive_skill" + ], + "states": [ + "queued", + "running", + "succeeded", + "failed" + ], + "payload_schema": "schema.json#/task_payload_schema", + "strategy": "drain_before_upgrade", + "conversion": "none; preserve terminal payload, identity, principal, idempotency and candidate references", + "unknown": "unsupported_task_format", + "nonterminal": "dream_tasks_require_drain; stop admissions and finish queued/running work with the old runtime before stopping writers", + "frozen_execution": "Never rewrite prompt/model identity, started_at, deadline_at, retry counters or failure outcomes" + }, + "processing_intents": "Preserve all fields; initialize consecutive_dream_attempts to zero", + "other_queues_and_leases": "Full Server preflight validates registered Processing bindings and counters; the revision fences the shared processing lease. Other historical schemas and optional feature queues are not adopted.", + "processing_manifest": "Require the target deployment manifest; preserve explicit capabilities, ownership mode and historical automatic bindings; add canonical Handoff/Prompt bindings; allow only declared Handoff/Prompt capability additions and reject incompatible targets before writes" + }, + "framework_requirements": [ + "Complete and verify master Atomic Memory migration before this transformation; candidate source tables must no longer declare memory_citations", + "Register a complete historical Server baseline and actual revision dependencies", + "Freeze and checksum this transformation and schema.json with the release bundle", + "Bind removal_release and minimum_client_version before production registration", + "Validate available backup policy, capacity for retained copies and restoration dependencies", + "Hold the framework maintenance lock and pinned connection with all writers stopped", + "Disable SQLite foreign keys before beginning the rebuild transaction; verify before revision advancement", + "Verify backend DDL and recovery on SQLite, seekdb and OceanBase MySQL mode", + "Run the legacy task recognizer during plan and locked execution before transformation", + "Bind the canonical target deployment manifest to the plan; validate it during planning and locked execution before any DDL or data writes; all new hosts must use the bound configuration", + "Run migration-only copy verification before revision advancement; use live invariants and current task recognizers for ordinary startup", + "Integrate complete runtime readiness before admitting a versioned database" + ] +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/dream/schema.json b/src/powercontext/builtin/persistence/migrations/server_resources/dream/schema.json new file mode 100644 index 0000000000..1edfa82a6d --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/dream/schema.json @@ -0,0 +1,870 @@ +{ + "source_commit": "4ae1371cbe61a8911451236e8d38178f1ccee2d4", + "tables": { + "pc_candidate_versions": { + "source_name": "pc_artifact_candidate_versions", + "target_name": "pc_candidate_versions", + "retained_name": "pc_retained_dream_candidate_versions", + "source_columns": [ + "scope_id", + "candidate_id", + "version", + "family", + "proposal", + "source_refs", + "artifact_refs", + "target_family", + "target_artifact_id", + "target_revision", + "reason" + ], + "target_columns": [ + "scope_id", + "candidate_id", + "version", + "family", + "proposal", + "source_refs", + "artifact_refs", + "target_family", + "target_artifact_id", + "target_revision", + "reason" + ], + "source": { + "sqlite": "CREATE TABLE pc_artifact_candidate_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tproposal BLOB NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\treason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_artifact_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)", + "mysql": "CREATE TABLE pc_artifact_candidate_versions (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tproposal MEDIUMBLOB NOT NULL, \n\tsource_refs MEDIUMBLOB NOT NULL, \n\tartifact_refs MEDIUMBLOB NOT NULL, \n\ttarget_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_revision INTEGER, \n\treason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_artifact_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)" + }, + "target": { + "sqlite": "CREATE TABLE pc_candidate_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tproposal BLOB NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\treason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)", + "mysql": "CREATE TABLE pc_candidate_versions (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tproposal MEDIUMBLOB NOT NULL, \n\tsource_refs MEDIUMBLOB NOT NULL, \n\tartifact_refs MEDIUMBLOB NOT NULL, \n\ttarget_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_revision INTEGER, \n\treason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)" + }, + "retained": { + "sqlite": "CREATE TABLE pc_retained_dream_candidate_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tproposal BLOB NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\treason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tCONSTRAINT ck_pc_retained_dream_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_retained_dream_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)", + "mysql": "CREATE TABLE pc_retained_dream_candidate_versions (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tproposal MEDIUMBLOB NOT NULL, \n\tsource_refs MEDIUMBLOB NOT NULL, \n\tartifact_refs MEDIUMBLOB NOT NULL, \n\ttarget_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_revision INTEGER, \n\treason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tCONSTRAINT ck_pc_retained_dream_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_retained_dream_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)" + } + }, + "pc_candidate_heads": { + "source_name": "pc_artifact_candidate_heads", + "target_name": "pc_candidate_heads", + "retained_name": "pc_retained_dream_candidate_heads", + "source_columns": [ + "scope_id", + "candidate_id", + "family", + "version", + "status", + "result_family", + "result_artifact_id", + "result_revision", + "decision_reason" + ], + "target_columns": [ + "scope_id", + "candidate_id", + "family", + "version", + "status", + "candidate_kind", + "result_payload", + "result_family", + "result_artifact_id", + "result_revision", + "decision_reason" + ], + "source": { + "sqlite": "CREATE TABLE pc_artifact_candidate_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\tresult_family VARCHAR(128), \n\tresult_artifact_id VARCHAR(128), \n\tresult_revision INTEGER, \n\tdecision_reason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tFOREIGN KEY(scope_id, candidate_id, version) REFERENCES pc_artifact_candidate_versions (scope_id, candidate_id, version) ON DELETE RESTRICT, \n\tFOREIGN KEY(scope_id, result_family, result_artifact_id, result_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_artifact_candidate_heads_terminal_result CHECK ((status = 'approved' AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'rejected' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)", + "mysql": "CREATE TABLE pc_artifact_candidate_heads (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tresult_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tresult_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tresult_revision INTEGER, \n\tdecision_reason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tFOREIGN KEY(scope_id, candidate_id, version) REFERENCES pc_artifact_candidate_versions (scope_id, candidate_id, version) ON DELETE RESTRICT, \n\tFOREIGN KEY(scope_id, result_family, result_artifact_id, result_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_artifact_candidate_heads_terminal_result CHECK ((status = 'approved' AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'rejected' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)" + }, + "target": { + "sqlite": "CREATE TABLE pc_candidate_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\tcandidate_kind VARCHAR(16) DEFAULT 'artifact' NOT NULL, \n\tresult_payload BLOB, \n\tresult_family VARCHAR(128), \n\tresult_artifact_id VARCHAR(128), \n\tresult_revision INTEGER, \n\tdecision_reason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, candidate_id, version) REFERENCES pc_candidate_versions (scope_id, candidate_id, version) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, result_family, result_artifact_id, result_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_candidate_kind CHECK (candidate_kind IN ('artifact', 'tag')), \n\tCONSTRAINT ck_pc_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_candidate_heads_terminal_result CHECK ((status = 'approved' AND candidate_kind = 'artifact' AND result_payload IS NULL AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'approved' AND candidate_kind = 'tag' AND result_payload IS NOT NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL) OR (status = 'rejected' AND result_payload IS NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_payload IS NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)", + "mysql": "CREATE TABLE pc_candidate_heads (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_kind VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'artifact', \n\tresult_payload MEDIUMBLOB, \n\tresult_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tresult_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tresult_revision INTEGER, \n\tdecision_reason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, candidate_id, version) REFERENCES pc_candidate_versions (scope_id, candidate_id, version) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, result_family, result_artifact_id, result_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_candidate_kind CHECK (candidate_kind IN ('artifact', 'tag')), \n\tCONSTRAINT ck_pc_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_candidate_heads_terminal_result CHECK ((status = 'approved' AND candidate_kind = 'artifact' AND result_payload IS NULL AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'approved' AND candidate_kind = 'tag' AND result_payload IS NOT NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL) OR (status = 'rejected' AND result_payload IS NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_payload IS NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)" + }, + "retained": { + "sqlite": "CREATE TABLE pc_retained_dream_candidate_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\tresult_family VARCHAR(128), \n\tresult_artifact_id VARCHAR(128), \n\tresult_revision INTEGER, \n\tdecision_reason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tCONSTRAINT ck_pc_retained_dream_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_retained_dream_heads_terminal_result CHECK ((status = 'approved' AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'rejected' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)", + "mysql": "CREATE TABLE pc_retained_dream_candidate_heads (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tresult_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tresult_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tresult_revision INTEGER, \n\tdecision_reason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tCONSTRAINT ck_pc_retained_dream_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_retained_dream_heads_terminal_result CHECK ((status = 'approved' AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'rejected' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)" + } + } + }, + "add_columns": { + "pc_dream_runs": { + "column": "proposal_fingerprint", + "ddl": { + "sqlite": "proposal_fingerprint VARCHAR(71)", + "mysql": "proposal_fingerprint VARCHAR(71)" + } + }, + "pc_artifact_processing_intents": { + "column": "consecutive_dream_attempts", + "ddl": { + "sqlite": "consecutive_dream_attempts BIGINT DEFAULT '0' NOT NULL", + "mysql": "consecutive_dream_attempts BIGINT NOT NULL DEFAULT '0'" + } + } + }, + "indexes": { + "sqlite": "CREATE INDEX ix_pc_dream_runs_proposal_fingerprint ON pc_dream_runs (scope_id, principal_key, proposal_fingerprint)", + "mysql": "CREATE INDEX ix_pc_dream_runs_proposal_fingerprint ON pc_dream_runs (scope_id, principal_key, proposal_fingerprint)" + }, + "task_payload_schema": { + "$defs": { + "ArtifactRef": { + "description": "A stable reference to one exact artifact revision.", + "properties": { + "family": { + "title": "Family", + "type": "string" + }, + "artifact_id": { + "title": "Artifact Id", + "type": "string" + }, + "revision": { + "minimum": 1, + "title": "Revision", + "type": "integer" + } + }, + "required": [ + "family", + "artifact_id", + "revision" + ], + "title": "ArtifactRef", + "type": "object" + }, + "CreateDreamRunRequest": { + "additionalProperties": false, + "properties": { + "operation": { + "enum": [ + "refine_experience", + "derive_skill" + ], + "title": "Operation", + "type": "string" + }, + "artifacts": { + "default": [], + "items": { + "$ref": "#/$defs/ArtifactRef" + }, + "title": "Artifacts", + "type": "array" + }, + "memory_citations": { + "default": [], + "items": { + "$ref": "#/$defs/MemoryCitation" + }, + "title": "Memory Citations", + "type": "array" + }, + "sources": { + "default": [], + "items": { + "$ref": "#/$defs/SourceRef" + }, + "title": "Sources", + "type": "array" + }, + "target": { + "anyOf": [ + { + "$ref": "#/$defs/ArtifactRef" + }, + { + "type": "null" + } + ], + "default": null + }, + "idempotency_key": { + "maxLength": 128, + "minLength": 1, + "title": "Idempotency Key", + "type": "string" + } + }, + "required": [ + "operation", + "idempotency_key" + ], + "title": "CreateDreamRunRequest", + "type": "object" + }, + "DreamBudget": { + "additionalProperties": false, + "properties": { + "max_items": { + "default": 32, + "maximum": 32, + "minimum": 1, + "title": "Max Items", + "type": "integer" + }, + "max_bytes": { + "default": 65536, + "maximum": 65536, + "minimum": 1, + "title": "Max Bytes", + "type": "integer" + }, + "max_nodes": { + "default": 128, + "maximum": 128, + "minimum": 1, + "title": "Max Nodes", + "type": "integer" + }, + "max_edges": { + "default": 256, + "maximum": 256, + "minimum": 1, + "title": "Max Edges", + "type": "integer" + }, + "max_depth": { + "default": 8, + "maximum": 8, + "minimum": 1, + "title": "Max Depth", + "type": "integer" + }, + "max_output_tokens": { + "default": 4096, + "maximum": 4096, + "minimum": 1, + "title": "Max Output Tokens", + "type": "integer" + }, + "max_model_calls": { + "default": 2, + "maximum": 2, + "minimum": 1, + "title": "Max Model Calls", + "type": "integer" + }, + "timeout_seconds": { + "default": 120, + "exclusiveMinimum": 0, + "maximum": 120, + "title": "Timeout Seconds", + "type": "number" + } + }, + "title": "DreamBudget", + "type": "object" + }, + "DreamCandidateRef": { + "properties": { + "candidate_id": { + "title": "Candidate Id", + "type": "string" + }, + "version": { + "minimum": 1, + "title": "Version", + "type": "integer" + } + }, + "required": [ + "candidate_id", + "version" + ], + "title": "DreamCandidateRef", + "type": "object" + }, + "DreamRun": { + "properties": { + "scope_id": { + "title": "Scope Id", + "type": "string" + }, + "run_id": { + "title": "Run Id", + "type": "string" + }, + "operation": { + "enum": [ + "refine_experience", + "derive_skill" + ], + "title": "Operation", + "type": "string" + }, + "status": { + "default": "queued", + "enum": [ + "queued", + "running", + "succeeded", + "failed" + ], + "title": "Status", + "type": "string" + }, + "outcome": { + "anyOf": [ + { + "enum": [ + "proposed", + "no_change", + "needs_evidence" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outcome" + }, + "target": { + "anyOf": [ + { + "$ref": "#/$defs/ArtifactRef" + }, + { + "type": "null" + } + ], + "default": null + }, + "candidate": { + "anyOf": [ + { + "$ref": "#/$defs/DreamCandidateRef" + }, + { + "type": "null" + } + ], + "default": null + }, + "reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Reason" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Error" + }, + "accepted_at": { + "format": "date-time", + "title": "Accepted At", + "type": "string" + }, + "started_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Started At" + }, + "completed_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Completed At" + }, + "attempt_count": { + "default": 0, + "title": "Attempt Count", + "type": "integer" + }, + "input_manifest": { + "anyOf": [ + { + "$ref": "#/$defs/EvidenceManifest" + }, + { + "type": "null" + } + ], + "default": null + }, + "usage": { + "$ref": "#/$defs/DreamUsage" + }, + "budget": { + "$ref": "#/$defs/DreamBudget" + }, + "prompt_version": { + "default": "powercontext.dream.v1", + "title": "Prompt Version", + "type": "string" + }, + "model_config_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Config Id" + } + }, + "required": [ + "scope_id", + "run_id", + "operation", + "accepted_at" + ], + "title": "DreamRun", + "type": "object" + }, + "DreamUsage": { + "properties": { + "model_calls": { + "default": 0, + "minimum": 0, + "title": "Model Calls", + "type": "integer" + }, + "input_tokens": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Tokens" + }, + "output_tokens": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Tokens" + } + }, + "title": "DreamUsage", + "type": "object" + }, + "EvidenceEdge": { + "additionalProperties": false, + "properties": { + "derived_id": { + "title": "Derived Id", + "type": "string" + }, + "upstream_id": { + "title": "Upstream Id", + "type": "string" + } + }, + "required": [ + "derived_id", + "upstream_id" + ], + "title": "EvidenceEdge", + "type": "object" + }, + "EvidenceManifest": { + "additionalProperties": false, + "properties": { + "transform_version": { + "default": "powercontext.dream.evidence.v1", + "title": "Transform Version", + "type": "string" + }, + "artifacts": { + "default": [], + "items": { + "$ref": "#/$defs/ArtifactRef" + }, + "title": "Artifacts", + "type": "array" + }, + "memory_citations": { + "default": [], + "items": { + "$ref": "#/$defs/MemoryCitation" + }, + "title": "Memory Citations", + "type": "array" + }, + "sources": { + "default": [], + "items": { + "$ref": "#/$defs/SourceRef" + }, + "title": "Sources", + "type": "array" + }, + "nodes": { + "default": [], + "items": { + "$ref": "#/$defs/EvidenceNode" + }, + "title": "Nodes", + "type": "array" + }, + "edges": { + "default": [], + "items": { + "$ref": "#/$defs/EvidenceEdge" + }, + "title": "Edges", + "type": "array" + }, + "root_groups": { + "default": [], + "items": { + "$ref": "#/$defs/RootEvidenceGroup" + }, + "title": "Root Groups", + "type": "array" + }, + "projection_digest": { + "title": "Projection Digest", + "type": "string" + }, + "projection_bytes": { + "title": "Projection Bytes", + "type": "integer" + }, + "incomplete": { + "default": false, + "title": "Incomplete", + "type": "boolean" + } + }, + "required": [ + "projection_digest", + "projection_bytes" + ], + "title": "EvidenceManifest", + "type": "object" + }, + "EvidenceNode": { + "additionalProperties": false, + "description": "An immutable content digest and its exact provenance, never its body.", + "properties": { + "evidence_id": { + "title": "Evidence Id", + "type": "string" + }, + "kind": { + "enum": [ + "source", + "experience", + "memory", + "profile", + "topic_memory", + "handoff", + "skill", + "prompt", + "catalog_target", + "unresolved" + ], + "title": "Kind", + "type": "string" + }, + "digest": { + "title": "Digest", + "type": "string" + }, + "source": { + "anyOf": [ + { + "$ref": "#/$defs/SourceRef" + }, + { + "type": "null" + } + ], + "default": null + }, + "artifact": { + "anyOf": [ + { + "$ref": "#/$defs/ArtifactRef" + }, + { + "type": "null" + } + ], + "default": null + }, + "memory_citations": { + "default": [], + "items": { + "$ref": "#/$defs/MemoryCitation" + }, + "title": "Memory Citations", + "type": "array" + }, + "role": { + "enum": [ + "root", + "derived", + "target", + "lineage_only", + "unresolved" + ], + "title": "Role", + "type": "string" + }, + "historical": { + "default": false, + "title": "Historical", + "type": "boolean" + }, + "current_entry_version_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Current Entry Version Id" + } + }, + "required": [ + "evidence_id", + "kind", + "digest", + "role" + ], + "title": "EvidenceNode", + "type": "object" + }, + "MemoryCitation": { + "description": "An exact entry version anchored in its owning Memory Revision.", + "properties": { + "memory_ref": { + "$ref": "#/$defs/ArtifactRef" + }, + "entry_id": { + "title": "Entry Id", + "type": "string" + }, + "entry_version_id": { + "title": "Entry Version Id", + "type": "string" + } + }, + "required": [ + "memory_ref", + "entry_id", + "entry_version_id" + ], + "title": "MemoryCitation", + "type": "object" + }, + "RootEvidenceGroup": { + "additionalProperties": false, + "properties": { + "group_id": { + "title": "Group Id", + "type": "string" + }, + "sources": { + "items": { + "$ref": "#/$defs/SourceRef" + }, + "title": "Sources", + "type": "array" + }, + "independence": { + "default": "unknown", + "enum": [ + "attested", + "unknown" + ], + "title": "Independence", + "type": "string" + } + }, + "required": [ + "group_id", + "sources" + ], + "title": "RootEvidenceGroup", + "type": "object" + }, + "SourceRef": { + "description": "A stable reference to one Source in the current catalog view.", + "properties": { + "source_type": { + "title": "Source Type", + "type": "string" + }, + "source_id": { + "title": "Source Id", + "type": "string" + } + }, + "required": [ + "source_type", + "source_id" + ], + "title": "SourceRef", + "type": "object" + } + }, + "description": "Private execution state; a principal identity is never a credential.", + "properties": { + "run": { + "$ref": "#/$defs/DreamRun" + }, + "request": { + "$ref": "#/$defs/CreateDreamRunRequest" + }, + "principal_id": { + "title": "Principal Id", + "type": "string" + }, + "generation": { + "default": 0, + "title": "Generation", + "type": "integer" + }, + "request_generation": { + "default": 0, + "title": "Request Generation", + "type": "integer" + }, + "deadline_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Deadline At" + } + }, + "required": [ + "run", + "request", + "principal_id" + ], + "title": "DreamRecord", + "type": "object" + }, + "processing_manifest": { + "schema_version": 1515, + "bindings": { + "memory-source-window": "memory", + "topic-memory-source-window": "topic-memory", + "experience-incubation": "experience", + "profile-source-window": "profile", + "skill.dream.v1": "skill" + }, + "binding_additions": { + "handoff.dream.v1": "handoff", + "prompt.dream.v1": "prompt" + }, + "capability_additions": [ + "handoff", + "prompt" + ], + "schema": { + "type": "object", + "additionalProperties": false, + "required": [ + "mode", + "bindings", + "capabilities", + "legacy_automatic_bindings" + ], + "properties": { + "mode": { + "enum": [ + "global", + "dedicated" + ] + }, + "bindings": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "capabilities": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "memory", + "experience", + "profile", + "topic-memory", + "skill", + "handoff", + "prompt" + ] + } + }, + "legacy_automatic_bindings": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "memory-source-window", + "topic-memory-source-window", + "experience-incubation", + "profile-source-window", + "skill.dream.v1" + ] + } + } + } + } + }, + "target_base_commit": "6541705794b5f40a7ccc71c862cfb54a61890feb", + "source_prerequisite": "Atomic Memory reference conversion and legacy citation column removal are complete." +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/dream/upgrade.py b/src/powercontext/builtin/persistence/migrations/server_resources/dream/upgrade.py new file mode 100644 index 0000000000..abbc0adebc --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/dream/upgrade.py @@ -0,0 +1,371 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Dream's frozen transformation for an RFC 1771 Alembic revision. + +The framework must recognize the complete baseline, stop writers, hold its lock, +apply the selected backup policy and own the supplied connection/transaction. +This module never opens connections, commits, stamps, locks, or resumes DDL. +It is not a migration runner or a full-database readiness check. +""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from typing import Any + +from jsonschema import Draft202012Validator, FormatChecker +from sqlalchemy import Connection, inspect, text + +RESOURCE_DIRECTORY = Path(__file__).parent + + +class DreamUpgradeError(RuntimeError): + """A domain precondition or postcondition failed; framework recovery applies.""" + + def __init__(self, reason: str) -> None: + self.reason = reason + super().__init__(reason) + + +def _schema() -> dict[str, Any]: + return json.loads((RESOURCE_DIRECTORY / "schema.json").read_text(encoding="utf-8")) + + +def validate_dream_tasks(connection: Connection) -> None: + """Legacy operations keep their original payloads; unknown formats block DDL. + + Other queues and lease protocols belong to the full framework's task inventory. + This recognizer intentionally accepts only the released pre-expansion operations. + """ + names = set(inspect(connection).get_table_names()) + if "pc_dream_runs" not in names: + raise DreamUpgradeError("unknown_baseline") + validator = Draft202012Validator(_schema()["task_payload_schema"], format_checker=FormatChecker()) + versions = "pc_candidate_versions" if "pc_candidate_versions" in names else "pc_artifact_candidate_versions" + rows = connection.exec_driver_sql( + "SELECT scope_id, run_id, principal_key, idempotency_key, operation, status, generation, " + "request_generation, payload FROM pc_dream_runs" + ).mappings() + for row in rows: + try: + payload = json.loads(row["payload"]) + run, request = payload["run"], payload["request"] + historical = request is None + checked_payload = payload + if historical: + history = run["historical_data"] + if ( + history["format"] != "powercontext.dream-history.v1" + or set(history) != {"format", "request", "input_manifest", "request_digest"} + or row["status"] not in {"succeeded", "failed"} + or run.get("input_manifest") is not None + or not isinstance(history["request_digest"], str) + ): + raise DreamUpgradeError("unsupported_task_format") + request = history["request"] + checked_payload = { + **payload, + "request": request, + "run": {**run, "input_manifest": history["input_manifest"]}, + } + valid = ( + validator.is_valid(checked_payload) + and row["operation"] in {"refine_experience", "derive_skill"} + and row["status"] in {"queued", "running", "succeeded", "failed"} + and run["operation"] == request["operation"] == row["operation"] + and run["status"] == row["status"] + and run["scope_id"] == row["scope_id"] + and run["run_id"] == row["run_id"] + and payload.get("generation", 0) == row["generation"] + and payload.get("request_generation", 0) == row["request_generation"] + and isinstance(payload["principal_id"], str) + and bool(payload["principal_id"]) + and hashlib.sha256(payload["principal_id"].encode()).hexdigest() == row["principal_key"] + and request["idempotency_key"] == row["idempotency_key"] + and _valid_selection(request) + and (historical or not request.get("memory_citations")) + ) + except (ValueError, TypeError, KeyError): + raise DreamUpgradeError("unsupported_task_format") from None + if not valid: + raise DreamUpgradeError("unsupported_task_format") + # The target release cannot execute the frozen legacy prompt contract. + # Check this before any DDL, including queued retries and expired runs; + # only the old runtime may finish them under their original budget. + if row["status"] in {"queued", "running"}: + raise DreamUpgradeError("dream_tasks_require_drain") + candidate = run.get("candidate") + if ( + candidate is not None + and connection.execute( + text( + f"SELECT 1 FROM {versions} WHERE scope_id=:scope AND candidate_id=:candidate AND version=:version" # noqa: S608 + ), + {"scope": row["scope_id"], "candidate": candidate["candidate_id"], "version": candidate["version"]}, + ).first() + is None + ): + raise DreamUpgradeError("dream_candidate_reference_invalid") + + +def _valid_selection(request: dict[str, Any]) -> bool: + """Frozen semantic rules not expressible by the historical model's JSON schema.""" + artifacts = request.get("artifacts", []) + citations = request.get("memory_citations", []) + sources = request.get("sources", []) + target = request.get("target") + selected = len(artifacts) + len(citations) + key = request["idempotency_key"] + return ( + key == key.strip() + and 1 <= selected <= 20 + and selected + len(sources) <= 32 + and all(ref["family"] in {"experience", "atomic-memory"} for ref in artifacts) + and all( + ref["memory_ref"]["family"] == "memory" + and 1 <= len(ref["entry_id"]) <= 128 + and 1 <= len(ref["entry_version_id"]) <= 128 + for ref in citations + ) + and (target is None or (target in artifacts and target["family"] == "experience")) + and ( + request["operation"] != "derive_skill" + or ( + not citations + and target is None + and bool(artifacts) + and all(ref["family"] == "experience" for ref in artifacts) + ) + ) + ) + + +def upgrade_dream_storage(connection: Connection, *, target_manifest: dict[str, Any]) -> None: + """Transform the recognized legacy shape on the framework's pinned connection. + + SQLite needs foreign_keys disabled *before* the framework starts its transaction, + then validates all live foreign keys before revision advancement. Remote DDL is + not atomic; a failure is left to framework recovery, never retried here. + """ + schema = _schema() + dialect = connection.dialect.name + if dialect not in {"sqlite", "mysql"}: + raise DreamUpgradeError("unsupported_backend") + if not connection.in_transaction(): + raise DreamUpgradeError("maintenance_connection_required") + if dialect == "sqlite" and connection.exec_driver_sql("PRAGMA foreign_keys").scalar(): + raise DreamUpgradeError("maintenance_connection_required") + _validate_source(connection, schema) + validate_dream_tasks(connection) + upgraded_manifest = validate_dream_processing_manifest(connection, target_manifest=target_manifest) + + for entry in schema["tables"].values(): + # Retained copies have no live FKs: deleting a Scope or Artifact must + # not cascade into the recovery snapshot. The frozen PK/checks remain. + connection.exec_driver_sql(entry["retained"][dialect]) + fields = ", ".join(entry["source_columns"]) + connection.exec_driver_sql( + f"INSERT INTO {entry['retained_name']} ({fields}) SELECT {fields} FROM {entry['source_name']}" # noqa: S608 + ) + for entry in schema["tables"].values(): + connection.exec_driver_sql(f"ALTER TABLE {entry['source_name']} RENAME TO {entry['target_name']}") + if dialect == "sqlite": + _sqlite_candidates(connection, schema) + else: + _mysql_candidates(connection, schema) + for table, addition in schema["add_columns"].items(): + connection.exec_driver_sql(f"ALTER TABLE {table} ADD COLUMN {addition['ddl'][dialect]}") + connection.exec_driver_sql(schema["indexes"][dialect]) + connection.execute( + text("UPDATE pc_artifact_processing_schema SET config_manifest=:manifest WHERE singleton=1"), + {"manifest": json.dumps(upgraded_manifest, sort_keys=True, separators=(",", ":"), ensure_ascii=False)}, + ) + if _processing_manifest(connection, schema, upgraded=True) != upgraded_manifest: + raise DreamUpgradeError("processing_manifest_verification_failed") + verify_dream_storage(connection) + + +def validate_dream_processing_manifest(connection: Connection, *, target_manifest: dict[str, Any]) -> dict[str, Any]: + """Validate the deployment declaration for planning and again before writes. + + The caller supplies the target deployment's canonical manifest. The legacy + record cannot distinguish explicit capabilities from inferred defaults. + This frozen contribution permits only its own additive capability changes; + ownership changes and removal of existing capabilities need separate maintenance. + """ + schema = _schema() + previous = _processing_manifest(connection, schema, upgraded=False) + contract = schema["processing_manifest"] + if not Draft202012Validator(contract["schema"]).is_valid(target_manifest): + raise DreamUpgradeError("incompatible_target_processing_manifest") + old_capabilities = set(previous["capabilities"]) + target_capabilities = set(target_manifest["capabilities"]) + if ( + target_manifest["mode"] != previous["mode"] + or target_manifest["bindings"] != {**contract["bindings"], **contract["binding_additions"]} + or not old_capabilities <= target_capabilities + or not target_capabilities - old_capabilities <= set(contract["capability_additions"]) + ): + raise DreamUpgradeError("incompatible_target_processing_manifest") + return { + **target_manifest, + "capabilities": sorted(target_capabilities), + # Preserve historical recovery obligations regardless of new schedules. + "legacy_automatic_bindings": previous["legacy_automatic_bindings"], + } + + +def _processing_manifest(connection: Connection, schema: dict[str, Any], *, upgraded: bool) -> dict[str, Any]: + """Recognize the frozen ownership manifest without reading runtime configuration.""" + if "pc_artifact_processing_schema" not in inspect(connection).get_table_names(): + raise DreamUpgradeError("unknown_processing_manifest") + rows = ( + connection + .exec_driver_sql("SELECT singleton, schema_version, phase, config_manifest FROM pc_artifact_processing_schema") + .mappings() + .all() + ) + contract = schema["processing_manifest"] + if ( + len(rows) != 1 + or rows[0]["singleton"] != 1 + or rows[0]["schema_version"] != contract["schema_version"] + or rows[0]["phase"] != "complete" + ): + raise DreamUpgradeError("unknown_processing_manifest") + try: + manifest = json.loads(rows[0]["config_manifest"]) + except (ValueError, TypeError): + raise DreamUpgradeError("unknown_processing_manifest") from None + bindings = {**contract["bindings"], **(contract["binding_additions"] if upgraded else {})} + if not Draft202012Validator(contract["schema"]).is_valid(manifest) or manifest["bindings"] != bindings: + raise DreamUpgradeError("unknown_processing_manifest") + additions = set(contract["capability_additions"]) + if not upgraded and set(manifest["capabilities"]) & additions: + raise DreamUpgradeError("unknown_processing_manifest") + return manifest + + +def _validate_source(connection: Connection, schema: dict[str, Any]) -> None: + names = set(inspect(connection).get_table_names()) + for entry in schema["tables"].values(): + if entry["source_name"] not in names or {entry["target_name"], entry["retained_name"]} & names: + raise DreamUpgradeError("unknown_baseline") + actual = [column["name"] for column in inspect(connection).get_columns(entry["source_name"])] + if actual != entry["source_columns"]: + raise DreamUpgradeError("unknown_baseline") + if names & {"pc_catalog_change_candidate_heads", "pc_catalog_change_candidate_versions"}: + raise DreamUpgradeError("unknown_baseline") + for table, addition in schema["add_columns"].items(): + if table not in names or addition["column"] in {c["name"] for c in inspect(connection).get_columns(table)}: + raise DreamUpgradeError("unknown_baseline") + if "pc_profile_policies" not in names: + raise DreamUpgradeError("unknown_baseline") + + +def _sqlite_candidates(connection: Connection, schema: dict[str, Any]) -> None: + for entry in schema["tables"].values(): + name = entry["target_name"] + temporary = "pc_dream_upgrade_" + name.removeprefix("pc_") + connection.exec_driver_sql( + entry["target"]["sqlite"].replace("CREATE TABLE " + name, "CREATE TABLE " + temporary, 1) + ) + fields = ", ".join(entry["source_columns"]) + connection.exec_driver_sql(f"INSERT INTO {temporary} ({fields}) SELECT {fields} FROM {name}") # noqa: S608 + # The earlier rename rewrote Profile's FK. Rebuilding without another live + # table rename preserves that reference to the final head table. + for entry in reversed(list(schema["tables"].values())): + connection.exec_driver_sql(f"DROP TABLE {entry['target_name']}") + for entry in schema["tables"].values(): + name = entry["target_name"] + temporary = "pc_dream_upgrade_" + name.removeprefix("pc_") + connection.exec_driver_sql(f"ALTER TABLE {temporary} RENAME TO {name}") + + +def _mysql_candidates(connection: Connection, schema: dict[str, Any]) -> None: + heads = schema["tables"]["pc_candidate_heads"] + for field in ("candidate_kind", "result_payload"): + declaration = next( + line.strip().rstrip(",") + for line in heads["target"]["mysql"].splitlines() + if line.strip().startswith(field + " ") + ) + connection.exec_driver_sql(f"ALTER TABLE pc_candidate_heads ADD COLUMN {declaration}") + quote = connection.dialect.identifier_preparer.quote + for entry in schema["tables"].values(): + name = entry["target_name"] + for check in inspect(connection).get_check_constraints(name): + constraint_name = check["name"] + if constraint_name is None: + raise DreamUpgradeError("unknown_baseline") + connection.exec_driver_sql(f"ALTER TABLE {name} DROP CHECK {quote(constraint_name)}") + for line in entry["target"]["mysql"].splitlines(): + if line.strip().startswith("CONSTRAINT "): + connection.exec_driver_sql(f"ALTER TABLE {name} ADD {line.strip().rstrip(',')}") + connection.exec_driver_sql( + f"ALTER TABLE {name} ADD CONSTRAINT fk_{name}_scope FOREIGN KEY (scope_id) " + "REFERENCES pc_scopes (scope_id) ON DELETE CASCADE" + ) + for key in inspect(connection).get_foreign_keys("pc_candidate_heads"): + if key["constrained_columns"] == ["scope_id", "candidate_id", "version"]: + constraint_name = key["name"] + if constraint_name is None: + raise DreamUpgradeError("unknown_baseline") + connection.exec_driver_sql(f"ALTER TABLE pc_candidate_heads DROP FOREIGN KEY {quote(constraint_name)}") + connection.exec_driver_sql( + "ALTER TABLE pc_candidate_heads ADD CONSTRAINT fk_pc_candidate_head_version " + "FOREIGN KEY (scope_id, candidate_id, version) REFERENCES pc_candidate_versions " + "(scope_id, candidate_id, version) ON DELETE CASCADE" + ) + + +def verify_dream_storage(connection: Connection) -> None: + """Check copied values and domain links; not a declaration of Server readiness.""" + schema = _schema() + _processing_manifest(connection, schema, upgraded=True) + for entry in schema["tables"].values(): + fields = ", ".join(entry["source_columns"]) + order = "scope_id, candidate_id" + (", version" if entry["target_name"].endswith("versions") else "") + old = connection.exec_driver_sql(f"SELECT {fields} FROM {entry['retained_name']} ORDER BY {order}") # noqa: S608 + new = connection.exec_driver_sql(f"SELECT {fields} FROM {entry['target_name']} ORDER BY {order}") # noqa: S608 + while True: + left, right = old.fetchmany(256), new.fetchmany(256) + if left != right: + raise DreamUpgradeError("candidate_copy_mismatch") + if not left: + break + invalid = connection.exec_driver_sql( + "SELECT 1 FROM pc_candidate_heads h LEFT JOIN pc_candidate_versions v ON " + "h.scope_id=v.scope_id AND h.candidate_id=v.candidate_id AND h.version=v.version " + "WHERE v.candidate_id IS NULL OR h.candidate_kind <> 'artifact' OR h.result_payload IS NOT NULL LIMIT 1" + ).first() + if invalid: + raise DreamUpgradeError("candidate_verification_failed") + invalid = connection.exec_driver_sql( + "SELECT 1 FROM pc_profile_policies p LEFT JOIN pc_candidate_heads h ON " + "p.scope_id=h.scope_id AND p.pending_candidate_id=h.candidate_id " + "WHERE p.pending_candidate_id IS NOT NULL AND h.candidate_id IS NULL LIMIT 1" + ).first() + if invalid: + raise DreamUpgradeError("profile_candidate_reference_invalid") + if connection.exec_driver_sql( + "SELECT 1 FROM pc_artifact_processing_intents WHERE consecutive_dream_attempts <> 0 LIMIT 1" + ).first(): + raise DreamUpgradeError("processing_state_invalid") + if connection.dialect.name == "sqlite" and connection.exec_driver_sql("PRAGMA foreign_key_check").first(): + raise DreamUpgradeError("foreign_key_verification_failed") + validate_dream_tasks(connection) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/env.py b/src/powercontext/builtin/persistence/migrations/server_resources/env.py new file mode 100644 index 0000000000..90315cee59 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/env.py @@ -0,0 +1,26 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Run a revision on the executor's pinned connection; never open another engine.""" + +from alembic import context + +connection = context.config.attributes["connection"] +context.configure( + connection=connection, + version_table="pc_schema_revision", + transactional_ddl=connection.dialect.name == "sqlite", +) +with context.begin_transaction(): + context.run_migrations() diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/manifest.json b/src/powercontext/builtin/persistence/migrations/server_resources/manifest.json new file mode 100644 index 0000000000..5664018968 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/manifest.json @@ -0,0 +1,206 @@ +{ + "revision_resources": { + "s1300": [ + "schemas/sqlite.json", + "mysql_operations.py", + "schemas/mysql-s1300.json" + ], + "s1400": [ + "schemas/sqlite.json", + "dream/upgrade.py", + "dream/schema.json", + "dream/manifest.json", + "mysql_operations.py", + "schemas/mysql-s1400.json" + ], + "s1401": [ + "schemas/sqlite-s1401.json", + "schemas/mysql-s1401.json" + ], + "s1402": [ + "schemas/sqlite-s1402.json", + "schemas/mysql-s1402.json" + ] + }, + "sqlite_baselines": { + "11e8770068a5fe32dfb25681564dbbe586ba0fd0238ed01adf1b09dde8a49f22": "s1300" + }, + "sqlite_fingerprints": { + "s1300": [ + "11e8770068a5fe32dfb25681564dbbe586ba0fd0238ed01adf1b09dde8a49f22" + ], + "s1400": [ + "9064ecb0c6233884e64312a1a9a48a6c52b7c379724fa83f9b77b90ed1ee0381" + ], + "s1401": [ + "8752beed9acfb66e03d4360f8652661107b58067ae7a9c461f1035345edf4f27" + ], + "s1402": [ + "795b6fd01188c15d16625c59506aadc42134dc8293ffdd4a1588a04f8f66e877" + ] + }, + "affected_objects": [ + "pc_access_audit", + "pc_access_idempotency", + "pc_access_owners", + "pc_access_relationship_heads", + "pc_access_relationships", + "pc_agent_skill_targets", + "pc_artifact_candidate_heads", + "pc_artifact_candidate_versions", + "pc_artifact_fts", + "pc_artifact_fts_config", + "pc_artifact_fts_content", + "pc_artifact_fts_data", + "pc_artifact_fts_docsize", + "pc_artifact_fts_idx", + "pc_artifact_handles", + "pc_artifact_heads", + "pc_artifact_lineage_artifacts", + "pc_artifact_lineage_sources", + "pc_artifact_processing_auto_wave_targets", + "pc_artifact_processing_binding_states", + "pc_artifact_processing_intents", + "pc_artifact_processing_leases", + "pc_artifact_processing_migration_receipts", + "pc_artifact_processing_pending", + "pc_artifact_processing_schema", + "pc_artifact_processing_sequences", + "pc_artifact_publications", + "pc_artifact_tags", + "pc_artifacts", + "pc_atomic_memory_current", + "pc_atomic_memory_current_fts", + "pc_atomic_memory_current_fts_config", + "pc_atomic_memory_current_fts_content", + "pc_atomic_memory_current_fts_data", + "pc_atomic_memory_current_fts_docsize", + "pc_atomic_memory_current_fts_idx", + "pc_candidate_heads", + "pc_candidate_versions", + "pc_connector_checkpoints", + "pc_context_bootstrap_receipts", + "pc_decision_observations", + "pc_dream_runs", + "pc_external_skill_registrations", + "pc_lexical_projection_versions", + "pc_memory_entry_heads", + "pc_memory_entry_versions", + "pc_memory_source_windows", + "pc_model_usage_daily", + "pc_profile_policies", + "pc_recall_effort_daily", + "pc_recall_token_daily", + "pc_receipt_migration_review", + "pc_recurrence_match", + "pc_recurrence_observation", + "pc_scope_bindings", + "pc_scope_context_references", + "pc_scope_creation_requests", + "pc_scope_external_references", + "pc_scope_settings", + "pc_scopes", + "pc_server_identity", + "pc_skill_packages", + "pc_skill_publications", + "pc_source_cursors", + "pc_source_definition_manifests", + "pc_source_journal_heads", + "pc_sources", + "pc_topic_memory_active_chunks", + "pc_topic_memory_active_topics", + "pc_topic_memory_chunk_fts", + "pc_topic_memory_chunk_fts_config", + "pc_topic_memory_chunk_fts_content", + "pc_topic_memory_chunk_fts_data", + "pc_topic_memory_chunk_fts_docsize", + "pc_topic_memory_chunk_fts_idx", + "pc_topic_memory_fts_index", + "pc_topic_memory_processing_targets", + "pc_topic_memory_retrieval_shape", + "pc_topic_memory_revision_publications", + "pc_topic_memory_topic_fts", + "pc_topic_memory_topic_fts_config", + "pc_topic_memory_topic_fts_content", + "pc_topic_memory_topic_fts_data", + "pc_topic_memory_topic_fts_docsize", + "pc_topic_memory_topic_fts_idx", + "pc_topic_memory_work_blocks", + "pc_topic_memory_work_budgets", + "pc_retained_dream_candidate_heads", + "pc_retained_dream_candidate_versions", + "pc_memory_artifact_archive", + "pc_portable_restore_receipts" + ], + "readiness_scope": "server", + "retained_objects": [ + "pc_retained_dream_candidate_heads", + "pc_retained_dream_candidate_versions" + ], + "retained_revisions": { + "pc_retained_dream_candidate_heads": "s1400", + "pc_retained_dream_candidate_versions": "s1400" + }, + "domain_steps": [ + { + "id": "server_upgrade", + "revision": "s1400", + "kind": "projection", + "module": "server_domain.py", + "resources": [ + "dream/upgrade.py", + "dream/schema.json", + "atomic_memory_readiness_1300.py" + ], + "backends": [ + "sqlite", + "seekdb", + "oceanbase" + ] + }, + { + "id": "portable_storage", + "revision": "s1402", + "kind": "data", + "module": "portable_storage.py", + "depends_on": [ + "server_upgrade" + ], + "backends": [ + "sqlite", + "seekdb", + "oceanbase" + ] + } + ], + "domain_readiness": { + "s1400": [ + "server_upgrade" + ], + "s1401": [ + "server_upgrade" + ], + "s1402": [ + "server_upgrade", + "portable_storage" + ] + }, + "retained_fingerprints": { + "pc_retained_dream_candidate_heads": [ + "4d94550e19b53534436c5870854db32dd2ac272bebb5a2dec57214fef3e6d06e" + ], + "pc_retained_dream_candidate_versions": [ + "eb338523d955338684cd3ad17d4393c32475cf615285515618fe61d80f74d236" + ] + }, + "mysql_state_layouts": [ + "schemas/mysql-s1300.json", + "schemas/mysql-s1400.json", + "schemas/mysql-s1401.json", + "schemas/mysql-s1402.json" + ], + "mysql_history": "mysql", + "mysql_schema_extensions": { + "seekdb_code": "profiles/seekdb-code.json" + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/mysql/env.py b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/env.py new file mode 100644 index 0000000000..2db6a78803 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/env.py @@ -0,0 +1,26 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Advance the standard Alembic head only after a registered DDL postcondition.""" + +from alembic import context + +context.configure( + connection=context.config.attributes["connection"], + version_table="pc_schema_revision", + transactional_ddl=False, + transaction_per_migration=True, +) +with context.begin_transaction(): + context.run_migrations() diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1300.py b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1300.py new file mode 100644 index 0000000000..88bf2adbfb --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1300.py @@ -0,0 +1,24 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Initialize the frozen MySQL baseline with declared partial-DDL recovery.""" + +from alembic import context + +revision = "s1300" +down_revision = None + + +def upgrade() -> None: + context.config.attributes["execute_revision"](revision) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1400.py b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1400.py new file mode 100644 index 0000000000..bae2f22585 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1400.py @@ -0,0 +1,24 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Initialize the frozen MySQL baseline with declared partial-DDL recovery.""" + +from alembic import context + +revision = "s1400" +down_revision = "s1300" + + +def upgrade() -> None: + context.config.attributes["execute_revision"](revision) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1401.py b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1401.py new file mode 100644 index 0000000000..1c524326af --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1401.py @@ -0,0 +1,24 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Register offline Memory history through the shared MySQL DDL executor.""" + +from alembic import context + +revision = "s1401" +down_revision = "s1400" + + +def upgrade() -> None: + context.config.attributes["execute_revision"](revision) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1402.py b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1402.py new file mode 100644 index 0000000000..c89b377dfb --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/mysql/versions/s1402.py @@ -0,0 +1,24 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Register portable receipts and timestamp precision through the shared DDL executor.""" + +from alembic import context + +revision = "s1402" +down_revision = "s1401" + + +def upgrade() -> None: + context.config.attributes["execute_revision"](revision) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/mysql_operations.py b/src/powercontext/builtin/persistence/migrations/server_resources/mysql_operations.py new file mode 100644 index 0000000000..3db12b1105 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/mysql_operations.py @@ -0,0 +1,53 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Frozen dynamic identifiers for Server initialization and Candidate conversion.""" + +import copy +import json + +from sqlalchemy import inspect, text + +from powercontext.builtin.persistence.migrations.models import MigrationError + + +def quote(identifier): + return "`" + identifier.replace("`", "``") + "`" + + +def execute(connection, statement, configuration): + if statement == "@initialize_processing": + manifest = copy.deepcopy(configuration["processing_manifest"]) + manifest["bindings"] = { + key: value for key, value in manifest["bindings"].items() if value not in {"handoff", "prompt"} + } + manifest["capabilities"] = [value for value in manifest["capabilities"] if value not in {"handoff", "prompt"}] + connection.execute( + text( + "INSERT INTO pc_artifact_processing_schema (singleton,schema_version,source_version,phase,migration_id,config_manifest) SELECT 1,1515,1515,'complete','fresh',:manifest WHERE NOT EXISTS (SELECT 1 FROM pc_artifact_processing_schema)" + ), + {"manifest": json.dumps(manifest, sort_keys=True, separators=(",", ":"))}, + ) + elif statement == "@drop_candidate_head_version_fk": + matches = [ + key + for key in inspect(connection).get_foreign_keys("pc_candidate_heads") + if key["constrained_columns"] == ["scope_id", "candidate_id", "version"] + and key["referred_table"] == "pc_candidate_versions" + ] + if len(matches) != 1 or not matches[0]["name"]: + raise MigrationError("incompatible_schema", "Candidate foreign-key identity is ambiguous.") + connection.exec_driver_sql("ALTER TABLE pc_candidate_heads DROP FOREIGN KEY " + quote(matches[0]["name"])) + else: + connection.exec_driver_sql(statement, execution_options={"no_parameters": True}) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/portable_storage.py b/src/powercontext/builtin/persistence/migrations/server_resources/portable_storage.py new file mode 100644 index 0000000000..fdba225e5b --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/portable_storage.py @@ -0,0 +1,40 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Require completed portable restores before admitting managed Server storage.""" + +from powercontext.builtin.persistence.migrations.models import MigrationError + + +def preflight(connection, *, configuration): + """Earlier registered layouts have no portable restore receipts.""" + + +def apply(connection, *, configuration): + """Receipt creation and timestamp widening require no data rewrite.""" + + +def verify(connection, *, configuration): + validate(connection, configuration=configuration) + + +def validate(connection, *, configuration): + pending = connection.exec_driver_sql( + "SELECT 1 FROM pc_portable_restore_receipts WHERE projections_ready = 0 LIMIT 1" + ).first() + if pending is not None: + raise MigrationError( + "archive_recovery_required", + "Portable restore projections are incomplete; finish archive restore before serving requests.", + ) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/profiles/seekdb-code.json b/src/powercontext/builtin/persistence/migrations/server_resources/profiles/seekdb-code.json new file mode 100644 index 0000000000..f9a1b838b6 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/profiles/seekdb-code.json @@ -0,0 +1,507 @@ +{ + "format": "powercontext.mysql-schema-extension.v1", + "source_release": "1.3.0", + "source_revision": "s1300", + "backend": "seekdb", + "statements": [ + "CREATE TABLE `pc_code_generations` (\n `id` char(32) COLLATE utf8mb4_bin NOT NULL,\n `binding` char(64) COLLATE utf8mb4_bin NOT NULL,\n `content_hash` char(64) COLLATE utf8mb4_bin NOT NULL,\n `node_count` bigint(20) NOT NULL,\n `edge_count` bigint(20) NOT NULL,\n `payload_bytes` bigint(20) NOT NULL,\n PRIMARY KEY (`id`),\n KEY `ix_code_binding` (`binding`) BLOCK_SIZE 16384 LOCAL\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_code_nodes` (\n `generation_id` char(32) COLLATE utf8mb4_bin NOT NULL,\n `id` char(64) COLLATE utf8mb4_bin NOT NULL,\n `kind` varchar(32) COLLATE utf8mb4_bin NOT NULL,\n `path` text COLLATE utf8mb4_bin NOT NULL,\n `file_id` char(64) COLLATE utf8mb4_bin DEFAULT NULL,\n `parent_id` char(64) COLLATE utf8mb4_bin DEFAULT NULL,\n `name` text COLLATE utf8mb4_bin NOT NULL,\n `qualified_name` text COLLATE utf8mb4_bin NOT NULL,\n `start_line` bigint(20) NOT NULL,\n `end_line` bigint(20) NOT NULL,\n `payload` longtext COLLATE utf8mb4_bin NOT NULL,\n `searchable_text` longtext COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`generation_id`, `id`),\n KEY `ix_code_path` (`generation_id`, `path`(190), `start_line`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_code_name` (`generation_id`, `name`(190)) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_code_qualified` (`generation_id`, `qualified_name`(190)) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_code_parent` (`generation_id`, `parent_id`) BLOCK_SIZE 16384 LOCAL,\n FULLTEXT KEY `ix_code_search` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_code_edges` (\n `generation_id` char(32) COLLATE utf8mb4_bin NOT NULL,\n `ordinal` bigint(20) NOT NULL,\n `source_id` char(64) COLLATE utf8mb4_bin NOT NULL,\n `target_id` char(64) COLLATE utf8mb4_bin NOT NULL,\n `kind` varchar(32) COLLATE utf8mb4_bin NOT NULL,\n `resolution` varchar(32) COLLATE utf8mb4_bin NOT NULL,\n `payload` longtext COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`generation_id`, `ordinal`),\n KEY `ix_code_source` (`generation_id`, `source_id`, `kind`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_code_target` (`generation_id`, `target_id`, `kind`) BLOCK_SIZE 16384 LOCAL\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0" + ], + "tables": { + "pc_code_generations": { + "columns": { + "id": { + "type": [ + "char", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding": { + "type": [ + "char", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "content_hash": { + "type": [ + "char", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "node_count": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "edge_count": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload_bytes": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "ix_code_binding": { + "columns": [ + [ + "binding", + null, + "asc" + ] + ], + "unique": false + } + } + }, + "pc_code_nodes": { + "columns": { + "generation_id": { + "type": [ + "char", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "id": { + "type": [ + "char", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "path": { + "type": [ + "text", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "file_id": { + "type": [ + "char", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "parent_id": { + "type": [ + "char", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "name": { + "type": [ + "text", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "qualified_name": { + "type": [ + "text", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "start_line": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "end_line": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "longtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "searchable_text": { + "type": [ + "longtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "generation_id", + null, + "asc" + ], + [ + "id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "ix_code_path": { + "columns": [ + [ + "generation_id", + null, + "asc" + ], + [ + "path", + 190, + "asc" + ], + [ + "start_line", + null, + "asc" + ] + ], + "unique": false + }, + "ix_code_name": { + "columns": [ + [ + "generation_id", + null, + "asc" + ], + [ + "name", + 190, + "asc" + ] + ], + "unique": false + }, + "ix_code_qualified": { + "columns": [ + [ + "generation_id", + null, + "asc" + ], + [ + "qualified_name", + 190, + "asc" + ] + ], + "unique": false + }, + "ix_code_parent": { + "columns": [ + [ + "generation_id", + null, + "asc" + ], + [ + "parent_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_code_search": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + }, + "pc_code_edges": { + "columns": { + "generation_id": { + "type": [ + "char", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "ordinal": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_id": { + "type": [ + "char", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "char", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "resolution": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "payload": { + "type": [ + "longtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "generation_id", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "ix_code_source": { + "columns": [ + [ + "generation_id", + null, + "asc" + ], + [ + "source_id", + null, + "asc" + ], + [ + "kind", + null, + "asc" + ] + ], + "unique": false + }, + "ix_code_target": { + "columns": [ + [ + "generation_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ], + [ + "kind", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + "provenance": { + "source_capture_sha256": "c7a2e0896b701a51e7a4998f69fb8fc53f0f33691150fb4d7cfa2bb66f214ffc", + "source_engine": "5.7.25-OceanBase seekdb-v1.4.0.0", + "source_wheel_sha256": "8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5", + "source_module": "/tmp/pc2084-release-130/lib/python3.11/site-packages/powercontext/__init__.py", + "scope": "schema captured from installed 1.3.0; upgrade and populated Code reuse require separate acceptance" + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/profiles/vector.json b/src/powercontext/builtin/persistence/migrations/server_resources/profiles/vector.json new file mode 100644 index 0000000000..ecce7ee1f8 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/profiles/vector.json @@ -0,0 +1,270 @@ +{ + "format": "powercontext.server-vector-profile.v1", + "source_release": "1.3.0", + "source_capture_sha256": { + "optional-release130-sqlite-vec3-20261023": "069f2153ac095fd00bfadbbb02b055d9bac54e4a8fd0f1c51dfa2ff88abccb92", + "optional-release130-oceanbase-vec3-e942bce93f": "b62b07ce28537fbdfcaeea9711bb6de1e3131794d0b76984c771527972ca876d" + }, + "sqlite": { + "source": [ + [ + "table", + "pc_topic_memory_chunk_vec", + "pc_topic_memory_chunk_vec", + "CREATE VIRTUAL TABLE pc_topic_memory_chunk_vec USING vec0(scope_id TEXT partition key, embedding float[__DIMENSION__])" + ], + [ + "table", + "pc_topic_memory_chunk_vec_chunks", + "pc_topic_memory_chunk_vec_chunks", + "CREATE TABLE \"pc_topic_memory_chunk_vec_chunks\"(chunk_id INTEGER PRIMARY KEY AUTOINCREMENT,size INTEGER NOT NULL,sequence_id integer,partition00,validity BLOB NOT NULL, rowids BLOB NOT NULL)" + ], + [ + "table", + "pc_topic_memory_chunk_vec_info", + "pc_topic_memory_chunk_vec_info", + "CREATE TABLE \"pc_topic_memory_chunk_vec_info\" (key text primary key, value any)" + ], + [ + "table", + "pc_topic_memory_chunk_vec_rowids", + "pc_topic_memory_chunk_vec_rowids", + "CREATE TABLE \"pc_topic_memory_chunk_vec_rowids\"(rowid INTEGER PRIMARY KEY AUTOINCREMENT,id,chunk_id INTEGER,chunk_offset INTEGER)" + ], + [ + "table", + "pc_topic_memory_chunk_vec_vector_chunks00", + "pc_topic_memory_chunk_vec_vector_chunks00", + "CREATE TABLE \"pc_topic_memory_chunk_vec_vector_chunks00\"(rowid PRIMARY KEY,vectors BLOB NOT NULL)" + ], + [ + "table", + "pc_topic_memory_topic_vec", + "pc_topic_memory_topic_vec", + "CREATE VIRTUAL TABLE pc_topic_memory_topic_vec USING vec0(scope_id TEXT partition key, embedding float[__DIMENSION__])" + ], + [ + "table", + "pc_topic_memory_topic_vec_chunks", + "pc_topic_memory_topic_vec_chunks", + "CREATE TABLE \"pc_topic_memory_topic_vec_chunks\"(chunk_id INTEGER PRIMARY KEY AUTOINCREMENT,size INTEGER NOT NULL,sequence_id integer,partition00,validity BLOB NOT NULL, rowids BLOB NOT NULL)" + ], + [ + "table", + "pc_topic_memory_topic_vec_info", + "pc_topic_memory_topic_vec_info", + "CREATE TABLE \"pc_topic_memory_topic_vec_info\" (key text primary key, value any)" + ], + [ + "table", + "pc_topic_memory_topic_vec_rowids", + "pc_topic_memory_topic_vec_rowids", + "CREATE TABLE \"pc_topic_memory_topic_vec_rowids\"(rowid INTEGER PRIMARY KEY AUTOINCREMENT,id,chunk_id INTEGER,chunk_offset INTEGER)" + ], + [ + "table", + "pc_topic_memory_topic_vec_vector_chunks00", + "pc_topic_memory_topic_vec_vector_chunks00", + "CREATE TABLE \"pc_topic_memory_topic_vec_vector_chunks00\"(rowid PRIMARY KEY,vectors BLOB NOT NULL)" + ], + [ + "table", + "pc_topic_memory_vector_chunks", + "pc_topic_memory_vector_chunks", + "CREATE TABLE pc_topic_memory_vector_chunks (\n\tvector_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tchunk_ordinal INTEGER NOT NULL, \n\tprofile_fingerprint VARCHAR(64) NOT NULL, \n\tCONSTRAINT uq_pc_topic_memory_vector_chunks_active UNIQUE (scope_id, artifact_id, chunk_ordinal)\n)" + ], + [ + "table", + "pc_topic_memory_vector_topics", + "pc_topic_memory_vector_topics", + "CREATE TABLE pc_topic_memory_vector_topics (\n\tvector_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tprofile_fingerprint VARCHAR(64) NOT NULL, \n\tCONSTRAINT uq_pc_topic_memory_vector_topics_active UNIQUE (scope_id, artifact_id)\n)" + ] + ], + "target": [ + [ + "table", + "pc_topic_memory_chunk_vec", + "pc_topic_memory_chunk_vec", + "CREATE VIRTUAL TABLE pc_topic_memory_chunk_vec USING vec0(scope_id TEXT partition key, embedding float[__DIMENSION__])" + ], + [ + "table", + "pc_topic_memory_chunk_vec_chunks", + "pc_topic_memory_chunk_vec_chunks", + "CREATE TABLE \"pc_topic_memory_chunk_vec_chunks\"(chunk_id INTEGER PRIMARY KEY AUTOINCREMENT,size INTEGER NOT NULL,sequence_id integer,partition00,validity BLOB NOT NULL, rowids BLOB NOT NULL)" + ], + [ + "table", + "pc_topic_memory_chunk_vec_info", + "pc_topic_memory_chunk_vec_info", + "CREATE TABLE \"pc_topic_memory_chunk_vec_info\" (key text primary key, value any)" + ], + [ + "table", + "pc_topic_memory_chunk_vec_rowids", + "pc_topic_memory_chunk_vec_rowids", + "CREATE TABLE \"pc_topic_memory_chunk_vec_rowids\"(rowid INTEGER PRIMARY KEY AUTOINCREMENT,id,chunk_id INTEGER,chunk_offset INTEGER)" + ], + [ + "table", + "pc_topic_memory_chunk_vec_vector_chunks00", + "pc_topic_memory_chunk_vec_vector_chunks00", + "CREATE TABLE \"pc_topic_memory_chunk_vec_vector_chunks00\"(rowid PRIMARY KEY,vectors BLOB NOT NULL)" + ], + [ + "table", + "pc_topic_memory_topic_vec", + "pc_topic_memory_topic_vec", + "CREATE VIRTUAL TABLE pc_topic_memory_topic_vec USING vec0(scope_id TEXT partition key, embedding float[__DIMENSION__])" + ], + [ + "table", + "pc_topic_memory_topic_vec_chunks", + "pc_topic_memory_topic_vec_chunks", + "CREATE TABLE \"pc_topic_memory_topic_vec_chunks\"(chunk_id INTEGER PRIMARY KEY AUTOINCREMENT,size INTEGER NOT NULL,sequence_id integer,partition00,validity BLOB NOT NULL, rowids BLOB NOT NULL)" + ], + [ + "table", + "pc_topic_memory_topic_vec_info", + "pc_topic_memory_topic_vec_info", + "CREATE TABLE \"pc_topic_memory_topic_vec_info\" (key text primary key, value any)" + ], + [ + "table", + "pc_topic_memory_topic_vec_rowids", + "pc_topic_memory_topic_vec_rowids", + "CREATE TABLE \"pc_topic_memory_topic_vec_rowids\"(rowid INTEGER PRIMARY KEY AUTOINCREMENT,id,chunk_id INTEGER,chunk_offset INTEGER)" + ], + [ + "table", + "pc_topic_memory_topic_vec_vector_chunks00", + "pc_topic_memory_topic_vec_vector_chunks00", + "CREATE TABLE \"pc_topic_memory_topic_vec_vector_chunks00\"(rowid PRIMARY KEY,vectors BLOB NOT NULL)" + ], + [ + "table", + "pc_topic_memory_vector_chunks", + "pc_topic_memory_vector_chunks", + "CREATE TABLE pc_topic_memory_vector_chunks (\n\tvector_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tchunk_ordinal INTEGER NOT NULL, \n\tprofile_fingerprint VARCHAR(64) NOT NULL, \n\tCONSTRAINT uq_pc_topic_memory_vector_chunks_active UNIQUE (scope_id, artifact_id, chunk_ordinal)\n)" + ], + [ + "table", + "pc_topic_memory_vector_topics", + "pc_topic_memory_vector_topics", + "CREATE TABLE pc_topic_memory_vector_topics (\n\tvector_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tprofile_fingerprint VARCHAR(64) NOT NULL, \n\tCONSTRAINT uq_pc_topic_memory_vector_topics_active UNIQUE (scope_id, artifact_id)\n)" + ], + [ + "table", + "pc_atomic_memory_current_vec", + "pc_atomic_memory_current_vec", + "CREATE VIRTUAL TABLE pc_atomic_memory_current_vec USING vec0(identity TEXT PRIMARY KEY, scope_id TEXT PARTITION KEY, kind TEXT, embedding float[__DIMENSION__], +artifact_id TEXT, +revision INTEGER, +state_version INTEGER, +text TEXT, chunk_size=128 /* __ATOMIC_FINGERPRINT__ */)" + ], + [ + "table", + "pc_atomic_memory_current_vec_auxiliary", + "pc_atomic_memory_current_vec_auxiliary", + "CREATE TABLE \"pc_atomic_memory_current_vec_auxiliary\"( rowid integer PRIMARY KEY , value00, value01, value02, value03)" + ], + [ + "table", + "pc_atomic_memory_current_vec_chunks", + "pc_atomic_memory_current_vec_chunks", + "CREATE TABLE \"pc_atomic_memory_current_vec_chunks\"(chunk_id INTEGER PRIMARY KEY AUTOINCREMENT,size INTEGER NOT NULL,sequence_id integer,partition00,validity BLOB NOT NULL, rowids BLOB NOT NULL)" + ], + [ + "table", + "pc_atomic_memory_current_vec_info", + "pc_atomic_memory_current_vec_info", + "CREATE TABLE \"pc_atomic_memory_current_vec_info\" (key text primary key, value any)" + ], + [ + "table", + "pc_atomic_memory_current_vec_metadatachunks00", + "pc_atomic_memory_current_vec_metadatachunks00", + "CREATE TABLE \"pc_atomic_memory_current_vec_metadatachunks00\"(rowid PRIMARY KEY, data BLOB NOT NULL)" + ], + [ + "table", + "pc_atomic_memory_current_vec_metadatatext00", + "pc_atomic_memory_current_vec_metadatatext00", + "CREATE TABLE \"pc_atomic_memory_current_vec_metadatatext00\"(rowid PRIMARY KEY, data TEXT)" + ], + [ + "table", + "pc_atomic_memory_current_vec_rowids", + "pc_atomic_memory_current_vec_rowids", + "CREATE TABLE \"pc_atomic_memory_current_vec_rowids\"(rowid INTEGER PRIMARY KEY AUTOINCREMENT,id TEXT UNIQUE NOT NULL,chunk_id INTEGER,chunk_offset INTEGER)" + ], + [ + "table", + "pc_atomic_memory_current_vec_vector_chunks00", + "pc_atomic_memory_current_vec_vector_chunks00", + "CREATE TABLE \"pc_atomic_memory_current_vec_vector_chunks00\"(rowid PRIMARY KEY,vectors BLOB NOT NULL)" + ], + [ + "trigger", + "pc_atomic_memory_vec_delete", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_vec_delete AFTER DELETE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_vec WHERE identity = ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); END" + ], + [ + "trigger", + "pc_atomic_memory_vec_insert", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_vec_insert AFTER INSERT ON pc_atomic_memory_current BEGIN INSERT INTO pc_atomic_memory_current_vec (identity, scope_id, kind, embedding, artifact_id, revision, state_version, text) SELECT 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id)), new.scope_id, new.kind, new.embedding, new.artifact_id, new.revision, new.state_version, new.text WHERE NOT (new.embedding IS NULL OR new.embedding_input_hash IS NULL OR new.profile_fingerprint IS NULL OR new.profile_fingerprint <> '__ATOMIC_FINGERPRINT__' OR new.projection_format <> 'powercontext.atomic-memory.current.v1' OR LENGTH(new.embedding) <> __VECTOR_BYTES__); END" + ], + [ + "trigger", + "pc_atomic_memory_vec_update", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_vec_update AFTER UPDATE OF scope_id, artifact_id, kind, embedding, revision, state_version, text, embedding_input_hash, profile_fingerprint, projection_format ON pc_atomic_memory_current WHEN old.scope_id IS NOT new.scope_id OR old.artifact_id IS NOT new.artifact_id OR old.kind IS NOT new.kind OR old.embedding IS NOT new.embedding OR old.revision IS NOT new.revision OR old.state_version IS NOT new.state_version OR old.text IS NOT new.text OR old.embedding_input_hash IS NOT new.embedding_input_hash OR old.profile_fingerprint IS NOT new.profile_fingerprint OR old.projection_format IS NOT new.projection_format BEGIN DELETE FROM pc_atomic_memory_current_vec WHERE identity = ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); INSERT INTO pc_atomic_memory_current_vec (identity, scope_id, kind, embedding, artifact_id, revision, state_version, text) SELECT 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id)), new.scope_id, new.kind, new.embedding, new.artifact_id, new.revision, new.state_version, new.text WHERE NOT (new.embedding IS NULL OR new.embedding_input_hash IS NULL OR new.profile_fingerprint IS NULL OR new.profile_fingerprint <> '__ATOMIC_FINGERPRINT__' OR new.projection_format <> 'powercontext.atomic-memory.current.v1' OR LENGTH(new.embedding) <> __VECTOR_BYTES__); END" + ], + [ + "table", + "pc_server_storage_profile", + "pc_server_storage_profile", + "CREATE TABLE pc_server_storage_profile (singleton INTEGER NOT NULL PRIMARY KEY CHECK (singleton=1), embedding_profile TEXT NOT NULL)" + ] + ], + "initialize": [ + "CREATE VIRTUAL TABLE pc_topic_memory_chunk_vec USING vec0(scope_id TEXT partition key, embedding float[__DIMENSION__])", + "CREATE VIRTUAL TABLE pc_topic_memory_topic_vec USING vec0(scope_id TEXT partition key, embedding float[__DIMENSION__])", + "CREATE TABLE pc_topic_memory_vector_chunks (\n\tvector_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tchunk_ordinal INTEGER NOT NULL, \n\tprofile_fingerprint VARCHAR(64) NOT NULL, \n\tCONSTRAINT uq_pc_topic_memory_vector_chunks_active UNIQUE (scope_id, artifact_id, chunk_ordinal)\n)", + "CREATE TABLE pc_topic_memory_vector_topics (\n\tvector_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tprofile_fingerprint VARCHAR(64) NOT NULL, \n\tCONSTRAINT uq_pc_topic_memory_vector_topics_active UNIQUE (scope_id, artifact_id)\n)" + ], + "upgrade": [ + "CREATE VIRTUAL TABLE pc_atomic_memory_current_vec USING vec0(identity TEXT PRIMARY KEY, scope_id TEXT PARTITION KEY, kind TEXT, embedding float[__DIMENSION__], +artifact_id TEXT, +revision INTEGER, +state_version INTEGER, +text TEXT, chunk_size=128 /* __ATOMIC_FINGERPRINT__ */)", + "CREATE TRIGGER pc_atomic_memory_vec_delete AFTER DELETE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_vec WHERE identity = ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); END", + "CREATE TRIGGER pc_atomic_memory_vec_insert AFTER INSERT ON pc_atomic_memory_current BEGIN INSERT INTO pc_atomic_memory_current_vec (identity, scope_id, kind, embedding, artifact_id, revision, state_version, text) SELECT 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id)), new.scope_id, new.kind, new.embedding, new.artifact_id, new.revision, new.state_version, new.text WHERE NOT (new.embedding IS NULL OR new.embedding_input_hash IS NULL OR new.profile_fingerprint IS NULL OR new.profile_fingerprint <> '__ATOMIC_FINGERPRINT__' OR new.projection_format <> 'powercontext.atomic-memory.current.v1' OR LENGTH(new.embedding) <> __VECTOR_BYTES__); END", + "CREATE TRIGGER pc_atomic_memory_vec_update AFTER UPDATE OF scope_id, artifact_id, kind, embedding, revision, state_version, text, embedding_input_hash, profile_fingerprint, projection_format ON pc_atomic_memory_current WHEN old.scope_id IS NOT new.scope_id OR old.artifact_id IS NOT new.artifact_id OR old.kind IS NOT new.kind OR old.embedding IS NOT new.embedding OR old.revision IS NOT new.revision OR old.state_version IS NOT new.state_version OR old.text IS NOT new.text OR old.embedding_input_hash IS NOT new.embedding_input_hash OR old.profile_fingerprint IS NOT new.profile_fingerprint OR old.projection_format IS NOT new.projection_format BEGIN DELETE FROM pc_atomic_memory_current_vec WHERE identity = ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); INSERT INTO pc_atomic_memory_current_vec (identity, scope_id, kind, embedding, artifact_id, revision, state_version, text) SELECT 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id)), new.scope_id, new.kind, new.embedding, new.artifact_id, new.revision, new.state_version, new.text WHERE NOT (new.embedding IS NULL OR new.embedding_input_hash IS NULL OR new.profile_fingerprint IS NULL OR new.profile_fingerprint <> '__ATOMIC_FINGERPRINT__' OR new.projection_format <> 'powercontext.atomic-memory.current.v1' OR LENGTH(new.embedding) <> __VECTOR_BYTES__); END", + "CREATE TABLE pc_server_storage_profile (singleton INTEGER NOT NULL PRIMARY KEY CHECK (singleton=1), embedding_profile TEXT NOT NULL)" + ], + "rebuilt_tables": [ + "pc_candidate_heads", + "pc_candidate_versions", + "pc_profile_policies", + "pc_artifacts", + "pc_artifact_processing_intents", + "pc_dream_runs", + "pc_topic_memory_work_budgets" + ] + }, + "mysql": { + "pc_atomic_memory_current": "CREATE TABLE `pc_atomic_memory_current` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` bigint(20) NOT NULL,\n `state_version` bigint(20) NOT NULL,\n `content_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `projection_format` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `kind` mediumtext NOT NULL,\n `text` mediumtext NOT NULL,\n `searchable_text` mediumtext NOT NULL,\n `tag_keys` mediumtext NOT NULL,\n `embedding` VECTOR(__DIMENSION__) DEFAULT NULL,\n `profile_fingerprint` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `embedding_input_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n PRIMARY KEY (`scope_id`, `artifact_id`),\n FULLTEXT KEY `ix_pc_atomic_memory_current_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n VECTOR KEY `ix_pc_atomic_memory_current_embedding` (`embedding`) WITH (DISTANCE=L2,TYPE=HNSW, LIB=VSAG, M=16, EF_CONSTRUCTION=200, EF_SEARCH=64) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_atomic_memory_current_revision` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_atomic_memory_current_state_version` CHECK ((`state_version` >= 0)),\n CONSTRAINT `ck_pc_atomic_memory_current_embedding_metadata` CHECK ((((`embedding` is null) and (`profile_fingerprint` is null) and (`embedding_input_hash` is null)) or ((`embedding` is not null) and (`profile_fingerprint` is not null) and (`embedding_input_hash` is not null))))\n) DEFAULT CHARSET=utf8mb4", + "pc_topic_memory_vector_chunks": "CREATE TABLE `pc_topic_memory_vector_chunks` (\n `vector_id` bigint(20) NOT NULL AUTO_INCREMENT,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` bigint(20) NOT NULL,\n `chunk_ordinal` bigint(20) NOT NULL,\n `profile_fingerprint` varchar(64) NOT NULL,\n `embedding` VECTOR(__DIMENSION__) NOT NULL,\n PRIMARY KEY (`vector_id`),\n UNIQUE KEY `uq_pc_topic_memory_vector_chunks_active` (`scope_id`, `artifact_id`, `chunk_ordinal`) BLOCK_SIZE 16384 LOCAL,\n VECTOR KEY `ix_pc_topic_memory_vector_chunks_embedding` (`embedding`) WITH (DISTANCE=L2,TYPE=HNSW, LIB=VSAG, M=16, EF_CONSTRUCTION=200, EF_SEARCH=64) BLOCK_SIZE 16384\n) DEFAULT CHARSET=utf8mb4", + "pc_topic_memory_vector_topics": "CREATE TABLE `pc_topic_memory_vector_topics` (\n `vector_id` bigint(20) NOT NULL AUTO_INCREMENT,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` bigint(20) NOT NULL,\n `profile_fingerprint` varchar(64) NOT NULL,\n `embedding` VECTOR(__DIMENSION__) NOT NULL,\n PRIMARY KEY (`vector_id`),\n UNIQUE KEY `uq_pc_topic_memory_vector_topics_active` (`scope_id`, `artifact_id`) BLOCK_SIZE 16384 LOCAL,\n VECTOR KEY `ix_pc_topic_memory_vector_topics_embedding` (`embedding`) WITH (DISTANCE=L2,TYPE=HNSW, LIB=VSAG, M=16, EF_CONSTRUCTION=200, EF_SEARCH=64) BLOCK_SIZE 16384\n) DEFAULT CHARSET=utf8mb4" + }, + "mysql_profile_table": "CREATE TABLE pc_server_storage_profile (singleton BIGINT NOT NULL, embedding_profile MEDIUMTEXT NOT NULL, PRIMARY KEY (singleton), CONSTRAINT ck_pc_server_storage_profile_singleton CHECK (singleton=1))", + "mysql_optional_table_options": { + "pc_topic_memory_vector_topics": { + "auto_increment_mode": [ + "order" + ] + }, + "pc_topic_memory_vector_chunks": { + "auto_increment_mode": [ + "order" + ] + } + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1300.json b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1300.json new file mode 100644 index 0000000000..bfc7818691 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1300.json @@ -0,0 +1,11304 @@ +{ + "format": "powercontext.mysql-state-layouts.v1", + "revisions": { + "s1300": { + "statements": [ + "CREATE TABLE `pc_access_audit` (\n `cursor` int(11) NOT NULL AUTO_INCREMENT,\n `event_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `occurred_at` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `request_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `transport` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `operation` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `principal_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `principal_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `principal_description` text DEFAULT NULL,\n `actor_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `actor_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `actor_description` text DEFAULT NULL,\n `action` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `resource_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `deployment_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `selector_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `selector_entry_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `allowed` tinyint(1) NOT NULL,\n `reason_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `policy_revision` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `matched_subject_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `matched_subject_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `matched_subject_description` text DEFAULT NULL,\n `binding_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_description` text DEFAULT NULL,\n `role` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `expected_version` int(11) DEFAULT NULL,\n `result_version` int(11) DEFAULT NULL,\n PRIMARY KEY (`cursor`),\n UNIQUE KEY `event_id` (`event_id`) BLOCK_SIZE 16384 LOCAL\n) ORGANIZATION INDEX AUTO_INCREMENT = 1000001 DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_access_idempotency` (\n `actor_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `idempotency_key_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `operation` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `payload_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `result_binding_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `secondary_binding_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n PRIMARY KEY (`actor_id`, `idempotency_key_hash`),\n UNIQUE KEY `uq_pc_access_idempotency_actor_key` (`actor_id`, `idempotency_key_hash`) BLOCK_SIZE 16384 LOCAL\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_access_owners` (\n `owner_kind` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `object_key_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `candidate_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `selector_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `selector_entry_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `owner_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `owner_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `owner_description` text DEFAULT NULL,\n `established_at` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `policy_revision` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `idempotency_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`owner_kind`, `object_key_hash`),\n CONSTRAINT `ck_pc_access_owners_kind` CHECK ((((`owner_kind` = 'artifact') and (`artifact_id` is not null) and (`candidate_id` is null) and (`target_artifact_id` is null) and (`established_at` is not null) and (`policy_revision` is not null)) or ((`owner_kind` = 'candidate') and (`candidate_id` is not null) and (`artifact_id` is null) and (`selector_type` is null) and (`selector_entry_id` is null) and (`established_at` is null) and (`policy_revision` is null))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_access_relationship_heads` (\n `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n PRIMARY KEY (`name`),\n CONSTRAINT `ck_pc_access_relationship_heads_revision_nonnegative` CHECK ((`revision` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_access_relationships` (\n `binding_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `subject_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `subject_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `subject_description` text DEFAULT NULL,\n `resource_key_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `resource_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `deployment_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `selector_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `selector_entry_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `role` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `singleton_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `granted_by_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `granted_by_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `granted_by_description` text DEFAULT NULL,\n `reason` text DEFAULT NULL,\n `created_at` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `expires_at` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `version` int(11) NOT NULL,\n `policy_revision` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `idempotency_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revoked_at` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `revoked_by_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `revoked_by_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `revoked_by_description` text DEFAULT NULL,\n PRIMARY KEY (`binding_id`),\n UNIQUE KEY `singleton_key` (`singleton_key`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_access_relationships_version_positive` CHECK ((`version` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_agent_skill_targets` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `target_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `display_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `agent_kind` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `installation_scope` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `delivery_mode` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `installation_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `enrollment_token_digest` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `enrollment_expires_at` datetime DEFAULT NULL,\n `credential_subject` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `credential_verifier` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `receiver_version` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `environment_fingerprint` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `machine_hostname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `workspace_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `last_seen_at` datetime DEFAULT NULL,\n `generation` bigint(20) NOT NULL,\n `created_at` datetime NOT NULL,\n `updated_at` datetime NOT NULL,\n PRIMARY KEY (`scope_id`, `target_id`),\n UNIQUE KEY `uq_pc_agent_skill_targets_installation` (`scope_id`, `agent_kind`, `installation_scope`, `installation_id`) BLOCK_SIZE 16384 LOCAL,\n UNIQUE KEY `uq_pc_agent_skill_targets_enrollment_token` (`enrollment_token_digest`) BLOCK_SIZE 16384 LOCAL,\n UNIQUE KEY `uq_pc_agent_skill_targets_credential_subject` (`credential_subject`) BLOCK_SIZE 16384 LOCAL,\n UNIQUE KEY `uq_pc_agent_skill_targets_credential_verifier` (`credential_verifier`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_agent_skill_targets_agent_kind` CHECK ((`agent_kind` in ('codex','claude_code'))),\n CONSTRAINT `ck_pc_agent_skill_targets_installation_scope` CHECK ((`installation_scope` = 'project')),\n CONSTRAINT `ck_pc_agent_skill_targets_delivery_mode` CHECK ((`delivery_mode` = 'agent_pull')),\n CONSTRAINT `ck_pc_agent_skill_targets_state` CHECK ((`state` in ('pending','active','revoked'))),\n CONSTRAINT `ck_pc_agent_skill_targets_state_payload` CHECK ((((`state` = 'pending') and (`enrollment_token_digest` is not null) and (`enrollment_expires_at` is not null) and (`installation_id` is null) and (`credential_subject` is null) and (`credential_verifier` is null)) or ((`state` = 'active') and (`enrollment_token_digest` is null) and (`enrollment_expires_at` is null) and (`installation_id` is not null) and (`credential_subject` is not null) and (`credential_verifier` is not null)) or ((`state` = 'revoked') and (`enrollment_token_digest` is null) and (`enrollment_expires_at` is null) and (`credential_verifier` is null)))),\n CONSTRAINT `ck_pc_agent_skill_targets_generation_nonnegative` CHECK ((`generation` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_processing_auto_wave_targets` (\n `wave_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_through` bigint(20) NOT NULL,\n `completed` tinyint(1) NOT NULL DEFAULT '0',\n PRIMARY KEY (`wave_id`, `binding_name`, `scope_id`),\n CONSTRAINT `ck_pc_artifact_processing_auto_wave_target_source_positive` CHECK ((`source_through` >= 1))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_processing_binding_states` (\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `last_auto_wave_completed_at` datetime DEFAULT NULL,\n `last_schedule_checkpoint_at` datetime DEFAULT NULL,\n `scan_generation` bigint(20) NOT NULL DEFAULT '0',\n `scan_in_progress` tinyint(1) NOT NULL DEFAULT '0',\n `scan_upper_pending_sequence` bigint(20) DEFAULT NULL,\n PRIMARY KEY (`binding_name`),\n CONSTRAINT `ck_pc_processing_scan_generation` CHECK ((`scan_generation` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_processing_intents` (\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `pending_sequence` bigint(20) NOT NULL,\n `dirty_generation` bigint(20) NOT NULL DEFAULT '0',\n `clean_generation` bigint(20) NOT NULL DEFAULT '0',\n `requested_generation` bigint(20) NOT NULL DEFAULT '0',\n `handled_generation` bigint(20) NOT NULL DEFAULT '0',\n `last_auto_scan_generation` bigint(20) NOT NULL DEFAULT '0',\n PRIMARY KEY (`binding_name`, `scope_id`),\n UNIQUE KEY `pending_sequence` (`pending_sequence`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_processing_intent_binding_sequence` (`binding_name`, `pending_sequence`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_processing_intent_sequence` CHECK ((`pending_sequence` > 0)),\n CONSTRAINT `ck_pc_processing_intent_dirty` CHECK (((`clean_generation` >= 0) and (`clean_generation` <= `dirty_generation`))),\n CONSTRAINT `ck_pc_processing_intent_request` CHECK (((`handled_generation` >= 0) and (`handled_generation` <= `requested_generation`))),\n CONSTRAINT `ck_pc_processing_intent_scan` CHECK ((`last_auto_scan_generation` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_processing_leases` (\n `supervisor_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `holder_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `supervisor_generation` bigint(20) NOT NULL,\n `lease_expires_at` datetime DEFAULT NULL,\n PRIMARY KEY (`supervisor_group`),\n CONSTRAINT `ck_pc_artifact_processing_leases_generation_positive` CHECK ((`supervisor_generation` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_processing_migration_receipts` (\n `migration_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_snapshot` mediumblob NOT NULL,\n `requested_generation` bigint(20) NOT NULL,\n `dirty_generation` bigint(20) NOT NULL,\n `source_through` bigint(20) NOT NULL,\n PRIMARY KEY (`migration_id`, `binding_name`, `scope_id`)\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_processing_pending` (\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_through` bigint(20) NOT NULL,\n `flush_generation` bigint(20) NOT NULL DEFAULT '0',\n `handled_flush_generation` bigint(20) NOT NULL DEFAULT '0',\n PRIMARY KEY (`binding_name`, `scope_id`),\n CONSTRAINT `ck_pc_artifact_processing_pending_source_positive` CHECK ((`source_through` >= 1)),\n CONSTRAINT `ck_pc_artifact_processing_pending_flush_nonnegative` CHECK ((`flush_generation` >= 0)),\n CONSTRAINT `ck_pc_artifact_processing_pending_handled_nonnegative` CHECK ((`handled_flush_generation` >= 0)),\n CONSTRAINT `ck_pc_artifact_processing_pending_handled_not_ahead` CHECK ((`handled_flush_generation` <= `flush_generation`))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_processing_schema` (\n `singleton` int(11) NOT NULL,\n `schema_version` int(11) NOT NULL,\n `source_version` int(11) NOT NULL DEFAULT '1490',\n `phase` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `migration_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `config_manifest` text NOT NULL,\n PRIMARY KEY (`singleton`),\n CONSTRAINT `ck_pc_processing_schema_singleton` CHECK ((`singleton` = 1))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_processing_sequences` (\n `singleton` int(11) NOT NULL,\n `sequence` bigint(20) NOT NULL,\n PRIMARY KEY (`singleton`),\n CONSTRAINT `ck_pc_processing_sequence` CHECK (((`singleton` = 1) and (`sequence` >= 0)))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifacts` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `content` mediumblob NOT NULL,\n PRIMARY KEY (`scope_id`, `family`, `artifact_id`, `revision`)\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_atomic_memory_current` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` bigint(20) NOT NULL,\n `state_version` bigint(20) NOT NULL,\n `content_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `projection_format` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `kind` mediumtext NOT NULL,\n `text` mediumtext NOT NULL,\n `searchable_text` mediumtext NOT NULL,\n `tag_keys` mediumtext NOT NULL,\n `embedding` blob DEFAULT NULL,\n `profile_fingerprint` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `embedding_input_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n PRIMARY KEY (`scope_id`, `artifact_id`),\n FULLTEXT KEY `ix_pc_atomic_memory_current_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_atomic_memory_current_revision` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_atomic_memory_current_state_version` CHECK ((`state_version` >= 0)),\n CONSTRAINT `ck_pc_atomic_memory_current_embedding_metadata` CHECK ((((`embedding` is null) and (`profile_fingerprint` is null) and (`embedding_input_hash` is null)) or ((`embedding` is not null) and (`profile_fingerprint` is not null) and (`embedding_input_hash` is not null))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_connector_checkpoints` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `binding_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `connector_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `connector_version` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `checkpoint` mediumblob NOT NULL,\n PRIMARY KEY (`scope_id`, `binding_id`)\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_decision_observations` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `observation_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `operation_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `created_at` datetime NOT NULL,\n `consumer` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `policy_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `policy_version` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `mode` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `privacy_boundary` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `coverage` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `verdict` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `assessment_source` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `final_action` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `used_fallback` tinyint(1) NOT NULL,\n `fallback_reason` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `provider_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `backend_model_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `model_policy_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `requests` bigint(20) NOT NULL,\n `input_tokens` bigint(20) DEFAULT NULL,\n `output_tokens` bigint(20) DEFAULT NULL,\n `latency_ms` float DEFAULT NULL,\n `payload` mediumblob NOT NULL,\n PRIMARY KEY (`scope_id`, `observation_id`),\n KEY `ix_pc_decision_observations_scope_created` (`scope_id`, `created_at`, `observation_id`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_decision_observations_operation` (`scope_id`, `operation_id`, `created_at`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_decision_observations_requests_nonnegative` CHECK ((`requests` >= 0)),\n CONSTRAINT `ck_pc_decision_observations_input_tokens_nonnegative` CHECK (((`input_tokens` is null) or (`input_tokens` >= 0))),\n CONSTRAINT `ck_pc_decision_observations_output_tokens_nonnegative` CHECK (((`output_tokens` is null) or (`output_tokens` >= 0))),\n CONSTRAINT `ck_pc_decision_observations_latency_nonnegative` CHECK (((`latency_ms` is null) or (`latency_ms` >= 0)))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_external_skill_registrations` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `external_skill_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `provider` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `agent_kind` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `host_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `installation_scope` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `locator` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `locator_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `fingerprint` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `description` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`scope_id`, `external_skill_id`),\n UNIQUE KEY `uq_pc_external_skill_registrations_binding` (`scope_id`, `provider`, `host_id`, `installation_scope`, `locator_hash`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_external_skill_registrations_scope` CHECK ((`installation_scope` in ('user','project','plugin')))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_memory_entry_versions` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `memory_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `entry_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `entry_version_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `version` int(11) NOT NULL,\n `previous_version_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `kind` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `text` mediumtext NOT NULL,\n `source_refs` mediumblob NOT NULL,\n `artifact_refs` mediumblob NOT NULL,\n `entry_content_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `created_in_revision` int(11) NOT NULL,\n PRIMARY KEY (`scope_id`, `memory_artifact_id`, `entry_version_id`),\n UNIQUE KEY `uq_pc_memory_entry_versions_logical_version` (`scope_id`, `memory_artifact_id`, `entry_id`, `version`) BLOCK_SIZE 16384 LOCAL,\n UNIQUE KEY `uq_pc_memory_entry_versions_identity` (`scope_id`, `memory_artifact_id`, `entry_id`, `entry_version_id`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_memory_entry_versions_version_positive` CHECK ((`version` > 0)),\n CONSTRAINT `ck_pc_memory_entry_versions_revision_positive` CHECK ((`created_in_revision` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_memory_source_windows` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_through` bigint(20) NOT NULL,\n `window_limit` bigint(20) NOT NULL,\n PRIMARY KEY (`scope_id`),\n CONSTRAINT `ck_pc_memory_source_window_positive` CHECK (((`source_through` > 0) and (`window_limit` > 0)))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_model_usage_daily` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `usage_date` date NOT NULL,\n `purpose` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `operation` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `requests` bigint(20) NOT NULL,\n `input_tokens` bigint(20) NOT NULL,\n `output_tokens` bigint(20) NOT NULL,\n `input_complete` tinyint(1) NOT NULL,\n `output_complete` tinyint(1) NOT NULL,\n PRIMARY KEY (`scope_id`, `usage_date`, `purpose`, `operation`),\n CONSTRAINT `ck_pc_model_usage_daily_operation` CHECK ((`operation` in ('generation','embedding'))),\n CONSTRAINT `ck_pc_model_usage_daily_requests_nonnegative` CHECK ((`requests` >= 0)),\n CONSTRAINT `ck_pc_model_usage_daily_input_nonnegative` CHECK ((`input_tokens` >= 0)),\n CONSTRAINT `ck_pc_model_usage_daily_output_nonnegative` CHECK ((`output_tokens` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_recall_effort_daily` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `usage_date` date NOT NULL,\n `policy_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `assessment` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `preparations` bigint(20) NOT NULL,\n `rounds` bigint(20) NOT NULL,\n `expanded_preparations` bigint(20) NOT NULL,\n `admission_expansions` bigint(20) NOT NULL,\n `policy_floor_expansions` bigint(20) NOT NULL,\n `candidate_round_samples` bigint(20) NOT NULL,\n `candidates_assessed` bigint(20) NOT NULL,\n `final_candidate_pool` bigint(20) NOT NULL,\n `added_embeddings` bigint(20) NOT NULL,\n `added_generation_calls` bigint(20) NOT NULL,\n `truncated_items` bigint(20) NOT NULL,\n `dropped_items` bigint(20) NOT NULL,\n `dropped_below_min_bytes` bigint(20) NOT NULL,\n `dropped_no_fitting_truncation` bigint(20) NOT NULL,\n PRIMARY KEY (`scope_id`, `usage_date`, `policy_id`, `assessment`),\n CONSTRAINT `ck_recall_effort_preparations_nonnegative` CHECK ((`preparations` >= 0)),\n CONSTRAINT `ck_recall_effort_rounds_nonnegative` CHECK ((`rounds` >= 0)),\n CONSTRAINT `ck_recall_effort_expanded_preparations_nonnegative` CHECK ((`expanded_preparations` >= 0)),\n CONSTRAINT `ck_recall_effort_admission_expansions_nonnegative` CHECK ((`admission_expansions` >= 0)),\n CONSTRAINT `ck_recall_effort_policy_floor_expansions_nonnegative` CHECK ((`policy_floor_expansions` >= 0)),\n CONSTRAINT `ck_recall_effort_candidate_round_samples_nonnegative` CHECK ((`candidate_round_samples` >= 0)),\n CONSTRAINT `ck_recall_effort_candidates_assessed_nonnegative` CHECK ((`candidates_assessed` >= 0)),\n CONSTRAINT `ck_recall_effort_final_candidate_pool_nonnegative` CHECK ((`final_candidate_pool` >= 0)),\n CONSTRAINT `ck_recall_effort_added_embeddings_nonnegative` CHECK ((`added_embeddings` >= 0)),\n CONSTRAINT `ck_recall_effort_added_generation_calls_nonnegative` CHECK ((`added_generation_calls` >= 0)),\n CONSTRAINT `ck_recall_effort_truncated_items_nonnegative` CHECK ((`truncated_items` >= 0)),\n CONSTRAINT `ck_recall_effort_dropped_items_nonnegative` CHECK ((`dropped_items` >= 0)),\n CONSTRAINT `ck_recall_effort_dropped_below_min_bytes_nonnegative` CHECK ((`dropped_below_min_bytes` >= 0)),\n CONSTRAINT `ck_recall_effort_dropped_no_fitting_truncation_nonnegative` CHECK ((`dropped_no_fitting_truncation` >= 0)),\n CONSTRAINT `ck_pc_recall_effort_daily_drop_total` CHECK ((`dropped_items` = (`dropped_below_min_bytes` + `dropped_no_fitting_truncation`)))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_recall_token_daily` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `usage_date` date NOT NULL,\n `estimator_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `estimator_version` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `preparations` bigint(20) NOT NULL,\n `ready_preparations` bigint(20) NOT NULL,\n `comparable_preparations` bigint(20) NOT NULL,\n `baseline_tokens` bigint(20) NOT NULL,\n `recalled_tokens` bigint(20) NOT NULL,\n PRIMARY KEY (`scope_id`, `usage_date`, `estimator_id`, `estimator_version`),\n CONSTRAINT `ck_pc_recall_token_daily_preparations_nonnegative` CHECK ((`preparations` >= 0)),\n CONSTRAINT `ck_pc_recall_token_daily_ready_nonnegative` CHECK ((`ready_preparations` >= 0)),\n CONSTRAINT `ck_pc_recall_token_daily_comparable_nonnegative` CHECK ((`comparable_preparations` >= 0)),\n CONSTRAINT `ck_pc_recall_token_daily_comparable_ready` CHECK ((`comparable_preparations` <= `ready_preparations`)),\n CONSTRAINT `ck_pc_recall_token_daily_ready_total` CHECK ((`ready_preparations` <= `preparations`)),\n CONSTRAINT `ck_pc_recall_token_daily_baseline_nonnegative` CHECK ((`baseline_tokens` >= 0)),\n CONSTRAINT `ck_pc_recall_token_daily_recalled_nonnegative` CHECK ((`recalled_tokens` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_receipt_migration_review` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `reason` varchar(64) NOT NULL,\n PRIMARY KEY (`scope_id`, `source_id`)\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_scopes` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `title` varchar(256) NOT NULL,\n `summary` varchar(2000) NOT NULL,\n `scope_id_search` mediumtext NOT NULL,\n `title_search` mediumtext NOT NULL,\n `summary_search` mediumtext NOT NULL,\n `parent_scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `version` int(11) NOT NULL,\n PRIMARY KEY (`scope_id`),\n FOREIGN KEY (`parent_scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `ck_pc_scopes_version_positive` CHECK ((`version` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_server_identity` (\n `singleton_key` int(11) NOT NULL,\n `server_id` varchar(36) NOT NULL,\n PRIMARY KEY (`singleton_key`),\n UNIQUE KEY `server_id` (`server_id`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_server_identity_singleton` CHECK ((`singleton_key` = 1))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_skill_packages` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `tree_digest` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `archive_digest` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `archive_bytes` mediumblob NOT NULL,\n `manifest` mediumblob NOT NULL,\n `file_count` int(11) NOT NULL,\n `uncompressed_size` bigint(20) NOT NULL,\n `archive_size` bigint(20) NOT NULL,\n `created_at` datetime NOT NULL,\n PRIMARY KEY (`scope_id`, `tree_digest`),\n CONSTRAINT `ck_pc_skill_packages_file_count` CHECK (((`file_count` > 0) and (`file_count` <= 256))),\n CONSTRAINT `ck_pc_skill_packages_uncompressed_size` CHECK (((`uncompressed_size` > 0) and (`uncompressed_size` <= 4194304))),\n CONSTRAINT `ck_pc_skill_packages_archive_size` CHECK (((`archive_size` > 0) and (`archive_size` <= 5242880)))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_skill_publications` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `target_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `desired_state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'published',\n `desired_revision` int(11) NOT NULL,\n `desired_tree_digest` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `observed_revision` int(11) DEFAULT NULL,\n `observed_tree_digest` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `observed_generation` bigint(20) DEFAULT NULL,\n `destination` mediumtext DEFAULT NULL,\n `state` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `selected_runtime_variant` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `environment_fingerprint` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `last_error_code` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `observed_at` datetime DEFAULT NULL,\n `generation` bigint(20) NOT NULL,\n `updated_at` datetime NOT NULL,\n PRIMARY KEY (`scope_id`, `target_id`, `artifact_id`),\n CONSTRAINT `ck_pc_skill_publications_desired_revision_positive` CHECK ((`desired_revision` > 0)),\n CONSTRAINT `ck_pc_skill_publications_observed_revision_positive` CHECK (((`observed_revision` is null) or (`observed_revision` > 0))),\n CONSTRAINT `ck_pc_skill_publications_desired_state` CHECK ((`desired_state` in ('published','unpublished'))),\n CONSTRAINT `ck_pc_skill_publications_state` CHECK ((`state` in ('unpublished','pending','current','update_available','delivery_failed','conflict','drifted','incompatible'))),\n CONSTRAINT `ck_pc_skill_publications_observed_generation_nonnegative` CHECK (((`observed_generation` is null) or (`observed_generation` >= 0))),\n CONSTRAINT `ck_pc_skill_publications_generation_nonnegative` CHECK ((`generation` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_source_cursors` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `cursor` mediumblob NOT NULL,\n `generation` bigint(20) NOT NULL,\n PRIMARY KEY (`scope_id`, `binding_name`),\n CONSTRAINT `ck_pc_source_cursors_generation_nonnegative` CHECK ((`generation` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_source_definition_manifests` (\n `definition_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `definition_version` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `fingerprint` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `manifest` mediumblob NOT NULL,\n PRIMARY KEY (`definition_name`, `definition_version`),\n UNIQUE KEY `uq_pc_source_definition_manifest_fingerprint` (`definition_name`, `fingerprint`) BLOCK_SIZE 16384 LOCAL\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_source_journal_heads` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `position` bigint(20) NOT NULL,\n PRIMARY KEY (`scope_id`),\n CONSTRAINT `ck_pc_source_journal_heads_position_nonnegative` CHECK ((`position` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_sources` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `payload` mediumblob NOT NULL,\n `journal_position` bigint(20) NOT NULL,\n PRIMARY KEY (`scope_id`, `source_type`, `source_id`),\n UNIQUE KEY `uq_pc_sources_scope_journal_position` (`scope_id`, `journal_position`) BLOCK_SIZE 16384 LOCAL\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_topic_memory_processing_targets` (\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `target_request_generation` bigint(20) NOT NULL,\n `source_through` bigint(20) NOT NULL,\n `captured_flush_generation` bigint(20) NOT NULL,\n `observed_dirty_generation` bigint(20) NOT NULL,\n PRIMARY KEY (`binding_name`, `scope_id`),\n CONSTRAINT `ck_pc_topic_processing_target` CHECK (((`target_request_generation` > 0) and (`source_through` >= 0) and (`captured_flush_generation` >= 0) and (`observed_dirty_generation` >= 0)))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_topic_memory_retrieval_shape` (\n `singleton` int(11) NOT NULL,\n `shape` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `profile_fingerprint` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n PRIMARY KEY (`singleton`),\n CONSTRAINT `ck_pc_topic_memory_retrieval_shape_singleton` CHECK ((`singleton` = 1)),\n CONSTRAINT `ck_pc_topic_memory_retrieval_shape_value` CHECK ((`shape` in ('fts','hybrid'))),\n CONSTRAINT `ck_pc_topic_memory_retrieval_shape_profile` CHECK ((((`shape` = 'fts') and (`profile_fingerprint` is null)) or ((`shape` = 'hybrid') and (`profile_fingerprint` is not null))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_topic_memory_work_budgets` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_after` bigint(20) NOT NULL,\n `source_through` bigint(20) NOT NULL,\n `attempt_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `attempts` int(11) NOT NULL,\n `requests` bigint(20) NOT NULL,\n `tokens` bigint(20) NOT NULL,\n `failure_code` varchar(64) NOT NULL,\n PRIMARY KEY (`scope_id`, `binding_name`, `source_after`),\n CONSTRAINT `ck_pc_topic_budget_window` CHECK (((`source_after` >= 0) and (`source_through` > `source_after`))),\n CONSTRAINT `ck_pc_topic_budget_usage` CHECK (((`attempts` > 0) and (`requests` >= 0) and (`tokens` >= 0)))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_candidate_versions` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `candidate_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `version` int(11) NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `proposal` mediumblob NOT NULL,\n `source_refs` mediumblob NOT NULL,\n `artifact_refs` mediumblob NOT NULL,\n `target_family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_revision` int(11) DEFAULT NULL,\n `reason` mediumtext DEFAULT NULL,\n PRIMARY KEY (`scope_id`, `candidate_id`, `version`),\n FOREIGN KEY (`scope_id`, `target_family`, `target_artifact_id`, `target_revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `ck_pc_artifact_candidate_versions_version_positive` CHECK ((`version` > 0)),\n CONSTRAINT `ck_pc_artifact_candidate_versions_target_complete` CHECK ((((`target_family` is null) and (`target_artifact_id` is null) and (`target_revision` is null)) or ((`target_family` is not null) and (`target_artifact_id` is not null) and (`target_revision` > 0))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_heads` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `searchable_text` mediumtext DEFAULT NULL,\n `lifecycle_state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'active',\n `replacement_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `merged_into_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `governance_generation` bigint(20) NOT NULL DEFAULT '0',\n PRIMARY KEY (`scope_id`, `family`, `artifact_id`),\n FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FULLTEXT KEY `ix_pc_artifact_heads_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_artifact_heads_revision_positive` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_artifact_heads_lifecycle_state` CHECK ((`lifecycle_state` in ('active','deprecated','retired'))),\n CONSTRAINT `ck_pc_artifact_heads_governance_generation_nonnegative` CHECK ((`governance_generation` >= 0)),\n CONSTRAINT `ck_pc_artifact_heads_replacement_deprecated` CHECK (((`replacement_artifact_id` is null) or (`lifecycle_state` = 'deprecated'))),\n CONSTRAINT `ck_pc_artifact_heads_merge_target` CHECK (((`merged_into_id` is null) or ((`lifecycle_state` = 'deprecated') and (`merged_into_id` <> `artifact_id`) and (`replacement_artifact_id` is null))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_lineage_artifacts` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `ordinal` int(11) NOT NULL,\n `upstream_family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `upstream_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `upstream_revision` int(11) NOT NULL,\n `is_merge_input` tinyint(1) NOT NULL DEFAULT '0',\n PRIMARY KEY (`scope_id`, `family`, `artifact_id`, `revision`, `ordinal`),\n FOREIGN KEY (`scope_id`, `upstream_family`, `upstream_artifact_id`, `upstream_revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE CASCADE \n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_lineage_sources` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `ordinal` int(11) NOT NULL,\n `source_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`scope_id`, `family`, `artifact_id`, `revision`, `ordinal`),\n FOREIGN KEY (`scope_id`, `source_type`, `source_id`) REFERENCES `pc_sources`(`scope_id`, `source_type`, `source_id`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE CASCADE \n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_publications` (\n `target_scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `target_family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `target_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `target_revision` int(11) NOT NULL,\n `source_scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_revision` int(11) NOT NULL,\n `content_digest` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `idempotency_key` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`target_scope_id`, `target_family`, `target_artifact_id`, `target_revision`),\n FOREIGN KEY (`source_scope_id`, `source_family`, `source_artifact_id`, `source_revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FOREIGN KEY (`target_scope_id`, `target_family`, `target_artifact_id`, `target_revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n UNIQUE KEY `uq_pc_artifact_publications_request` (`target_scope_id`, `idempotency_key`) BLOCK_SIZE 16384 LOCAL\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_dream_runs` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `run_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `principal_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `idempotency_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `request_digest` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `operation` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `status` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `accepted_at` bigint(20) NOT NULL,\n `generation` int(11) NOT NULL,\n `request_generation` bigint(20) NOT NULL,\n `payload` mediumblob NOT NULL,\n PRIMARY KEY (`scope_id`, `run_id`),\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n UNIQUE KEY `uq_pc_dream_idempotency` (`scope_id`, `principal_key`, `idempotency_key`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_dream_dispatch` (`scope_id`, `operation`, `status`, `request_generation`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_dream_list` (`scope_id`, `accepted_at`, `run_id`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_dream_generation` CHECK ((`generation` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_memory_entry_heads` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `memory_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `head_revision` int(11) NOT NULL,\n `entry_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `entry_version_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `entry_content_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `searchable_text` mediumtext NOT NULL,\n PRIMARY KEY (`scope_id`, `memory_artifact_id`, `entry_id`),\n FOREIGN KEY (`scope_id`, `memory_artifact_id`, `entry_id`, `entry_version_id`) REFERENCES `pc_memory_entry_versions`(`scope_id`, `memory_artifact_id`, `entry_id`, `entry_version_id`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `ck_pc_memory_entry_heads_revision_positive` CHECK ((`head_revision` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_recurrence_match` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `match_key` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `task_outcome_source_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `task_outcome_source_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `task_outcome_position` bigint(20) NOT NULL,\n `failure_item_kind` varchar(16) NOT NULL,\n `failure_item_index` int(11) NOT NULL,\n `failure_item_digest` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `candidate_set_mode` varchar(32) NOT NULL,\n `candidate_set_digest` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `result` varchar(16) NOT NULL,\n `target_family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_revision` int(11) DEFAULT NULL,\n `signature_key` mediumtext DEFAULT NULL,\n `payload` mediumblob NOT NULL,\n PRIMARY KEY (`scope_id`, `match_key`),\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n UNIQUE KEY `uq_pc_recurrence_match_identity` (`scope_id`, `task_outcome_source_type`, `task_outcome_source_id`, `failure_item_kind`, `failure_item_index`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_recurrence_match_result` CHECK ((`result` in ('matched','unmatched','ambiguous'))),\n CONSTRAINT `ck_pc_recurrence_match_candidate_set_mode` CHECK ((`candidate_set_mode` in ('handoff_citations','scope_heads'))),\n CONSTRAINT `ck_pc_recurrence_match_failure_item_kind` CHECK ((`failure_item_kind` in ('observation','check'))),\n CONSTRAINT `ck_pc_recurrence_match_position_positive` CHECK ((`task_outcome_position` > 0)),\n CONSTRAINT `ck_pc_recurrence_match_item_index_nonnegative` CHECK ((`failure_item_index` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_recurrence_observation` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `observation_id` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `selection_key` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `verdict_key` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `event` varchar(16) NOT NULL,\n `match_basis` varchar(8) NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `signature_key` mediumtext NOT NULL,\n `signature_key_hash` binary(32) NOT NULL,\n `task_outcome_source_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `task_outcome_source_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `task_outcome_position` bigint(20) NOT NULL,\n `payload` mediumblob NOT NULL,\n PRIMARY KEY (`scope_id`, `observation_id`),\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n UNIQUE KEY `uq_pc_recurrence_observation_selection` (`scope_id`, `selection_key`) BLOCK_SIZE 16384 LOCAL,\n UNIQUE KEY `uq_pc_recurrence_observation_verdict` (`scope_id`, `verdict_key`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_recurrence_observation_key` (`scope_id`, `signature_key_hash`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_recurrence_observation_outcome` (`scope_id`, `task_outcome_source_type`, `task_outcome_source_id`, `task_outcome_position`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_recurrence_observation_revision` (`scope_id`, `family`, `artifact_id`, `revision`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_recurrence_observation_event` CHECK ((`event` in ('selected','recurred','avoided'))),\n CONSTRAINT `ck_pc_recurrence_observation_revision_positive` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_recurrence_observation_position_positive` CHECK ((`task_outcome_position` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_scope_bindings` (\n `integration` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `kind` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `external_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `external_id_search` mediumtext NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`integration`, `kind`, `external_id`),\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE RESTRICT \n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_scope_context_references` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `referenced_scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`scope_id`, `referenced_scope_id`),\n FOREIGN KEY (`referenced_scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n CONSTRAINT `ck_pc_scope_context_references_not_self` CHECK ((`scope_id` <> `referenced_scope_id`))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_scope_creation_requests` (\n `idempotency_key` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `request_digest` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`idempotency_key`),\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE RESTRICT \n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_scope_external_references` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `ordinal` int(11) NOT NULL,\n `kind` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `value` varchar(2000) NOT NULL,\n `value_search` mediumtext NOT NULL,\n `value_digest` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`scope_id`, `ordinal`),\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n UNIQUE KEY `uq_pc_scope_external_references_value` (`scope_id`, `kind`, `value_digest`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_scope_external_references_ordinal_nonnegative` CHECK ((`ordinal` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_scope_settings` (\n `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`name`),\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE RESTRICT \n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_topic_memory_revision_publications` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `published_at` datetime NOT NULL,\n PRIMARY KEY (`scope_id`, `family`, `artifact_id`, `revision`),\n FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `ck_pc_topic_memory_publications_family` CHECK ((`family` = 'topic-memory')),\n CONSTRAINT `ck_pc_topic_memory_publications_revision_positive` CHECK ((`revision` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_candidate_heads` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `candidate_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `version` int(11) NOT NULL,\n `status` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `result_family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `result_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `result_revision` int(11) DEFAULT NULL,\n `decision_reason` mediumtext DEFAULT NULL,\n PRIMARY KEY (`scope_id`, `candidate_id`),\n FOREIGN KEY (`scope_id`, `result_family`, `result_artifact_id`, `result_revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FOREIGN KEY (`scope_id`, `candidate_id`, `version`) REFERENCES `pc_artifact_candidate_versions`(`scope_id`, `candidate_id`, `version`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `ck_pc_artifact_candidate_heads_status` CHECK ((`status` in ('pending','approved','rejected'))),\n CONSTRAINT `ck_pc_artifact_candidate_heads_terminal_result` CHECK ((((`status` = 'approved') and (`result_family` is not null) and (`result_artifact_id` is not null) and (`result_revision` > 0) and (`decision_reason` is null)) or ((`status` = 'rejected') and (`result_family` is null) and (`result_artifact_id` is null) and (`result_revision` is null) and (`decision_reason` is not null)) or ((`status` = 'pending') and (`result_family` is null) and (`result_artifact_id` is null) and (`result_revision` is null) and (`decision_reason` is null))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_artifact_tags` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `target_type` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `target_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `tag_key_hash` binary(32) NOT NULL,\n `tag_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `tag` varchar(64) NOT NULL,\n `assigned_at` datetime NOT NULL,\n PRIMARY KEY (`scope_id`, `family`, `artifact_id`, `target_type`, `target_id`, `tag_key_hash`),\n FOREIGN KEY (`scope_id`, `family`, `artifact_id`) REFERENCES `pc_artifact_heads`(`scope_id`, `family`, `artifact_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n KEY `ix_pc_artifact_tags_key` (`scope_id`, `tag_key_hash`, `family`, `target_type`, `artifact_id`, `target_id`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_artifact_tags_family_key` (`scope_id`, `family`, `tag_key_hash`, `target_type`, `artifact_id`, `target_id`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_artifact_tags_target` CHECK ((((`target_type` = 'artifact') and (`target_id` = `artifact_id`)) or ((`target_type` = 'memory_entry') and (`family` = 'memory'))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_topic_memory_active_chunks` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `chunk_ordinal` int(11) NOT NULL,\n `start_offset` int(11) NOT NULL,\n `end_offset` int(11) NOT NULL,\n `chunk_text` mediumtext NOT NULL,\n `searchable_text` mediumtext NOT NULL,\n `policy_version` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`scope_id`, `artifact_id`, `chunk_ordinal`),\n FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `pc_topic_memory_revision_publications`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FULLTEXT KEY `ix_pc_topic_memory_active_chunks_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_topic_memory_active_chunks_family` CHECK ((`family` = 'topic-memory')),\n CONSTRAINT `ck_pc_topic_memory_active_chunks_revision_positive` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_topic_memory_active_chunks_ordinal_nonnegative` CHECK ((`chunk_ordinal` >= 0)),\n CONSTRAINT `ck_pc_topic_memory_active_chunks_start_nonnegative` CHECK ((`start_offset` >= 0)),\n CONSTRAINT `ck_pc_topic_memory_active_chunks_offsets` CHECK ((`end_offset` > `start_offset`))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_topic_memory_active_topics` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `title` mediumtext NOT NULL,\n `summary` mediumtext NOT NULL,\n `searchable_text` mediumtext NOT NULL,\n `source_count` int(11) NOT NULL,\n PRIMARY KEY (`scope_id`, `artifact_id`),\n FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `pc_topic_memory_revision_publications`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FULLTEXT KEY `ix_pc_topic_memory_active_topics_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_topic_memory_active_topics_family` CHECK ((`family` = 'topic-memory')),\n CONSTRAINT `ck_pc_topic_memory_active_topics_revision_positive` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_topic_memory_active_topics_sources_nonnegative` CHECK ((`source_count` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_profile_policies` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `generation_enabled` tinyint(1) NOT NULL,\n `activation_mode` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `pending_candidate_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `version` bigint(20) NOT NULL,\n `updated_at` datetime NOT NULL,\n PRIMARY KEY (`scope_id`),\n FOREIGN KEY (`scope_id`, `pending_candidate_id`) REFERENCES `pc_artifact_candidate_heads`(`scope_id`, `candidate_id`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n CONSTRAINT `ck_pc_profile_policies_activation_mode` CHECK ((`activation_mode` in ('automatic','review_required'))),\n CONSTRAINT `ck_pc_profile_policies_version_positive` CHECK ((`version` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "@mysql_operations.py:initialize_processing" + ], + "changes": [ + { + "pc_access_audit": { + "columns": { + "cursor": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [ + "auto_increment" + ] + }, + "event_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "occurred_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "transport": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "actor_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "actor_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "actor_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "action": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "resource_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "deployment_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "allowed": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "matched_subject_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "role": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "expected_version": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "result_version": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "cursor", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "event_id": { + "columns": [ + [ + "event_id", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_access_idempotency": { + "columns": { + "actor_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "payload_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "secondary_binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "actor_id", + null, + "asc" + ], + [ + "idempotency_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_access_idempotency_actor_key": { + "columns": [ + [ + "actor_id", + null, + "asc" + ], + [ + "idempotency_key_hash", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_access_owners": { + "columns": { + "owner_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "object_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "owner_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "established_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "owner_kind", + null, + "asc" + ], + [ + "object_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_owners_kind": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "owner_kind" + ], + [ + "literal", + "artifact" + ] + ], + [ + "is_not_null", + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "candidate_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "established_at" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "policy_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "owner_kind" + ], + [ + "literal", + "candidate" + ] + ], + [ + "is_not_null", + [ + "column", + "candidate_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "selector_type" + ] + ] + ], + [ + "is_null", + [ + "column", + "selector_entry_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "established_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "policy_revision" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_access_relationship_heads": { + "columns": { + "name": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_relationship_heads_revision_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_access_relationships": { + "columns": { + "binding_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "subject_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "resource_key_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "resource_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "deployment_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_type": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selector_entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "role": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "singleton_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "granted_by_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "expires_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_revision": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_at": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_type": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_id": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revoked_by_description": { + "type": [ + "text", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_access_relationships_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "singleton_key": { + "columns": [ + [ + "singleton_key", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_agent_skill_targets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "display_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "agent_kind": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_scope": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "delivery_mode": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "enrollment_token_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "enrollment_expires_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "credential_subject": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "credential_verifier": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "receiver_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "environment_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "machine_hostname": { + "type": [ + "varchar", + [ + "255" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "workspace_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_seen_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_agent_skill_targets_agent_kind": { + "expression": [ + "in", + [ + "column", + "agent_kind" + ], + [ + [ + "literal", + "codex" + ], + [ + "literal", + "claude_code" + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_installation_scope": { + "expression": [ + "binary", + "=", + [ + "column", + "installation_scope" + ], + [ + "literal", + "project" + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_delivery_mode": { + "expression": [ + "binary", + "=", + [ + "column", + "delivery_mode" + ], + [ + "literal", + "agent_pull" + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_state": { + "expression": [ + "in", + [ + "column", + "state" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "active" + ], + [ + "literal", + "revoked" + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_state_payload": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_not_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "installation_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_subject" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_verifier" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "active" + ] + ], + [ + "is_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "installation_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "credential_subject" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "credential_verifier" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "state" + ], + [ + "literal", + "revoked" + ] + ], + [ + "is_null", + [ + "column", + "enrollment_token_digest" + ] + ] + ], + [ + "is_null", + [ + "column", + "enrollment_expires_at" + ] + ] + ], + [ + "is_null", + [ + "column", + "credential_verifier" + ] + ] + ] + ], + "enforced": true + }, + "ck_pc_agent_skill_targets_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_agent_skill_targets_installation": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "agent_kind", + null, + "asc" + ], + [ + "installation_scope", + null, + "asc" + ], + [ + "installation_id", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_enrollment_token": { + "columns": [ + [ + "enrollment_token_digest", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_credential_subject": { + "columns": [ + [ + "credential_subject", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_agent_skill_targets_credential_verifier": { + "columns": [ + [ + "credential_verifier", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_artifact_processing_auto_wave_targets": { + "columns": { + "wave_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "completed": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "wave_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_auto_wave_target_source_positive": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_processing_binding_states": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_auto_wave_completed_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "last_schedule_checkpoint_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "scan_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "scan_in_progress": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + }, + "scan_upper_pending_sequence": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_scan_generation": { + "expression": [ + "binary", + ">=", + [ + "column", + "scan_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_processing_intents": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_sequence": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "clean_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "requested_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "handled_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "last_auto_scan_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_intent_sequence": { + "expression": [ + "binary", + ">", + [ + "column", + "pending_sequence" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_dirty": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "clean_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "clean_generation" + ], + [ + "column", + "dirty_generation" + ] + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_request": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "handled_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "handled_generation" + ], + [ + "column", + "requested_generation" + ] + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_scan": { + "expression": [ + "binary", + ">=", + [ + "column", + "last_auto_scan_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "pending_sequence": { + "columns": [ + [ + "pending_sequence", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_processing_intent_binding_sequence": { + "columns": [ + [ + "binding_name", + null, + "asc" + ], + [ + "pending_sequence", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_artifact_processing_leases": { + "columns": { + "supervisor_group": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "holder_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "supervisor_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "lease_expires_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "supervisor_group", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_leases_generation_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "supervisor_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_processing_migration_receipts": { + "columns": { + "migration_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_snapshot": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requested_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "migration_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_artifact_processing_pending": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "handled_flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_artifact_processing_pending_source_positive": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 1 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_flush_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "flush_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_handled_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "handled_flush_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_processing_pending_handled_not_ahead": { + "expression": [ + "binary", + "<=", + [ + "column", + "handled_flush_generation" + ], + [ + "column", + "flush_generation" + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_processing_schema": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "schema_version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": [ + "number", + 1490 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "phase": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "migration_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "config_manifest": { + "type": [ + "text", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_schema_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_processing_sequences": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "sequence": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_sequence": { + "expression": [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + [ + "binary", + ">=", + [ + "column", + "sequence" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_atomic_memory_current": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "state_version": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "projection_format": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "tag_keys": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "embedding": { + "type": [ + "blob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "profile_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "embedding_input_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_atomic_memory_current_revision": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_atomic_memory_current_state_version": { + "expression": [ + "binary", + ">=", + [ + "column", + "state_version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_atomic_memory_current_embedding_metadata": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "embedding" + ] + ], + [ + "is_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "is_null", + [ + "column", + "embedding_input_hash" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "embedding" + ] + ], + [ + "is_not_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "embedding_input_hash" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_atomic_memory_current_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + } + }, + { + "pc_connector_checkpoints": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "connector_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "connector_version": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "checkpoint": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_decision_observations": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observation_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "consumer": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "policy_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "mode": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "privacy_boundary": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "coverage": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "verdict": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "assessment_source": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "final_action": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "used_fallback": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "fallback_reason": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "provider_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "backend_model_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "model_policy_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "latency_ms": { + "type": [ + "float", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_decision_observations_requests_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_input_tokens_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "input_tokens" + ] + ], + [ + "binary", + ">=", + [ + "column", + "input_tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_output_tokens_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "output_tokens" + ] + ], + [ + "binary", + ">=", + [ + "column", + "output_tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_decision_observations_latency_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "latency_ms" + ] + ], + [ + "binary", + ">=", + [ + "column", + "latency_ms" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_decision_observations_scope_created": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "created_at", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_decision_observations_operation": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation_id", + null, + "asc" + ], + [ + "created_at", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_external_skill_registrations": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_skill_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "provider": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "agent_kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "host_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "installation_scope": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "locator": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "locator_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "description": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "external_skill_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_external_skill_registrations_scope": { + "expression": [ + "in", + [ + "column", + "installation_scope" + ], + [ + [ + "literal", + "user" + ], + [ + "literal", + "project" + ], + [ + "literal", + "plugin" + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_external_skill_registrations_binding": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "provider", + null, + "asc" + ], + [ + "host_id", + null, + "asc" + ], + [ + "installation_scope", + null, + "asc" + ], + [ + "locator_hash", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_memory_entry_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "memory_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "previous_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "entry_content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "created_in_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_version_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_memory_entry_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_memory_entry_versions_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "created_in_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_memory_entry_versions_logical_version": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_memory_entry_versions_identity": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ], + [ + "entry_version_id", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_memory_source_windows": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "window_limit": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_memory_source_window_positive": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "window_limit" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_model_usage_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "purpose": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "input_complete": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "output_complete": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "purpose", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_model_usage_daily_operation": { + "expression": [ + "in", + [ + "column", + "operation" + ], + [ + [ + "literal", + "generation" + ], + [ + "literal", + "embedding" + ] + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_requests_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_input_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "input_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_model_usage_daily_output_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "output_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_recall_effort_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "assessment": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "rounds": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "expanded_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "admission_expansions": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "policy_floor_expansions": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "candidate_round_samples": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "candidates_assessed": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "final_candidate_pool": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "added_embeddings": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "added_generation_calls": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "truncated_items": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_items": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_below_min_bytes": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dropped_no_fitting_truncation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "policy_id", + null, + "asc" + ], + [ + "assessment", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_recall_effort_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_rounds_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "rounds" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_expanded_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "expanded_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_admission_expansions_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "admission_expansions" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_policy_floor_expansions_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "policy_floor_expansions" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_candidate_round_samples_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "candidate_round_samples" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_candidates_assessed_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "candidates_assessed" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_final_candidate_pool_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "final_candidate_pool" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_added_embeddings_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "added_embeddings" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_added_generation_calls_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "added_generation_calls" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_truncated_items_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "truncated_items" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_items_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_items" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_below_min_bytes_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_below_min_bytes" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_recall_effort_dropped_no_fitting_truncation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "dropped_no_fitting_truncation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_effort_daily_drop_total": { + "expression": [ + "binary", + "=", + [ + "column", + "dropped_items" + ], + [ + "binary", + "+", + [ + "column", + "dropped_below_min_bytes" + ], + [ + "column", + "dropped_no_fitting_truncation" + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_recall_token_daily": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "usage_date": { + "type": [ + "date", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "estimator_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "estimator_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ready_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "comparable_preparations": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "baseline_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "recalled_tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "usage_date", + null, + "asc" + ], + [ + "estimator_id", + null, + "asc" + ], + [ + "estimator_version", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_recall_token_daily_preparations_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_ready_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "ready_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_comparable_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "comparable_preparations" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_comparable_ready": { + "expression": [ + "binary", + "<=", + [ + "column", + "comparable_preparations" + ], + [ + "column", + "ready_preparations" + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_ready_total": { + "expression": [ + "binary", + "<=", + [ + "column", + "ready_preparations" + ], + [ + "column", + "preparations" + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_baseline_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "baseline_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recall_token_daily_recalled_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "recalled_tokens" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_receipt_migration_review": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "reason": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "source_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_scopes": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "title": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "scope_id_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "title_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "parent_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "parent_scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scopes_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_server_identity": { + "columns": { + "singleton_key": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "server_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "singleton_key", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_server_identity_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton_key" + ], + [ + "number", + 1 + ] + ], + "enforced": true + } + }, + "indexes": { + "server_id": { + "columns": [ + [ + "server_id", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_skill_packages": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "archive_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "archive_bytes": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "manifest": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "file_count": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "uncompressed_size": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "archive_size": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "tree_digest", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_skill_packages_file_count": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "file_count" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "file_count" + ], + [ + "number", + 256 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_packages_uncompressed_size": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "uncompressed_size" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "uncompressed_size" + ], + [ + "number", + 4194304 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_packages_archive_size": { + "expression": [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "archive_size" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "archive_size" + ], + [ + "number", + 5242880 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_skill_publications": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "desired_state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "published" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "desired_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "desired_tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "observed_tree_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_generation": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "destination": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selected_runtime_variant": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "environment_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_error_code": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observed_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_skill_publications_desired_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "desired_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_observed_revision_positive": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "observed_revision" + ] + ], + [ + "binary", + ">", + [ + "column", + "observed_revision" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_desired_state": { + "expression": [ + "in", + [ + "column", + "desired_state" + ], + [ + [ + "literal", + "published" + ], + [ + "literal", + "unpublished" + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_state": { + "expression": [ + "in", + [ + "column", + "state" + ], + [ + [ + "literal", + "unpublished" + ], + [ + "literal", + "pending" + ], + [ + "literal", + "current" + ], + [ + "literal", + "update_available" + ], + [ + "literal", + "delivery_failed" + ], + [ + "literal", + "conflict" + ], + [ + "literal", + "drifted" + ], + [ + "literal", + "incompatible" + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_observed_generation_nonnegative": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "observed_generation" + ] + ], + [ + "binary", + ">=", + [ + "column", + "observed_generation" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_skill_publications_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_source_cursors": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "cursor": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_source_cursors_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_source_definition_manifests": { + "columns": { + "definition_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "definition_version": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "fingerprint": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "manifest": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "definition_name", + null, + "asc" + ], + [ + "definition_version", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_source_definition_manifest_fingerprint": { + "columns": [ + [ + "definition_name", + null, + "asc" + ], + [ + "fingerprint", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_source_journal_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_source_journal_heads_position_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_sources": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "journal_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "source_type", + null, + "asc" + ], + [ + "source_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": { + "uq_pc_sources_scope_journal_position": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "journal_position", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_topic_memory_processing_targets": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "captured_flush_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "observed_dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_processing_target": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "target_request_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "source_through" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "captured_flush_generation" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "observed_dirty_generation" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_retrieval_shape": { + "columns": { + "singleton": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "shape": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "profile_fingerprint": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "singleton", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_memory_retrieval_shape_singleton": { + "expression": [ + "binary", + "=", + [ + "column", + "singleton" + ], + [ + "number", + 1 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_retrieval_shape_value": { + "expression": [ + "in", + [ + "column", + "shape" + ], + [ + [ + "literal", + "fts" + ], + [ + "literal", + "hybrid" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_retrieval_shape_profile": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "shape" + ], + [ + "literal", + "fts" + ] + ], + [ + "is_null", + [ + "column", + "profile_fingerprint" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "shape" + ], + [ + "literal", + "hybrid" + ] + ], + [ + "is_not_null", + [ + "column", + "profile_fingerprint" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_versions_target_complete": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "target_family" + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "target_family" + ] + ], + [ + "is_not_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "target_revision" + ], + [ + "number", + 0 + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "lifecycle_state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "active" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "replacement_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "merged_into_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "governance_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_heads_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_lifecycle_state": { + "expression": [ + "in", + [ + "column", + "lifecycle_state" + ], + [ + [ + "literal", + "active" + ], + [ + "literal", + "deprecated" + ], + [ + "literal", + "retired" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_governance_generation_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "governance_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_replacement_deprecated": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "replacement_artifact_id" + ] + ], + [ + "binary", + "=", + [ + "column", + "lifecycle_state" + ], + [ + "literal", + "deprecated" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_heads_merge_target": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "merged_into_id" + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "lifecycle_state" + ], + [ + "literal", + "deprecated" + ] + ], + [ + "binary", + "<>", + [ + "column", + "merged_into_id" + ], + [ + "column", + "artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "replacement_artifact_id" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_artifact_heads_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + } + }, + { + "pc_artifact_lineage_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "upstream_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "upstream_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "upstream_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "is_merge_input": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": [ + "literal", + "0" + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "upstream_family", + "upstream_artifact_id", + "upstream_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + } + }, + { + "pc_artifact_lineage_sources": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "source_type", + "source_id" + ], + "referred_schema": null, + "referred_table": "pc_sources", + "referred_columns": [ + "scope_id", + "source_type", + "source_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + } + }, + { + "pc_artifact_publications": { + "columns": { + "target_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "target_scope_id", + null, + "asc" + ], + [ + "target_family", + null, + "asc" + ], + [ + "target_artifact_id", + null, + "asc" + ], + [ + "target_revision", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "source_scope_id", + "source_family", + "source_artifact_id", + "source_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "target_scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": { + "uq_pc_artifact_publications_request": { + "columns": [ + [ + "target_scope_id", + null, + "asc" + ], + [ + "idempotency_key", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_dream_runs": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "run_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "accepted_at": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_dream_generation": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_dream_idempotency": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "principal_key", + null, + "asc" + ], + [ + "idempotency_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_dream_dispatch": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ], + [ + "status", + null, + "asc" + ], + [ + "request_generation", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_dream_list": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "accepted_at", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_memory_entry_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "memory_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "head_revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "entry_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_version_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "entry_content_hash": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "memory_artifact_id", + null, + "asc" + ], + [ + "entry_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "memory_artifact_id", + "entry_id", + "entry_version_id" + ], + "referred_schema": null, + "referred_table": "pc_memory_entry_versions", + "referred_columns": [ + "scope_id", + "memory_artifact_id", + "entry_id", + "entry_version_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_memory_entry_heads_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "head_revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_recurrence_match": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "match_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_item_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "failure_item_index": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_item_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_set_mode": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_set_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "signature_key": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "match_key", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_recurrence_match_result": { + "expression": [ + "in", + [ + "column", + "result" + ], + [ + [ + "literal", + "matched" + ], + [ + "literal", + "unmatched" + ], + [ + "literal", + "ambiguous" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_candidate_set_mode": { + "expression": [ + "in", + [ + "column", + "candidate_set_mode" + ], + [ + [ + "literal", + "handoff_citations" + ], + [ + "literal", + "scope_heads" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_failure_item_kind": { + "expression": [ + "in", + [ + "column", + "failure_item_kind" + ], + [ + [ + "literal", + "observation" + ], + [ + "literal", + "check" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_position_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "task_outcome_position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recurrence_match_item_index_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "failure_item_index" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_recurrence_match_identity": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "task_outcome_source_type", + null, + "asc" + ], + [ + "task_outcome_source_id", + null, + "asc" + ], + [ + "failure_item_kind", + null, + "asc" + ], + [ + "failure_item_index", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_recurrence_observation": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "observation_id": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "selection_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "verdict_key": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "event": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "match_basis": { + "type": [ + "varchar", + [ + "8" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "signature_key": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "signature_key_hash": { + "type": [ + "binary", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "task_outcome_source_type": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_source_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "task_outcome_position": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "observation_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_recurrence_observation_event": { + "expression": [ + "in", + [ + "column", + "event" + ], + [ + [ + "literal", + "selected" + ], + [ + "literal", + "recurred" + ], + [ + "literal", + "avoided" + ] + ] + ], + "enforced": true + }, + "ck_pc_recurrence_observation_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_recurrence_observation_position_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "task_outcome_position" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_recurrence_observation_selection": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "selection_key", + null, + "asc" + ] + ], + "unique": true + }, + "uq_pc_recurrence_observation_verdict": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "verdict_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_recurrence_observation_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "signature_key_hash", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_recurrence_observation_outcome": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "task_outcome_source_type", + null, + "asc" + ], + [ + "task_outcome_source_id", + null, + "asc" + ], + [ + "task_outcome_position", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_recurrence_observation_revision": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_scope_bindings": { + "columns": { + "integration": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "external_id_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "integration", + null, + "asc" + ], + [ + "kind", + null, + "asc" + ], + [ + "external_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + } + }, + { + "pc_scope_context_references": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "referenced_scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "referenced_scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "referenced_scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scope_context_references_not_self": { + "expression": [ + "binary", + "<>", + [ + "column", + "scope_id" + ], + [ + "column", + "referenced_scope_id" + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_scope_creation_requests": { + "columns": { + "idempotency_key": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "idempotency_key", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + } + }, + { + "pc_scope_external_references": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "kind": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "value": { + "type": [ + "varchar", + [ + "2000" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "value_search": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "value_digest": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_scope_external_references_ordinal_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "ordinal" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_scope_external_references_value": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "kind", + null, + "asc" + ], + [ + "value_digest", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + { + "pc_scope_settings": { + "columns": { + "name": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "name", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + } + }, + { + "pc_topic_memory_revision_publications": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "published_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_publications_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_publications_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_artifact_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_tags": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_type": { + "type": [ + "varchar", + [ + "12" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tag_key_hash": { + "type": [ + "binary", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tag_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tag": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "assigned_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id" + ], + "referred_schema": null, + "referred_table": "pc_artifact_heads", + "referred_columns": [ + "scope_id", + "family", + "artifact_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_tags_target": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "target_type" + ], + [ + "literal", + "artifact" + ] + ], + [ + "binary", + "=", + [ + "column", + "target_id" + ], + [ + "column", + "artifact_id" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "target_type" + ], + [ + "literal", + "memory_entry" + ] + ], + [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "memory" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_artifact_tags_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_artifact_tags_family_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_topic_memory_active_chunks": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "chunk_ordinal": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "start_offset": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "end_offset": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "chunk_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "policy_version": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "chunk_ordinal", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_topic_memory_revision_publications", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_active_chunks_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_ordinal_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "chunk_ordinal" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_start_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "start_offset" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_chunks_offsets": { + "expression": [ + "binary", + ">", + [ + "column", + "end_offset" + ], + [ + "column", + "start_offset" + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_topic_memory_active_chunks_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + } + }, + { + "pc_topic_memory_active_topics": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "title": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "summary": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "searchable_text": { + "type": [ + "mediumtext", + [] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "source_count": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_topic_memory_revision_publications", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_active_topics_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_topics_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_active_topics_sources_nonnegative": { + "expression": [ + "binary", + ">=", + [ + "column", + "source_count" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_topic_memory_active_topics_fts": { + "columns": [ + [ + "searchable_text", + null, + "asc" + ] + ], + "unique": false, + "kind": "fulltext", + "parser": "space", + "parser_properties": { + "min_token_size": 3, + "max_token_size": 84 + } + } + } + } + }, + { + "pc_profile_policies": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "generation_enabled": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "activation_mode": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "pending_candidate_id" + ], + "referred_schema": null, + "referred_table": "pc_artifact_candidate_heads", + "referred_columns": [ + "scope_id", + "candidate_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_profile_policies_activation_mode": { + "expression": [ + "in", + [ + "column", + "activation_mode" + ], + [ + [ + "literal", + "automatic" + ], + [ + "literal", + "review_required" + ] + ] + ], + "enforced": true + }, + "ck_pc_profile_policies_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + {} + ] + } + }, + "optional_table_options": { + "pc_access_audit": { + "auto_increment_mode": [ + "order" + ] + } + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1400.json b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1400.json new file mode 100644 index 0000000000..91b7920c33 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1400.json @@ -0,0 +1,11484 @@ +{ + "format": "powercontext.mysql-state-layouts.v1", + "revisions": { + "s1400": { + "statements": [ + "CREATE TABLE pc_retained_dream_candidate_versions (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tproposal MEDIUMBLOB NOT NULL, \n\tsource_refs MEDIUMBLOB NOT NULL, \n\tartifact_refs MEDIUMBLOB NOT NULL, \n\ttarget_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\ttarget_revision INTEGER, \n\treason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tCONSTRAINT ck_pc_retained_dream_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_retained_dream_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)", + "INSERT IGNORE INTO pc_retained_dream_candidate_versions (scope_id, candidate_id, version, family, proposal, source_refs, artifact_refs, target_family, target_artifact_id, target_revision, reason) SELECT scope_id, candidate_id, version, family, proposal, source_refs, artifact_refs, target_family, target_artifact_id, target_revision, reason FROM pc_artifact_candidate_versions", + "CREATE TABLE pc_retained_dream_candidate_heads (\n\tscope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tcandidate_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tfamily VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tresult_family VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tresult_artifact_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, \n\tresult_revision INTEGER, \n\tdecision_reason MEDIUMTEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tCONSTRAINT ck_pc_retained_dream_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_retained_dream_heads_terminal_result CHECK ((status = 'approved' AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'rejected' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)", + "INSERT IGNORE INTO pc_retained_dream_candidate_heads (scope_id, candidate_id, family, version, status, result_family, result_artifact_id, result_revision, decision_reason) SELECT scope_id, candidate_id, family, version, status, result_family, result_artifact_id, result_revision, decision_reason FROM pc_artifact_candidate_heads", + "ALTER TABLE pc_artifact_candidate_versions RENAME TO pc_candidate_versions", + "ALTER TABLE pc_artifact_candidate_heads RENAME TO pc_candidate_heads", + "ALTER TABLE pc_candidate_heads ADD COLUMN candidate_kind VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'artifact'", + "ALTER TABLE pc_candidate_heads ADD COLUMN result_payload MEDIUMBLOB", + "ALTER TABLE pc_candidate_versions DROP CHECK ck_pc_artifact_candidate_versions_target_complete", + "ALTER TABLE pc_candidate_versions DROP CHECK ck_pc_artifact_candidate_versions_version_positive", + "ALTER TABLE pc_candidate_versions ADD CONSTRAINT ck_pc_candidate_versions_version_positive CHECK (version > 0)", + "ALTER TABLE pc_candidate_versions ADD CONSTRAINT ck_pc_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))", + "ALTER TABLE pc_candidate_versions ADD CONSTRAINT fk_pc_candidate_versions_scope FOREIGN KEY (scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE", + "ALTER TABLE pc_candidate_heads DROP CHECK ck_pc_artifact_candidate_heads_status", + "ALTER TABLE pc_candidate_heads DROP CHECK ck_pc_artifact_candidate_heads_terminal_result", + "ALTER TABLE pc_candidate_heads ADD CONSTRAINT ck_pc_candidate_kind CHECK (candidate_kind IN ('artifact', 'tag'))", + "ALTER TABLE pc_candidate_heads ADD CONSTRAINT ck_pc_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected'))", + "ALTER TABLE pc_candidate_heads ADD CONSTRAINT ck_pc_candidate_heads_terminal_result CHECK ((status = 'approved' AND candidate_kind = 'artifact' AND result_payload IS NULL AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'approved' AND candidate_kind = 'tag' AND result_payload IS NOT NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL) OR (status = 'rejected' AND result_payload IS NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_payload IS NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))", + "ALTER TABLE pc_candidate_heads ADD CONSTRAINT fk_pc_candidate_heads_scope FOREIGN KEY (scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE", + "@mysql_operations.py:drop_candidate_head_version_fk", + "ALTER TABLE pc_candidate_heads ADD CONSTRAINT fk_pc_candidate_head_version FOREIGN KEY (scope_id, candidate_id, version) REFERENCES pc_candidate_versions (scope_id, candidate_id, version) ON DELETE CASCADE", + "ALTER TABLE pc_dream_runs ADD COLUMN proposal_fingerprint VARCHAR(71)", + "ALTER TABLE pc_artifact_processing_intents ADD COLUMN consecutive_dream_attempts BIGINT NOT NULL DEFAULT '0'", + "CREATE INDEX ix_pc_dream_runs_proposal_fingerprint ON pc_dream_runs (scope_id, principal_key, proposal_fingerprint)", + "ALTER TABLE pc_artifacts ADD COLUMN `created_at` datetime DEFAULT NULL", + "ALTER TABLE pc_artifacts ADD COLUMN `created_by` varchar(2048) DEFAULT NULL", + "ALTER TABLE pc_artifacts ADD COLUMN `restored_from_revision` int(11) DEFAULT NULL", + "ALTER TABLE pc_artifacts ADD COLUMN `rollback_reason` varchar(1024) DEFAULT NULL", + "ALTER TABLE pc_topic_memory_work_budgets ADD COLUMN `attempt_source_through` bigint(20) DEFAULT NULL", + "ALTER TABLE pc_topic_memory_work_budgets ADD COLUMN `started_at` datetime DEFAULT NULL", + "ALTER TABLE pc_topic_memory_work_budgets ADD COLUMN `finished_at` datetime DEFAULT NULL", + "ALTER TABLE pc_topic_memory_work_budgets ADD COLUMN `supervisor_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL", + "ALTER TABLE pc_topic_memory_work_budgets ADD COLUMN `supervisor_generation` bigint(20) DEFAULT NULL", + "ALTER TABLE pc_topic_memory_work_budgets ADD COLUMN `holder_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL", + "ALTER TABLE pc_topic_memory_work_budgets ADD COLUMN `last_error_code` varchar(64) DEFAULT NULL", + "ALTER TABLE pc_topic_memory_work_budgets ADD CONSTRAINT `ck_pc_topic_budget_attempt_range` CHECK (((`attempt_source_through` is null) or ((`attempt_source_through` > `source_after`) and (`attempt_source_through` <= `source_through`))))", + "CREATE TABLE `pc_artifact_handles` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `handle_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n PRIMARY KEY (`scope_id`, `family`, `handle_key`),\n FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `ck_pc_artifact_handles_revision_positive` CHECK ((`revision` > 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_context_bootstrap_receipts` (\n `receipt_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `event_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `request_digest` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `integration` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `lifecycle` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `profile` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `reason` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `max_bytes` int(11) NOT NULL,\n `package_digest` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `content_bytes` int(11) NOT NULL,\n `truncated` tinyint(1) NOT NULL,\n `items` mediumblob NOT NULL,\n `created_at` datetime NOT NULL,\n PRIMARY KEY (`receipt_id`),\n FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n UNIQUE KEY `uq_pc_context_bootstrap_receipts_event` (`event_key`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_context_bootstrap_receipts_created_at` (`created_at`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_context_bootstrap_receipts_state` CHECK ((`state` in ('pending','injected','skipped','failed'))),\n CONSTRAINT `ck_pc_context_bootstrap_receipts_budget` CHECK (((`max_bytes` >= 512) and (`max_bytes` <= 8192))),\n CONSTRAINT `ck_pc_context_bootstrap_receipts_content_bytes` CHECK ((`content_bytes` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_lexical_projection_versions` (\n `projection` varchar(128) NOT NULL,\n `analyzer_version` varchar(64) NOT NULL,\n PRIMARY KEY (`projection`)\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "CREATE TABLE `pc_topic_memory_work_blocks` (\n `block_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `attempt_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `binding_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `source_after` bigint(20) NOT NULL,\n `source_through` bigint(20) DEFAULT NULL,\n `budget_source_through` bigint(20) DEFAULT NULL,\n `legacy_source_through` bigint(20) DEFAULT NULL,\n `target_source_through` bigint(20) NOT NULL,\n `attempts` int(11) NOT NULL,\n `requests` bigint(20) NOT NULL,\n `tokens` bigint(20) NOT NULL,\n `last_error_code` varchar(64) DEFAULT NULL,\n `error_code` varchar(64) NOT NULL,\n `blocked_at` datetime NOT NULL,\n `retried_at` datetime DEFAULT NULL,\n `retry_request_generation` bigint(20) DEFAULT NULL,\n PRIMARY KEY (`block_id`),\n UNIQUE KEY `uq_pc_topic_block_identity` (`scope_id`, `binding_name`, `source_after`, `attempt_id`) BLOCK_SIZE 16384 LOCAL,\n CONSTRAINT `ck_pc_topic_block_window` CHECK (((`source_after` >= 0) and (`target_source_through` > `source_after`) and ((`source_through` is null) or ((`source_through` > `source_after`) and (`source_through` <= `target_source_through`))) and ((`budget_source_through` is null) or ((`budget_source_through` > `source_after`) and (`budget_source_through` <= `target_source_through`))) and ((`source_through` is null) or (`budget_source_through` is null) or (`source_through` <= `budget_source_through`)) and ((`legacy_source_through` is null) or ((`legacy_source_through` > `source_after`) and (`legacy_source_through` <= `target_source_through`))))),\n CONSTRAINT `ck_pc_topic_block_usage` CHECK (((`attempts` > 0) and (`requests` >= 0) and (`tokens` >= 0))),\n CONSTRAINT `ck_pc_topic_block_acceptance` CHECK ((((`retried_at` is null) and (`retry_request_generation` is null)) or ((`retried_at` is not null) and (`retry_request_generation` is not null) and (`retry_request_generation` > 0))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "UPDATE pc_artifact_processing_leases SET supervisor_generation=supervisor_generation+1, holder_id='migration-s1400', lease_expires_at='1970-01-01 00:00:00' WHERE holder_id <> 'migration-s1400' OR lease_expires_at IS NULL OR lease_expires_at <> '1970-01-01 00:00:00'" + ], + "changes": [ + { + "pc_retained_dream_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_retained_dream_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_retained_dream_versions_target_complete": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "target_family" + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "target_family" + ] + ], + [ + "is_not_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "target_revision" + ], + [ + "number", + 0 + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + {}, + { + "pc_retained_dream_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_retained_dream_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_retained_dream_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + {}, + { + "pc_artifact_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_artifact_candidate_versions": null, + "pc_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_versions_target_complete": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "target_family" + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "target_family" + ] + ], + [ + "is_not_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "target_revision" + ], + [ + "number", + 0 + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_candidate_heads": null, + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + }, + "pc_profile_policies": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "generation_enabled": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "activation_mode": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "pending_candidate_id" + ], + "referred_schema": null, + "referred_table": "pc_candidate_heads", + "referred_columns": [ + "scope_id", + "candidate_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_profile_policies_activation_mode": { + "expression": [ + "in", + [ + "column", + "activation_mode" + ], + [ + [ + "literal", + "automatic" + ], + [ + "literal", + "review_required" + ] + ] + ], + "enforced": true + }, + "ck_pc_profile_policies_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_artifact_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + } + }, + { + "pc_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_candidate_versions_target_complete": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "target_family" + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "target_family" + ] + ], + [ + "is_not_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "target_revision" + ], + [ + "number", + 0 + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_versions": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "proposal": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "artifact_refs": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ], + [ + "version", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "target_family", + "target_artifact_id", + "target_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_versions_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_candidate_versions_target_complete": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "target_family" + ] + ], + [ + "is_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "target_revision" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "target_family" + ] + ], + [ + "is_not_null", + [ + "column", + "target_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "target_revision" + ], + [ + "number", + 0 + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": {}, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_kind": { + "expression": [ + "in", + [ + "column", + "candidate_kind" + ], + [ + [ + "literal", + "artifact" + ], + [ + "literal", + "tag" + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_kind": { + "expression": [ + "in", + [ + "column", + "candidate_kind" + ], + [ + [ + "literal", + "artifact" + ], + [ + "literal", + "tag" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_kind": { + "expression": [ + "in", + [ + "column", + "candidate_kind" + ], + [ + [ + "literal", + "artifact" + ], + [ + "literal", + "tag" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "binary", + "=", + [ + "column", + "candidate_kind" + ], + [ + "literal", + "artifact" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "binary", + "=", + [ + "column", + "candidate_kind" + ], + [ + "literal", + "tag" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "restrict", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_kind": { + "expression": [ + "in", + [ + "column", + "candidate_kind" + ], + [ + [ + "literal", + "artifact" + ], + [ + "literal", + "tag" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "binary", + "=", + [ + "column", + "candidate_kind" + ], + [ + "literal", + "artifact" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "binary", + "=", + [ + "column", + "candidate_kind" + ], + [ + "literal", + "tag" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_kind": { + "expression": [ + "in", + [ + "column", + "candidate_kind" + ], + [ + [ + "literal", + "artifact" + ], + [ + "literal", + "tag" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "binary", + "=", + [ + "column", + "candidate_kind" + ], + [ + "literal", + "artifact" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "binary", + "=", + [ + "column", + "candidate_kind" + ], + [ + "literal", + "tag" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_candidate_heads": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "decision_reason": { + "type": [ + "mediumtext", + [] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "candidate_kind": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": [ + "literal", + "artifact" + ], + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "result_payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "candidate_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "candidate_id", + "version" + ], + "referred_schema": null, + "referred_table": "pc_candidate_versions", + "referred_columns": [ + "scope_id", + "candidate_id", + "version" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "result_family", + "result_artifact_id", + "result_revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_candidate_kind": { + "expression": [ + "in", + [ + "column", + "candidate_kind" + ], + [ + [ + "literal", + "artifact" + ], + [ + "literal", + "tag" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "approved" + ], + [ + "literal", + "rejected" + ] + ] + ], + "enforced": true + }, + "ck_pc_candidate_heads_terminal_result": { + "expression": [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "binary", + "=", + [ + "column", + "candidate_kind" + ], + [ + "literal", + "artifact" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "result_revision" + ], + [ + "number", + 0 + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "approved" + ] + ], + [ + "binary", + "=", + [ + "column", + "candidate_kind" + ], + [ + "literal", + "tag" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "rejected" + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_not_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "status" + ], + [ + "literal", + "pending" + ] + ], + [ + "is_null", + [ + "column", + "result_payload" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_family" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_artifact_id" + ] + ] + ], + [ + "is_null", + [ + "column", + "result_revision" + ] + ] + ], + [ + "is_null", + [ + "column", + "decision_reason" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_dream_runs": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "run_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "accepted_at": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "proposal_fingerprint": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_dream_generation": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_dream_idempotency": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "principal_key", + null, + "asc" + ], + [ + "idempotency_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_dream_dispatch": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ], + [ + "status", + null, + "asc" + ], + [ + "request_generation", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_dream_list": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "accepted_at", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_artifact_processing_intents": { + "columns": { + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_sequence": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "dirty_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "clean_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "requested_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "handled_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "last_auto_scan_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + }, + "consecutive_dream_attempts": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": [ + "number", + 0 + ], + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "binding_name", + null, + "asc" + ], + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_processing_intent_sequence": { + "expression": [ + "binary", + ">", + [ + "column", + "pending_sequence" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_dirty": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "clean_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "clean_generation" + ], + [ + "column", + "dirty_generation" + ] + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_request": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "handled_generation" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + "<=", + [ + "column", + "handled_generation" + ], + [ + "column", + "requested_generation" + ] + ] + ], + "enforced": true + }, + "ck_pc_processing_intent_scan": { + "expression": [ + "binary", + ">=", + [ + "column", + "last_auto_scan_generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "pending_sequence": { + "columns": [ + [ + "pending_sequence", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_processing_intent_binding_sequence": { + "columns": [ + [ + "binding_name", + null, + "asc" + ], + [ + "pending_sequence", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_dream_runs": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "run_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "principal_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "idempotency_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "operation": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "accepted_at": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "generation": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "payload": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "proposal_fingerprint": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_dream_generation": { + "expression": [ + "binary", + ">=", + [ + "column", + "generation" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_dream_idempotency": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "principal_key", + null, + "asc" + ], + [ + "idempotency_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_dream_dispatch": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ], + [ + "status", + null, + "asc" + ], + [ + "request_generation", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_dream_list": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "accepted_at", + null, + "asc" + ], + [ + "run_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_dream_runs_proposal_fingerprint": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "principal_key", + null, + "asc" + ], + [ + "proposal_fingerprint", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_by": { + "type": [ + "varchar", + [ + "2048" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_by": { + "type": [ + "varchar", + [ + "2048" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "restored_from_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_by": { + "type": [ + "varchar", + [ + "2048" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "restored_from_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "rollback_reason": { + "type": [ + "varchar", + [ + "1024" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "attempt_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "attempt_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "started_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "attempt_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "started_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "finished_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "attempt_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "started_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "finished_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "supervisor_group": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "attempt_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "started_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "finished_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "supervisor_group": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "supervisor_generation": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "attempt_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "started_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "finished_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "supervisor_group": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "supervisor_generation": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "holder_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "attempt_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "started_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "finished_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "supervisor_group": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "supervisor_generation": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "holder_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_error_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_work_budgets": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "failure_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "attempt_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "started_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "finished_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "supervisor_group": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "supervisor_generation": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "holder_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "last_error_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_budget_window": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_budget_attempt_range": { + "expression": [ + "binary", + "or", + [ + "is_null", + [ + "column", + "attempt_source_through" + ] + ], + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempt_source_through" + ], + [ + "column", + "source_after" + ] + ], + [ + "binary", + "<=", + [ + "column", + "attempt_source_through" + ], + [ + "column", + "source_through" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_handles": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "handle_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "handle_key", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_handles_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_context_bootstrap_receipts": { + "columns": { + "receipt_id": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "event_key": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "request_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "integration": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "lifecycle": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "profile": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "state": { + "type": [ + "varchar", + [ + "16" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "reason": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "max_bytes": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "package_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "content_bytes": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "truncated": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "items": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "receipt_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_context_bootstrap_receipts_state": { + "expression": [ + "in", + [ + "column", + "state" + ], + [ + [ + "literal", + "pending" + ], + [ + "literal", + "injected" + ], + [ + "literal", + "skipped" + ], + [ + "literal", + "failed" + ] + ] + ], + "enforced": true + }, + "ck_pc_context_bootstrap_receipts_budget": { + "expression": [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "max_bytes" + ], + [ + "number", + 512 + ] + ], + [ + "binary", + "<=", + [ + "column", + "max_bytes" + ], + [ + "number", + 8192 + ] + ] + ], + "enforced": true + }, + "ck_pc_context_bootstrap_receipts_content_bytes": { + "expression": [ + "binary", + ">=", + [ + "column", + "content_bytes" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_context_bootstrap_receipts_event": { + "columns": [ + [ + "event_key", + null, + "asc" + ] + ], + "unique": true + }, + "ix_pc_context_bootstrap_receipts_created_at": { + "columns": [ + [ + "created_at", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "pc_lexical_projection_versions": { + "columns": { + "projection": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "analyzer_version": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "projection", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_topic_memory_work_blocks": { + "columns": { + "block_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "attempt_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "binding_name": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "source_after": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "budget_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "legacy_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "target_source_through": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "attempts": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "requests": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tokens": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "last_error_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "error_code": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "blocked_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "retried_at": { + "type": [ + "datetime", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "retry_request_generation": { + "type": [ + "bigint", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "block_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_topic_block_window": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">=", + [ + "column", + "source_after" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">", + [ + "column", + "target_source_through" + ], + [ + "column", + "source_after" + ] + ] + ], + [ + "binary", + "or", + [ + "is_null", + [ + "column", + "source_through" + ] + ], + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "source_through" + ], + [ + "column", + "source_after" + ] + ], + [ + "binary", + "<=", + [ + "column", + "source_through" + ], + [ + "column", + "target_source_through" + ] + ] + ] + ] + ], + [ + "binary", + "or", + [ + "is_null", + [ + "column", + "budget_source_through" + ] + ], + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "budget_source_through" + ], + [ + "column", + "source_after" + ] + ], + [ + "binary", + "<=", + [ + "column", + "budget_source_through" + ], + [ + "column", + "target_source_through" + ] + ] + ] + ] + ], + [ + "binary", + "or", + [ + "binary", + "or", + [ + "is_null", + [ + "column", + "source_through" + ] + ], + [ + "is_null", + [ + "column", + "budget_source_through" + ] + ] + ], + [ + "binary", + "<=", + [ + "column", + "source_through" + ], + [ + "column", + "budget_source_through" + ] + ] + ] + ], + [ + "binary", + "or", + [ + "is_null", + [ + "column", + "legacy_source_through" + ] + ], + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "legacy_source_through" + ], + [ + "column", + "source_after" + ] + ], + [ + "binary", + "<=", + [ + "column", + "legacy_source_through" + ], + [ + "column", + "target_source_through" + ] + ] + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_block_usage": { + "expression": [ + "binary", + "and", + [ + "binary", + "and", + [ + "binary", + ">", + [ + "column", + "attempts" + ], + [ + "number", + 0 + ] + ], + [ + "binary", + ">=", + [ + "column", + "requests" + ], + [ + "number", + 0 + ] + ] + ], + [ + "binary", + ">=", + [ + "column", + "tokens" + ], + [ + "number", + 0 + ] + ] + ], + "enforced": true + }, + "ck_pc_topic_block_acceptance": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "is_null", + [ + "column", + "retried_at" + ] + ], + [ + "is_null", + [ + "column", + "retry_request_generation" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "and", + [ + "is_not_null", + [ + "column", + "retried_at" + ] + ], + [ + "is_not_null", + [ + "column", + "retry_request_generation" + ] + ] + ], + [ + "binary", + ">", + [ + "column", + "retry_request_generation" + ], + [ + "number", + 0 + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "uq_pc_topic_block_identity": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "binding_name", + null, + "asc" + ], + [ + "source_after", + null, + "asc" + ], + [ + "attempt_id", + null, + "asc" + ] + ], + "unique": true + } + } + } + }, + {} + ] + } + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1401.json b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1401.json new file mode 100644 index 0000000000..3e2fe4c39b --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1401.json @@ -0,0 +1,126 @@ +{ + "format": "powercontext.mysql-state-layouts.v1", + "revisions": { + "s1401": { + "statements": [ + "CREATE TABLE pc_memory_artifact_archive (\n\tscope_id VARCHAR(256) COLLATE utf8mb4_bin NOT NULL, \n\tfamily VARCHAR(128) COLLATE utf8mb4_bin NOT NULL, \n\tartifact_id VARCHAR(128) COLLATE utf8mb4_bin NOT NULL, \n\trevision INTEGER NOT NULL, \n\tcontent LONGBLOB NOT NULL, \n\tmemory_citations LONGBLOB, \n\tmetadata LONGBLOB NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision)\n)" + ], + "changes": [ + { + "pc_memory_artifact_archive": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "longblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "memory_citations": { + "type": [ + "longblob", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "metadata": { + "type": [ + "longblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + } + ] + } + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1402.json b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1402.json new file mode 100644 index 0000000000..cdc2492aff --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/mysql-s1402.json @@ -0,0 +1,956 @@ +{ + "format": "powercontext.mysql-state-layouts.v1", + "revisions": { + "s1402": { + "statements": [ + "CREATE TABLE pc_portable_restore_receipts (\n\tbundle_id VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tformat_version INTEGER NOT NULL, \n\ttotal_digest VARCHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\trecord_count BIGINT NOT NULL, \n\tstatus VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, \n\tinserted BIGINT NOT NULL, \n\talready_present BIGINT NOT NULL, \n\tprojections_ready TINYINT(1) NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (bundle_id), \n\tCONSTRAINT ck_pc_portable_restore_receipts_status CHECK (status IN ('authoritative_restored', 'ready')), \n\tCONSTRAINT ck_pc_portable_restore_receipts_record_count CHECK (record_count >= 0), \n\tCONSTRAINT ck_pc_portable_restore_receipts_inserted CHECK (inserted >= 0), \n\tCONSTRAINT ck_pc_portable_restore_receipts_existing CHECK (already_present >= 0)\n)", + "ALTER TABLE `pc_artifacts` MODIFY COLUMN `created_at` DATETIME(6) NULL", + "ALTER TABLE `pc_profile_policies` MODIFY COLUMN `updated_at` DATETIME(6) NOT NULL", + "ALTER TABLE `pc_topic_memory_revision_publications` MODIFY COLUMN `published_at` DATETIME(6) NOT NULL", + "ALTER TABLE `pc_artifact_tags` MODIFY COLUMN `assigned_at` DATETIME(6) NOT NULL" + ], + "changes": [ + { + "pc_portable_restore_receipts": { + "columns": { + "bundle_id": { + "type": [ + "varchar", + [ + "36" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "format_version": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "total_digest": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "record_count": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "status": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "inserted": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "already_present": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "projections_ready": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "bundle_id", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": { + "ck_pc_portable_restore_receipts_status": { + "expression": [ + "in", + [ + "column", + "status" + ], + [ + [ + "literal", + "authoritative_restored" + ], + [ + "literal", + "ready" + ] + ] + ], + "enforced": true + }, + "ck_pc_portable_restore_receipts_record_count": { + "expression": [ + "binary", + ">=", + [ + "column", + "record_count" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_portable_restore_receipts_inserted": { + "expression": [ + "binary", + ">=", + [ + "column", + "inserted" + ], + [ + "number", + 0 + ] + ], + "enforced": true + }, + "ck_pc_portable_restore_receipts_existing": { + "expression": [ + "binary", + ">=", + [ + "column", + "already_present" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifacts": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "content": { + "type": [ + "mediumblob", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_at": { + "type": [ + "datetime", + [ + "6" + ] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "created_by": { + "type": [ + "varchar", + [ + "2048" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "restored_from_revision": { + "type": [ + "int", + [] + ], + "nullable": true, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "rollback_reason": { + "type": [ + "varchar", + [ + "1024" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [], + "checks": {}, + "indexes": {} + } + }, + { + "pc_profile_policies": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "generation_enabled": { + "type": [ + "tinyint", + [ + "1" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "activation_mode": { + "type": [ + "varchar", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "pending_candidate_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "version": { + "type": [ + "bigint", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "updated_at": { + "type": [ + "datetime", + [ + "6" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id" + ], + "referred_schema": null, + "referred_table": "pc_scopes", + "referred_columns": [ + "scope_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + }, + { + "columns": [ + "scope_id", + "pending_candidate_id" + ], + "referred_schema": null, + "referred_table": "pc_candidate_heads", + "referred_columns": [ + "scope_id", + "candidate_id" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_profile_policies_activation_mode": { + "expression": [ + "in", + [ + "column", + "activation_mode" + ], + [ + [ + "literal", + "automatic" + ], + [ + "literal", + "review_required" + ] + ] + ], + "enforced": true + }, + "ck_pc_profile_policies_version_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "version" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_topic_memory_revision_publications": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "revision": { + "type": [ + "int", + [] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "published_at": { + "type": [ + "datetime", + [ + "6" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "revision", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "referred_schema": null, + "referred_table": "pc_artifacts", + "referred_columns": [ + "scope_id", + "family", + "artifact_id", + "revision" + ], + "ondelete": "restrict", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_topic_memory_publications_family": { + "expression": [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "topic-memory" + ] + ], + "enforced": true + }, + "ck_pc_topic_memory_publications_revision_positive": { + "expression": [ + "binary", + ">", + [ + "column", + "revision" + ], + [ + "number", + 0 + ] + ], + "enforced": true + } + }, + "indexes": {} + } + }, + { + "pc_artifact_tags": { + "columns": { + "scope_id": { + "type": [ + "varchar", + [ + "256" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "family": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "artifact_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_type": { + "type": [ + "varchar", + [ + "12" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "target_id": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tag_key_hash": { + "type": [ + "binary", + [ + "32" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + }, + "tag_key": { + "type": [ + "varchar", + [ + "128" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": "utf8mb4_bin", + "extra": [] + }, + "tag": { + "type": [ + "varchar", + [ + "64" + ] + ], + "nullable": false, + "default": null, + "charset": "utf8mb4", + "collation": null, + "extra": [] + }, + "assigned_at": { + "type": [ + "datetime", + [ + "6" + ] + ], + "nullable": false, + "default": null, + "charset": null, + "collation": null, + "extra": [] + } + }, + "primary_key": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ] + ], + "foreign_keys": [ + { + "columns": [ + "scope_id", + "family", + "artifact_id" + ], + "referred_schema": null, + "referred_table": "pc_artifact_heads", + "referred_columns": [ + "scope_id", + "family", + "artifact_id" + ], + "ondelete": "cascade", + "onupdate": "restrict" + } + ], + "checks": { + "ck_pc_artifact_tags_target": { + "expression": [ + "binary", + "or", + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "target_type" + ], + [ + "literal", + "artifact" + ] + ], + [ + "binary", + "=", + [ + "column", + "target_id" + ], + [ + "column", + "artifact_id" + ] + ] + ], + [ + "binary", + "and", + [ + "binary", + "=", + [ + "column", + "target_type" + ], + [ + "literal", + "memory_entry" + ] + ], + [ + "binary", + "=", + [ + "column", + "family" + ], + [ + "literal", + "memory" + ] + ] + ] + ], + "enforced": true + } + }, + "indexes": { + "ix_pc_artifact_tags_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "unique": false + }, + "ix_pc_artifact_tags_family_key": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "family", + null, + "asc" + ], + [ + "tag_key_hash", + null, + "asc" + ], + [ + "target_type", + null, + "asc" + ], + [ + "artifact_id", + null, + "asc" + ], + [ + "target_id", + null, + "asc" + ] + ], + "unique": false + } + } + } + } + ] + } + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite-s1401.json b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite-s1401.json new file mode 100644 index 0000000000..8e3b8d87c4 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite-s1401.json @@ -0,0 +1,3 @@ +{ + "create": "CREATE TABLE pc_memory_artifact_archive (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tcontent BLOB NOT NULL, \n\tmemory_citations BLOB, \n\tmetadata BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision)\n)" +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite-s1402.json b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite-s1402.json new file mode 100644 index 0000000000..bc1d26631c --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite-s1402.json @@ -0,0 +1,3 @@ +{ + "create": "CREATE TABLE pc_portable_restore_receipts (\n\tbundle_id VARCHAR(36) NOT NULL, \n\tformat_version INTEGER NOT NULL, \n\ttotal_digest VARCHAR(71) NOT NULL, \n\trecord_count BIGINT NOT NULL, \n\tstatus VARCHAR(32) NOT NULL, \n\tinserted BIGINT NOT NULL, \n\talready_present BIGINT NOT NULL, \n\tprojections_ready BOOLEAN NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (bundle_id), \n\tCONSTRAINT ck_pc_portable_restore_receipts_status CHECK (status IN ('authoritative_restored', 'ready')), \n\tCONSTRAINT ck_pc_portable_restore_receipts_record_count CHECK (record_count >= 0), \n\tCONSTRAINT ck_pc_portable_restore_receipts_inserted CHECK (inserted >= 0), \n\tCONSTRAINT ck_pc_portable_restore_receipts_existing CHECK (already_present >= 0)\n)" +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite.json b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite.json new file mode 100644 index 0000000000..436c275398 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/schemas/sqlite.json @@ -0,0 +1,1246 @@ +{ + "source": [ + [ + "index", + "ix_pc_artifact_tags_family_key", + "pc_artifact_tags", + "CREATE INDEX ix_pc_artifact_tags_family_key ON pc_artifact_tags (scope_id, family, tag_key_hash, target_type, artifact_id, target_id)" + ], + [ + "index", + "ix_pc_artifact_tags_key", + "pc_artifact_tags", + "CREATE INDEX ix_pc_artifact_tags_key ON pc_artifact_tags (scope_id, tag_key_hash, family, target_type, artifact_id, target_id)" + ], + [ + "index", + "ix_pc_decision_observations_operation", + "pc_decision_observations", + "CREATE INDEX ix_pc_decision_observations_operation ON pc_decision_observations (scope_id, operation_id, created_at)" + ], + [ + "index", + "ix_pc_decision_observations_scope_created", + "pc_decision_observations", + "CREATE INDEX ix_pc_decision_observations_scope_created ON pc_decision_observations (scope_id, created_at, observation_id)" + ], + [ + "index", + "ix_pc_dream_dispatch", + "pc_dream_runs", + "CREATE INDEX ix_pc_dream_dispatch ON pc_dream_runs (scope_id, operation, status, request_generation)" + ], + [ + "index", + "ix_pc_dream_list", + "pc_dream_runs", + "CREATE INDEX ix_pc_dream_list ON pc_dream_runs (scope_id, accepted_at, run_id)" + ], + [ + "index", + "ix_pc_processing_intent_binding_sequence", + "pc_artifact_processing_intents", + "CREATE INDEX ix_pc_processing_intent_binding_sequence ON pc_artifact_processing_intents (binding_name, pending_sequence)" + ], + [ + "index", + "ix_pc_recurrence_observation_key", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_key ON pc_recurrence_observation (scope_id, signature_key_hash)" + ], + [ + "index", + "ix_pc_recurrence_observation_outcome", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_outcome ON pc_recurrence_observation (scope_id, task_outcome_source_type, task_outcome_source_id, task_outcome_position)" + ], + [ + "index", + "ix_pc_recurrence_observation_revision", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_revision ON pc_recurrence_observation (scope_id, family, artifact_id, revision)" + ], + [ + "table", + "pc_access_audit", + "pc_access_audit", + "CREATE TABLE pc_access_audit (\n\tcursor INTEGER NOT NULL, \n\tevent_id VARCHAR(64) NOT NULL, \n\toccurred_at VARCHAR(32) NOT NULL, \n\trequest_id VARCHAR(128), \n\ttransport VARCHAR(16) NOT NULL, \n\toperation VARCHAR(128) NOT NULL, \n\tprincipal_type VARCHAR(16) NOT NULL, \n\tprincipal_id VARCHAR(255) NOT NULL, \n\tprincipal_description TEXT, \n\tactor_type VARCHAR(16), \n\tactor_id VARCHAR(255), \n\tactor_description TEXT, \n\taction VARCHAR(64) NOT NULL, \n\tresource_type VARCHAR(16) NOT NULL, \n\tdeployment_id VARCHAR(128), \n\tscope_id VARCHAR(256), \n\tfamily VARCHAR(128), \n\tartifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\tallowed BOOLEAN NOT NULL, \n\treason_code VARCHAR(64) NOT NULL, \n\tpolicy_revision VARCHAR(64), \n\tmatched_subject_type VARCHAR(16), \n\tmatched_subject_id VARCHAR(255), \n\tmatched_subject_description TEXT, \n\tbinding_id VARCHAR(64), \n\ttarget_type VARCHAR(16), \n\ttarget_id VARCHAR(255), \n\ttarget_description TEXT, \n\trole VARCHAR(32), \n\texpected_version INTEGER, \n\tresult_version INTEGER, \n\tPRIMARY KEY (cursor), \n\tUNIQUE (event_id)\n)" + ], + [ + "table", + "pc_access_idempotency", + "pc_access_idempotency", + "CREATE TABLE pc_access_idempotency (\n\tactor_id VARCHAR(255) NOT NULL, \n\tidempotency_key_hash VARCHAR(64) NOT NULL, \n\toperation VARCHAR(64) NOT NULL, \n\tpayload_hash VARCHAR(64) NOT NULL, \n\tresult_binding_id VARCHAR(64), \n\tsecondary_binding_id VARCHAR(64), \n\tPRIMARY KEY (actor_id, idempotency_key_hash), \n\tCONSTRAINT uq_pc_access_idempotency_actor_key UNIQUE (actor_id, idempotency_key_hash)\n)" + ], + [ + "table", + "pc_access_owners", + "pc_access_owners", + "CREATE TABLE pc_access_owners (\n\towner_kind VARCHAR(16) NOT NULL, \n\tobject_key_hash VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128), \n\tcandidate_id VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\towner_type VARCHAR(16) NOT NULL, \n\towner_id VARCHAR(255) NOT NULL, \n\towner_description TEXT, \n\testablished_at VARCHAR(32), \n\tpolicy_revision VARCHAR(64), \n\tidempotency_key VARCHAR(255) NOT NULL, \n\tPRIMARY KEY (owner_kind, object_key_hash), \n\tCONSTRAINT ck_pc_access_owners_kind CHECK ((owner_kind = 'artifact' AND artifact_id IS NOT NULL AND candidate_id IS NULL AND target_artifact_id IS NULL AND established_at IS NOT NULL AND policy_revision IS NOT NULL) OR (owner_kind = 'candidate' AND candidate_id IS NOT NULL AND artifact_id IS NULL AND selector_type IS NULL AND selector_entry_id IS NULL AND established_at IS NULL AND policy_revision IS NULL))\n)" + ], + [ + "table", + "pc_access_relationship_heads", + "pc_access_relationship_heads", + "CREATE TABLE pc_access_relationship_heads (\n\tname VARCHAR(32) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tPRIMARY KEY (name), \n\tCONSTRAINT ck_pc_access_relationship_heads_revision_nonnegative CHECK (revision >= 0)\n)" + ], + [ + "table", + "pc_access_relationships", + "pc_access_relationships", + "CREATE TABLE pc_access_relationships (\n\tbinding_id VARCHAR(64) NOT NULL, \n\tsubject_type VARCHAR(16) NOT NULL, \n\tsubject_id VARCHAR(255) NOT NULL, \n\tsubject_description TEXT, \n\tresource_key_hash VARCHAR(64) NOT NULL, \n\tresource_type VARCHAR(16) NOT NULL, \n\tdeployment_id VARCHAR(128), \n\tscope_id VARCHAR(256), \n\tfamily VARCHAR(128), \n\tartifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\trole VARCHAR(32) NOT NULL, \n\tsingleton_key VARCHAR(64), \n\tgranted_by_type VARCHAR(16) NOT NULL, \n\tgranted_by_id VARCHAR(255) NOT NULL, \n\tgranted_by_description TEXT, \n\treason TEXT, \n\tcreated_at VARCHAR(32) NOT NULL, \n\texpires_at VARCHAR(32), \n\tstate VARCHAR(16) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tpolicy_revision VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(255) NOT NULL, \n\trevoked_at VARCHAR(32), \n\trevoked_by_type VARCHAR(16), \n\trevoked_by_id VARCHAR(255), \n\trevoked_by_description TEXT, \n\tPRIMARY KEY (binding_id), \n\tCONSTRAINT ck_pc_access_relationships_version_positive CHECK (version > 0), \n\tUNIQUE (singleton_key)\n)" + ], + [ + "table", + "pc_agent_skill_targets", + "pc_agent_skill_targets", + "CREATE TABLE pc_agent_skill_targets (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_id VARCHAR(64) NOT NULL, \n\tdisplay_name VARCHAR(128) NOT NULL, \n\tagent_kind VARCHAR(32) NOT NULL, \n\tinstallation_scope VARCHAR(16) NOT NULL, \n\tdelivery_mode VARCHAR(16) NOT NULL, \n\tinstallation_id VARCHAR(128), \n\tstate VARCHAR(16) NOT NULL, \n\tenrollment_token_digest VARCHAR(64), \n\tenrollment_expires_at DATETIME, \n\tcredential_subject VARCHAR(128), \n\tcredential_verifier VARCHAR(64), \n\treceiver_version VARCHAR(64), \n\tenvironment_fingerprint VARCHAR(64), \n\tmachine_hostname VARCHAR(255), \n\tworkspace_name VARCHAR(128), \n\tlast_seen_at DATETIME, \n\tgeneration BIGINT NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, target_id), \n\tCONSTRAINT uq_pc_agent_skill_targets_installation UNIQUE (scope_id, agent_kind, installation_scope, installation_id), \n\tCONSTRAINT uq_pc_agent_skill_targets_enrollment_token UNIQUE (enrollment_token_digest), \n\tCONSTRAINT uq_pc_agent_skill_targets_credential_subject UNIQUE (credential_subject), \n\tCONSTRAINT uq_pc_agent_skill_targets_credential_verifier UNIQUE (credential_verifier), \n\tCONSTRAINT ck_pc_agent_skill_targets_agent_kind CHECK (agent_kind IN ('codex', 'claude_code')), \n\tCONSTRAINT ck_pc_agent_skill_targets_installation_scope CHECK (installation_scope IN ('project')), \n\tCONSTRAINT ck_pc_agent_skill_targets_delivery_mode CHECK (delivery_mode = 'agent_pull'), \n\tCONSTRAINT ck_pc_agent_skill_targets_state CHECK (state IN ('pending', 'active', 'revoked')), \n\tCONSTRAINT ck_pc_agent_skill_targets_state_payload CHECK ((state = 'pending' AND enrollment_token_digest IS NOT NULL AND enrollment_expires_at IS NOT NULL AND installation_id IS NULL AND credential_subject IS NULL AND credential_verifier IS NULL) OR (state = 'active' AND enrollment_token_digest IS NULL AND enrollment_expires_at IS NULL AND installation_id IS NOT NULL AND credential_subject IS NOT NULL AND credential_verifier IS NOT NULL) OR (state = 'revoked' AND enrollment_token_digest IS NULL AND enrollment_expires_at IS NULL AND credential_verifier IS NULL)), \n\tCONSTRAINT ck_pc_agent_skill_targets_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_artifact_candidate_heads", + "pc_artifact_candidate_heads", + "CREATE TABLE pc_artifact_candidate_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\tresult_family VARCHAR(128), \n\tresult_artifact_id VARCHAR(128), \n\tresult_revision INTEGER, \n\tdecision_reason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tFOREIGN KEY(scope_id, candidate_id, version) REFERENCES pc_artifact_candidate_versions (scope_id, candidate_id, version) ON DELETE RESTRICT, \n\tFOREIGN KEY(scope_id, result_family, result_artifact_id, result_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_artifact_candidate_heads_terminal_result CHECK ((status = 'approved' AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'rejected' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)" + ], + [ + "table", + "pc_artifact_candidate_versions", + "pc_artifact_candidate_versions", + "CREATE TABLE pc_artifact_candidate_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tproposal BLOB NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\treason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_artifact_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)" + ], + [ + "table", + "pc_artifact_fts", + "pc_artifact_fts", + "CREATE VIRTUAL TABLE pc_artifact_fts USING fts5(\n scope_id UNINDEXED,\n family UNINDEXED,\n artifact_id UNINDEXED,\n revision UNINDEXED,\n searchable_text,\n tokenize='unicode61'\n)" + ], + [ + "table", + "pc_artifact_fts_config", + "pc_artifact_fts_config", + "CREATE TABLE 'pc_artifact_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_artifact_fts_content", + "pc_artifact_fts_content", + "CREATE TABLE 'pc_artifact_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4)" + ], + [ + "table", + "pc_artifact_fts_data", + "pc_artifact_fts_data", + "CREATE TABLE 'pc_artifact_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_artifact_fts_docsize", + "pc_artifact_fts_docsize", + "CREATE TABLE 'pc_artifact_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_artifact_fts_idx", + "pc_artifact_fts_idx", + "CREATE TABLE 'pc_artifact_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_artifact_heads", + "pc_artifact_heads", + "CREATE TABLE pc_artifact_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsearchable_text TEXT, \n\tlifecycle_state VARCHAR(16) DEFAULT 'active' NOT NULL, \n\treplacement_artifact_id VARCHAR(128), \n\tmerged_into_id VARCHAR(128), \n\tgovernance_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_heads_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_artifact_heads_lifecycle_state CHECK (lifecycle_state IN ('active', 'deprecated', 'retired')), \n\tCONSTRAINT ck_pc_artifact_heads_governance_generation_nonnegative CHECK (governance_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_heads_replacement_deprecated CHECK (replacement_artifact_id IS NULL OR lifecycle_state = 'deprecated'), \n\tCONSTRAINT ck_pc_artifact_heads_merge_target CHECK (merged_into_id IS NULL OR (lifecycle_state = 'deprecated' AND merged_into_id <> artifact_id AND replacement_artifact_id IS NULL))\n)" + ], + [ + "table", + "pc_artifact_lineage_artifacts", + "pc_artifact_lineage_artifacts", + "CREATE TABLE pc_artifact_lineage_artifacts (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tupstream_family VARCHAR(128) NOT NULL, \n\tupstream_artifact_id VARCHAR(128) NOT NULL, \n\tupstream_revision INTEGER NOT NULL, \n\tis_merge_input BOOLEAN DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision, ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, upstream_family, upstream_artifact_id, upstream_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_artifact_lineage_sources", + "pc_artifact_lineage_sources", + "CREATE TABLE pc_artifact_lineage_sources (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tsource_type VARCHAR(128) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision, ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, source_type, source_id) REFERENCES pc_sources (scope_id, source_type, source_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_artifact_processing_auto_wave_targets", + "pc_artifact_processing_auto_wave_targets", + "CREATE TABLE pc_artifact_processing_auto_wave_targets (\n\twave_id VARCHAR(36) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tcompleted BOOLEAN DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (wave_id, binding_name, scope_id), \n\tCONSTRAINT ck_pc_artifact_processing_auto_wave_target_source_positive CHECK (source_through >= 1)\n)" + ], + [ + "table", + "pc_artifact_processing_binding_states", + "pc_artifact_processing_binding_states", + "CREATE TABLE pc_artifact_processing_binding_states (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tlast_auto_wave_completed_at DATETIME, \n\tlast_schedule_checkpoint_at DATETIME, \n\tscan_generation BIGINT DEFAULT '0' NOT NULL, \n\tscan_in_progress BOOLEAN DEFAULT '0' NOT NULL, \n\tscan_upper_pending_sequence BIGINT, \n\tPRIMARY KEY (binding_name), \n\tCONSTRAINT ck_pc_processing_scan_generation CHECK (scan_generation >= 0)\n)" + ], + [ + "table", + "pc_artifact_processing_intents", + "pc_artifact_processing_intents", + "CREATE TABLE pc_artifact_processing_intents (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tpending_sequence BIGINT NOT NULL, \n\tdirty_generation BIGINT DEFAULT '0' NOT NULL, \n\tclean_generation BIGINT DEFAULT '0' NOT NULL, \n\trequested_generation BIGINT DEFAULT '0' NOT NULL, \n\thandled_generation BIGINT DEFAULT '0' NOT NULL, \n\tlast_auto_scan_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_processing_intent_sequence CHECK (pending_sequence > 0), \n\tCONSTRAINT ck_pc_processing_intent_dirty CHECK (clean_generation >= 0 AND clean_generation <= dirty_generation), \n\tCONSTRAINT ck_pc_processing_intent_request CHECK (handled_generation >= 0 AND handled_generation <= requested_generation), \n\tCONSTRAINT ck_pc_processing_intent_scan CHECK (last_auto_scan_generation >= 0), \n\tUNIQUE (pending_sequence)\n)" + ], + [ + "table", + "pc_artifact_processing_leases", + "pc_artifact_processing_leases", + "CREATE TABLE pc_artifact_processing_leases (\n\tsupervisor_group VARCHAR(64) NOT NULL, \n\tholder_id VARCHAR(36) NOT NULL, \n\tsupervisor_generation BIGINT NOT NULL, \n\tlease_expires_at DATETIME, \n\tPRIMARY KEY (supervisor_group), \n\tCONSTRAINT ck_pc_artifact_processing_leases_generation_positive CHECK (supervisor_generation > 0)\n)" + ], + [ + "table", + "pc_artifact_processing_migration_receipts", + "pc_artifact_processing_migration_receipts", + "CREATE TABLE pc_artifact_processing_migration_receipts (\n\tmigration_id VARCHAR(64) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_snapshot BLOB NOT NULL, \n\trequested_generation BIGINT NOT NULL, \n\tdirty_generation BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tPRIMARY KEY (migration_id, binding_name, scope_id)\n)" + ], + [ + "table", + "pc_artifact_processing_pending", + "pc_artifact_processing_pending", + "CREATE TABLE pc_artifact_processing_pending (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tflush_generation BIGINT DEFAULT '0' NOT NULL, \n\thandled_flush_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_artifact_processing_pending_source_positive CHECK (source_through >= 1), \n\tCONSTRAINT ck_pc_artifact_processing_pending_flush_nonnegative CHECK (flush_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_processing_pending_handled_nonnegative CHECK (handled_flush_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_processing_pending_handled_not_ahead CHECK (handled_flush_generation <= flush_generation)\n)" + ], + [ + "table", + "pc_artifact_processing_schema", + "pc_artifact_processing_schema", + "CREATE TABLE pc_artifact_processing_schema (\n\tsingleton INTEGER NOT NULL, \n\tschema_version INTEGER NOT NULL, \n\tsource_version INTEGER DEFAULT '1490' NOT NULL, \n\tphase VARCHAR(32) NOT NULL, \n\tmigration_id VARCHAR(64) NOT NULL, \n\tconfig_manifest TEXT NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_processing_schema_singleton CHECK (singleton = 1)\n)" + ], + [ + "table", + "pc_artifact_processing_sequences", + "pc_artifact_processing_sequences", + "CREATE TABLE pc_artifact_processing_sequences (\n\tsingleton INTEGER NOT NULL, \n\tsequence BIGINT NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_processing_sequence CHECK (singleton = 1 AND sequence >= 0)\n)" + ], + [ + "table", + "pc_artifact_publications", + "pc_artifact_publications", + "CREATE TABLE pc_artifact_publications (\n\ttarget_scope_id VARCHAR(256) NOT NULL, \n\ttarget_family VARCHAR(128) NOT NULL, \n\ttarget_artifact_id VARCHAR(128) NOT NULL, \n\ttarget_revision INTEGER NOT NULL, \n\tsource_scope_id VARCHAR(256) NOT NULL, \n\tsource_family VARCHAR(128) NOT NULL, \n\tsource_artifact_id VARCHAR(128) NOT NULL, \n\tsource_revision INTEGER NOT NULL, \n\tcontent_digest VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (target_scope_id, target_family, target_artifact_id, target_revision), \n\tFOREIGN KEY(target_scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tFOREIGN KEY(source_scope_id, source_family, source_artifact_id, source_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT uq_pc_artifact_publications_request UNIQUE (target_scope_id, idempotency_key)\n)" + ], + [ + "table", + "pc_artifact_tags", + "pc_artifact_tags", + "CREATE TABLE pc_artifact_tags (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\ttarget_type VARCHAR(12) NOT NULL, \n\ttarget_id VARCHAR(128) NOT NULL, \n\ttag_key_hash BLOB NOT NULL, \n\ttag_key VARCHAR(128) NOT NULL, \n\ttag VARCHAR(64) NOT NULL, \n\tassigned_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, target_type, target_id, tag_key_hash), \n\tFOREIGN KEY(scope_id, family, artifact_id) REFERENCES pc_artifact_heads (scope_id, family, artifact_id) ON DELETE CASCADE, \n\tCONSTRAINT ck_pc_artifact_tags_target CHECK ((target_type = 'artifact' AND target_id = artifact_id) OR (target_type = 'memory_entry' AND family = 'memory'))\n)" + ], + [ + "table", + "pc_artifacts", + "pc_artifacts", + "CREATE TABLE pc_artifacts (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tcontent BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision)\n)" + ], + [ + "table", + "pc_atomic_memory_current", + "pc_atomic_memory_current", + "CREATE TABLE pc_atomic_memory_current (\n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision BIGINT NOT NULL, \n\tstate_version BIGINT NOT NULL, \n\tcontent_hash VARCHAR(64) NOT NULL, \n\tprojection_format VARCHAR(64) NOT NULL, \n\tkind TEXT NOT NULL, \n\ttext TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\ttag_keys TEXT NOT NULL, \n\tembedding BLOB, \n\tprofile_fingerprint VARCHAR(64), \n\tembedding_input_hash VARCHAR(64), \n\tPRIMARY KEY (scope_id, artifact_id), \n\tCONSTRAINT ck_pc_atomic_memory_current_revision CHECK (revision > 0), \n\tCONSTRAINT ck_pc_atomic_memory_current_state_version CHECK (state_version >= 0), \n\tCONSTRAINT ck_pc_atomic_memory_current_embedding_metadata CHECK ((embedding IS NULL AND profile_fingerprint IS NULL AND embedding_input_hash IS NULL) OR (embedding IS NOT NULL AND profile_fingerprint IS NOT NULL AND embedding_input_hash IS NOT NULL))\n)" + ], + [ + "table", + "pc_atomic_memory_current_fts", + "pc_atomic_memory_current_fts", + "CREATE VIRTUAL TABLE pc_atomic_memory_current_fts USING fts5(scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED, state_version UNINDEXED, content_hash UNINDEXED, projection_format UNINDEXED, kind UNINDEXED, text UNINDEXED, searchable_text, tag_keys UNINDEXED, identity_token, tokenize='unicode61')" + ], + [ + "table", + "pc_atomic_memory_current_fts_config", + "pc_atomic_memory_current_fts_config", + "CREATE TABLE 'pc_atomic_memory_current_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_atomic_memory_current_fts_content", + "pc_atomic_memory_current_fts_content", + "CREATE TABLE 'pc_atomic_memory_current_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)" + ], + [ + "table", + "pc_atomic_memory_current_fts_data", + "pc_atomic_memory_current_fts_data", + "CREATE TABLE 'pc_atomic_memory_current_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_atomic_memory_current_fts_docsize", + "pc_atomic_memory_current_fts_docsize", + "CREATE TABLE 'pc_atomic_memory_current_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_atomic_memory_current_fts_idx", + "pc_atomic_memory_current_fts_idx", + "CREATE TABLE 'pc_atomic_memory_current_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_connector_checkpoints", + "pc_connector_checkpoints", + "CREATE TABLE pc_connector_checkpoints (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_id VARCHAR(256) NOT NULL, \n\tconnector_name VARCHAR(128) NOT NULL, \n\tconnector_version VARCHAR(128) NOT NULL, \n\tcheckpoint BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, binding_id)\n)" + ], + [ + "table", + "pc_decision_observations", + "pc_decision_observations", + "CREATE TABLE pc_decision_observations (\n\tscope_id VARCHAR(256) NOT NULL, \n\tobservation_id VARCHAR(128) NOT NULL, \n\toperation_id VARCHAR(256) NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tconsumer VARCHAR(128) NOT NULL, \n\tpolicy_id VARCHAR(256) NOT NULL, \n\tpolicy_version VARCHAR(64) NOT NULL, \n\tmode VARCHAR(16) NOT NULL, \n\tprivacy_boundary VARCHAR(32) NOT NULL, \n\tcoverage VARCHAR(16) NOT NULL, \n\tverdict VARCHAR(16) NOT NULL, \n\tassessment_source VARCHAR(32) NOT NULL, \n\tfinal_action VARCHAR(128) NOT NULL, \n\tused_fallback BOOLEAN NOT NULL, \n\tfallback_reason VARCHAR(128), \n\tprovider_id VARCHAR(128), \n\tbackend_model_id VARCHAR(256), \n\tmodel_policy_id VARCHAR(256), \n\trequests BIGINT NOT NULL, \n\tinput_tokens BIGINT, \n\toutput_tokens BIGINT, \n\tlatency_ms FLOAT, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, observation_id), \n\tCONSTRAINT ck_pc_decision_observations_requests_nonnegative CHECK (requests >= 0), \n\tCONSTRAINT ck_pc_decision_observations_input_tokens_nonnegative CHECK (input_tokens IS NULL OR input_tokens >= 0), \n\tCONSTRAINT ck_pc_decision_observations_output_tokens_nonnegative CHECK (output_tokens IS NULL OR output_tokens >= 0), \n\tCONSTRAINT ck_pc_decision_observations_latency_nonnegative CHECK (latency_ms IS NULL OR latency_ms >= 0)\n)" + ], + [ + "table", + "pc_dream_runs", + "pc_dream_runs", + "CREATE TABLE pc_dream_runs (\n\tscope_id VARCHAR(256) NOT NULL, \n\trun_id VARCHAR(64) NOT NULL, \n\tprincipal_key VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(128) NOT NULL, \n\trequest_digest VARCHAR(71) NOT NULL, \n\toperation VARCHAR(32) NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\taccepted_at BIGINT NOT NULL, \n\tgeneration INTEGER NOT NULL, \n\trequest_generation BIGINT NOT NULL, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, run_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_dream_idempotency UNIQUE (scope_id, principal_key, idempotency_key), \n\tCONSTRAINT ck_pc_dream_generation CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_external_skill_registrations", + "pc_external_skill_registrations", + "CREATE TABLE pc_external_skill_registrations (\n\tscope_id VARCHAR(256) NOT NULL, \n\texternal_skill_id VARCHAR(128) NOT NULL, \n\tprovider VARCHAR(128) NOT NULL, \n\tagent_kind VARCHAR(128) NOT NULL, \n\thost_id VARCHAR(128) NOT NULL, \n\tinstallation_scope VARCHAR(16) NOT NULL, \n\tlocator VARCHAR(2000) NOT NULL, \n\tlocator_hash VARCHAR(64) NOT NULL, \n\tfingerprint VARCHAR(64) NOT NULL, \n\tname VARCHAR(128) NOT NULL, \n\tdescription VARCHAR(2000) NOT NULL, \n\tPRIMARY KEY (scope_id, external_skill_id), \n\tCONSTRAINT uq_pc_external_skill_registrations_binding UNIQUE (scope_id, provider, host_id, installation_scope, locator_hash), \n\tCONSTRAINT ck_pc_external_skill_registrations_scope CHECK (installation_scope IN ('user', 'project', 'plugin'))\n)" + ], + [ + "table", + "pc_memory_entry_heads", + "pc_memory_entry_heads", + "CREATE TABLE pc_memory_entry_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tmemory_artifact_id VARCHAR(128) NOT NULL, \n\thead_revision INTEGER NOT NULL, \n\tentry_id VARCHAR(128) NOT NULL, \n\tentry_version_id VARCHAR(128) NOT NULL, \n\tentry_content_hash VARCHAR(64) NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tPRIMARY KEY (scope_id, memory_artifact_id, entry_id), \n\tFOREIGN KEY(scope_id, memory_artifact_id, entry_id, entry_version_id) REFERENCES pc_memory_entry_versions (scope_id, memory_artifact_id, entry_id, entry_version_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_memory_entry_heads_revision_positive CHECK (head_revision > 0)\n)" + ], + [ + "table", + "pc_memory_entry_versions", + "pc_memory_entry_versions", + "CREATE TABLE pc_memory_entry_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tmemory_artifact_id VARCHAR(128) NOT NULL, \n\tentry_id VARCHAR(128) NOT NULL, \n\tentry_version_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tprevious_version_id VARCHAR(128), \n\tkind VARCHAR(128) NOT NULL, \n\ttext TEXT NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\tentry_content_hash VARCHAR(64) NOT NULL, \n\tcreated_in_revision INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, memory_artifact_id, entry_version_id), \n\tCONSTRAINT uq_pc_memory_entry_versions_logical_version UNIQUE (scope_id, memory_artifact_id, entry_id, version), \n\tCONSTRAINT uq_pc_memory_entry_versions_identity UNIQUE (scope_id, memory_artifact_id, entry_id, entry_version_id), \n\tCONSTRAINT ck_pc_memory_entry_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_memory_entry_versions_revision_positive CHECK (created_in_revision > 0)\n)" + ], + [ + "table", + "pc_memory_source_windows", + "pc_memory_source_windows", + "CREATE TABLE pc_memory_source_windows (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\twindow_limit BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tCONSTRAINT ck_pc_memory_source_window_positive CHECK (source_through > 0 AND window_limit > 0)\n)" + ], + [ + "table", + "pc_model_usage_daily", + "pc_model_usage_daily", + "CREATE TABLE pc_model_usage_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\tpurpose VARCHAR(64) NOT NULL, \n\toperation VARCHAR(16) NOT NULL, \n\trequests BIGINT NOT NULL, \n\tinput_tokens BIGINT NOT NULL, \n\toutput_tokens BIGINT NOT NULL, \n\tinput_complete BOOLEAN NOT NULL, \n\toutput_complete BOOLEAN NOT NULL, \n\tPRIMARY KEY (scope_id, usage_date, purpose, operation), \n\tCONSTRAINT ck_pc_model_usage_daily_operation CHECK (operation IN ('generation', 'embedding')), \n\tCONSTRAINT ck_pc_model_usage_daily_requests_nonnegative CHECK (requests >= 0), \n\tCONSTRAINT ck_pc_model_usage_daily_input_nonnegative CHECK (input_tokens >= 0), \n\tCONSTRAINT ck_pc_model_usage_daily_output_nonnegative CHECK (output_tokens >= 0)\n)" + ], + [ + "table", + "pc_profile_policies", + "pc_profile_policies", + "CREATE TABLE pc_profile_policies (\n\tscope_id VARCHAR(256) NOT NULL, \n\tgeneration_enabled BOOLEAN NOT NULL, \n\tactivation_mode VARCHAR(32) NOT NULL, \n\tpending_candidate_id VARCHAR(128), \n\tversion BIGINT NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, pending_candidate_id) REFERENCES pc_artifact_candidate_heads (scope_id, candidate_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_profile_policies_activation_mode CHECK (activation_mode IN ('automatic', 'review_required')), \n\tCONSTRAINT ck_pc_profile_policies_version_positive CHECK (version > 0)\n)" + ], + [ + "table", + "pc_recall_effort_daily", + "pc_recall_effort_daily", + "CREATE TABLE pc_recall_effort_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\tpolicy_id VARCHAR(128) NOT NULL, \n\tassessment VARCHAR(64) NOT NULL, \n\tpreparations BIGINT NOT NULL CONSTRAINT ck_recall_effort_preparations_nonnegative CHECK (preparations >= 0), \n\trounds BIGINT NOT NULL CONSTRAINT ck_recall_effort_rounds_nonnegative CHECK (rounds >= 0), \n\texpanded_preparations BIGINT NOT NULL CONSTRAINT ck_recall_effort_expanded_preparations_nonnegative CHECK (expanded_preparations >= 0), \n\tadmission_expansions BIGINT NOT NULL CONSTRAINT ck_recall_effort_admission_expansions_nonnegative CHECK (admission_expansions >= 0), \n\tpolicy_floor_expansions BIGINT NOT NULL CONSTRAINT ck_recall_effort_policy_floor_expansions_nonnegative CHECK (policy_floor_expansions >= 0), \n\tcandidate_round_samples BIGINT NOT NULL CONSTRAINT ck_recall_effort_candidate_round_samples_nonnegative CHECK (candidate_round_samples >= 0), \n\tcandidates_assessed BIGINT NOT NULL CONSTRAINT ck_recall_effort_candidates_assessed_nonnegative CHECK (candidates_assessed >= 0), \n\tfinal_candidate_pool BIGINT NOT NULL CONSTRAINT ck_recall_effort_final_candidate_pool_nonnegative CHECK (final_candidate_pool >= 0), \n\tadded_embeddings BIGINT NOT NULL CONSTRAINT ck_recall_effort_added_embeddings_nonnegative CHECK (added_embeddings >= 0), \n\tadded_generation_calls BIGINT NOT NULL CONSTRAINT ck_recall_effort_added_generation_calls_nonnegative CHECK (added_generation_calls >= 0), \n\ttruncated_items BIGINT NOT NULL CONSTRAINT ck_recall_effort_truncated_items_nonnegative CHECK (truncated_items >= 0), \n\tdropped_items BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_items_nonnegative CHECK (dropped_items >= 0), \n\tdropped_below_min_bytes BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_below_min_bytes_nonnegative CHECK (dropped_below_min_bytes >= 0), \n\tdropped_no_fitting_truncation BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_no_fitting_truncation_nonnegative CHECK (dropped_no_fitting_truncation >= 0), \n\tPRIMARY KEY (scope_id, usage_date, policy_id, assessment), \n\tCONSTRAINT ck_pc_recall_effort_daily_drop_total CHECK (dropped_items = dropped_below_min_bytes + dropped_no_fitting_truncation)\n)" + ], + [ + "table", + "pc_recall_token_daily", + "pc_recall_token_daily", + "CREATE TABLE pc_recall_token_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\testimator_id VARCHAR(128) NOT NULL, \n\testimator_version VARCHAR(64) NOT NULL, \n\tpreparations BIGINT NOT NULL, \n\tready_preparations BIGINT NOT NULL, \n\tcomparable_preparations BIGINT NOT NULL, \n\tbaseline_tokens BIGINT NOT NULL, \n\trecalled_tokens BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, usage_date, estimator_id, estimator_version), \n\tCONSTRAINT ck_pc_recall_token_daily_preparations_nonnegative CHECK (preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_ready_nonnegative CHECK (ready_preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_comparable_nonnegative CHECK (comparable_preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_comparable_ready CHECK (comparable_preparations <= ready_preparations), \n\tCONSTRAINT ck_pc_recall_token_daily_ready_total CHECK (ready_preparations <= preparations), \n\tCONSTRAINT ck_pc_recall_token_daily_baseline_nonnegative CHECK (baseline_tokens >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_recalled_nonnegative CHECK (recalled_tokens >= 0)\n)" + ], + [ + "table", + "pc_receipt_migration_review", + "pc_receipt_migration_review", + "CREATE TABLE pc_receipt_migration_review (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\treason VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, source_id)\n)" + ], + [ + "table", + "pc_recurrence_match", + "pc_recurrence_match", + "CREATE TABLE pc_recurrence_match (\n\tscope_id VARCHAR(256) NOT NULL, \n\tmatch_key VARCHAR(71) NOT NULL, \n\ttask_outcome_source_type VARCHAR(128) NOT NULL, \n\ttask_outcome_source_id VARCHAR(256) NOT NULL, \n\ttask_outcome_position BIGINT NOT NULL, \n\tfailure_item_kind VARCHAR(16) NOT NULL, \n\tfailure_item_index INTEGER NOT NULL, \n\tfailure_item_digest VARCHAR(71) NOT NULL, \n\tcandidate_set_mode VARCHAR(32) NOT NULL, \n\tcandidate_set_digest VARCHAR(71) NOT NULL, \n\tresult VARCHAR(16) NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\tsignature_key TEXT, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, match_key), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_recurrence_match_identity UNIQUE (scope_id, task_outcome_source_type, task_outcome_source_id, failure_item_kind, failure_item_index), \n\tCONSTRAINT ck_pc_recurrence_match_result CHECK (result IN ('matched', 'unmatched', 'ambiguous')), \n\tCONSTRAINT ck_pc_recurrence_match_candidate_set_mode CHECK (candidate_set_mode IN ('handoff_citations', 'scope_heads')), \n\tCONSTRAINT ck_pc_recurrence_match_failure_item_kind CHECK (failure_item_kind IN ('observation', 'check')), \n\tCONSTRAINT ck_pc_recurrence_match_position_positive CHECK (task_outcome_position > 0), \n\tCONSTRAINT ck_pc_recurrence_match_item_index_nonnegative CHECK (failure_item_index >= 0)\n)" + ], + [ + "table", + "pc_recurrence_observation", + "pc_recurrence_observation", + "CREATE TABLE pc_recurrence_observation (\n\tscope_id VARCHAR(256) NOT NULL, \n\tobservation_id VARCHAR(71) NOT NULL, \n\tselection_key VARCHAR(71) NOT NULL, \n\tverdict_key VARCHAR(71) NOT NULL, \n\tevent VARCHAR(16) NOT NULL, \n\tmatch_basis VARCHAR(8) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsignature_key TEXT NOT NULL, \n\tsignature_key_hash BLOB NOT NULL, \n\ttask_outcome_source_type VARCHAR(128) NOT NULL, \n\ttask_outcome_source_id VARCHAR(256) NOT NULL, \n\ttask_outcome_position BIGINT NOT NULL, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, observation_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_recurrence_observation_selection UNIQUE (scope_id, selection_key), \n\tCONSTRAINT uq_pc_recurrence_observation_verdict UNIQUE (scope_id, verdict_key), \n\tCONSTRAINT ck_pc_recurrence_observation_event CHECK (event IN ('selected', 'recurred', 'avoided')), \n\tCONSTRAINT ck_pc_recurrence_observation_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_recurrence_observation_position_positive CHECK (task_outcome_position > 0)\n)" + ], + [ + "table", + "pc_scope_bindings", + "pc_scope_bindings", + "CREATE TABLE pc_scope_bindings (\n\tintegration VARCHAR(128) NOT NULL, \n\tkind VARCHAR(64) NOT NULL, \n\texternal_id VARCHAR(256) NOT NULL, \n\texternal_id_search TEXT NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (integration, kind, external_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scope_context_references", + "pc_scope_context_references", + "CREATE TABLE pc_scope_context_references (\n\tscope_id VARCHAR(256) NOT NULL, \n\treferenced_scope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (scope_id, referenced_scope_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(referenced_scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_scope_context_references_not_self CHECK (scope_id <> referenced_scope_id)\n)" + ], + [ + "table", + "pc_scope_creation_requests", + "pc_scope_creation_requests", + "CREATE TABLE pc_scope_creation_requests (\n\tidempotency_key VARCHAR(256) NOT NULL, \n\trequest_digest VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (idempotency_key), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scope_external_references", + "pc_scope_external_references", + "CREATE TABLE pc_scope_external_references (\n\tscope_id VARCHAR(256) NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tkind VARCHAR(128) NOT NULL, \n\tvalue VARCHAR(2000) NOT NULL, \n\tvalue_search TEXT NOT NULL, \n\tvalue_digest VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, ordinal), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_scope_external_references_value UNIQUE (scope_id, kind, value_digest), \n\tCONSTRAINT ck_pc_scope_external_references_ordinal_nonnegative CHECK (ordinal >= 0)\n)" + ], + [ + "table", + "pc_scope_settings", + "pc_scope_settings", + "CREATE TABLE pc_scope_settings (\n\tname VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (name), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scopes", + "pc_scopes", + "CREATE TABLE pc_scopes (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttitle VARCHAR(256) NOT NULL, \n\tsummary VARCHAR(2000) NOT NULL, \n\tscope_id_search TEXT NOT NULL, \n\ttitle_search TEXT NOT NULL, \n\tsummary_search TEXT NOT NULL, \n\tparent_scope_id VARCHAR(256), \n\tversion INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tFOREIGN KEY(parent_scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_scopes_version_positive CHECK (version > 0)\n)" + ], + [ + "table", + "pc_server_identity", + "pc_server_identity", + "CREATE TABLE pc_server_identity (\n\tsingleton_key INTEGER NOT NULL, \n\tserver_id VARCHAR(36) NOT NULL, \n\tPRIMARY KEY (singleton_key), \n\tCONSTRAINT ck_pc_server_identity_singleton CHECK (singleton_key = 1), \n\tUNIQUE (server_id)\n)" + ], + [ + "table", + "pc_skill_packages", + "pc_skill_packages", + "CREATE TABLE pc_skill_packages (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttree_digest VARCHAR(64) NOT NULL, \n\tarchive_digest VARCHAR(64) NOT NULL, \n\tarchive_bytes BLOB NOT NULL, \n\tmanifest BLOB NOT NULL, \n\tfile_count INTEGER NOT NULL, \n\tuncompressed_size BIGINT NOT NULL, \n\tarchive_size BIGINT NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, tree_digest), \n\tCONSTRAINT ck_pc_skill_packages_file_count CHECK (file_count > 0 AND file_count <= 256), \n\tCONSTRAINT ck_pc_skill_packages_uncompressed_size CHECK (uncompressed_size > 0 AND uncompressed_size <= 4194304), \n\tCONSTRAINT ck_pc_skill_packages_archive_size CHECK (archive_size > 0 AND archive_size <= 5242880)\n)" + ], + [ + "table", + "pc_skill_publications", + "pc_skill_publications", + "CREATE TABLE pc_skill_publications (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_id VARCHAR(64) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\tdesired_state VARCHAR(16) DEFAULT 'published' NOT NULL, \n\tdesired_revision INTEGER NOT NULL, \n\tdesired_tree_digest VARCHAR(64) NOT NULL, \n\tobserved_revision INTEGER, \n\tobserved_tree_digest VARCHAR(64), \n\tobserved_generation BIGINT, \n\tdestination TEXT, \n\tstate VARCHAR(32) NOT NULL, \n\tselected_runtime_variant VARCHAR(128), \n\tenvironment_fingerprint VARCHAR(64), \n\tlast_error_code VARCHAR(128), \n\tobserved_at DATETIME, \n\tgeneration BIGINT NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, target_id, artifact_id), \n\tCONSTRAINT ck_pc_skill_publications_desired_revision_positive CHECK (desired_revision > 0), \n\tCONSTRAINT ck_pc_skill_publications_observed_revision_positive CHECK (observed_revision IS NULL OR observed_revision > 0), \n\tCONSTRAINT ck_pc_skill_publications_desired_state CHECK (desired_state IN ('published', 'unpublished')), \n\tCONSTRAINT ck_pc_skill_publications_state CHECK (state IN ('unpublished', 'pending', 'current', 'update_available', 'delivery_failed', 'conflict', 'drifted', 'incompatible')), \n\tCONSTRAINT ck_pc_skill_publications_observed_generation_nonnegative CHECK (observed_generation IS NULL OR observed_generation >= 0), \n\tCONSTRAINT ck_pc_skill_publications_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_source_cursors", + "pc_source_cursors", + "CREATE TABLE pc_source_cursors (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tcursor BLOB NOT NULL, \n\tgeneration BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, binding_name), \n\tCONSTRAINT ck_pc_source_cursors_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_source_definition_manifests", + "pc_source_definition_manifests", + "CREATE TABLE pc_source_definition_manifests (\n\tdefinition_name VARCHAR(128) NOT NULL, \n\tdefinition_version VARCHAR(128) NOT NULL, \n\tfingerprint VARCHAR(71) NOT NULL, \n\tmanifest BLOB NOT NULL, \n\tPRIMARY KEY (definition_name, definition_version), \n\tCONSTRAINT uq_pc_source_definition_manifest_fingerprint UNIQUE (definition_name, fingerprint)\n)" + ], + [ + "table", + "pc_source_journal_heads", + "pc_source_journal_heads", + "CREATE TABLE pc_source_journal_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tposition BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tCONSTRAINT ck_pc_source_journal_heads_position_nonnegative CHECK (position >= 0)\n)" + ], + [ + "table", + "pc_sources", + "pc_sources", + "CREATE TABLE pc_sources (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_type VARCHAR(128) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\tpayload BLOB NOT NULL, \n\tjournal_position BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, source_type, source_id), \n\tCONSTRAINT uq_pc_sources_scope_journal_position UNIQUE (scope_id, journal_position)\n)" + ], + [ + "table", + "pc_topic_memory_active_chunks", + "pc_topic_memory_active_chunks", + "CREATE TABLE pc_topic_memory_active_chunks (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tchunk_ordinal INTEGER NOT NULL, \n\tstart_offset INTEGER NOT NULL, \n\tend_offset INTEGER NOT NULL, \n\tchunk_text TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tpolicy_version VARCHAR(32) NOT NULL, \n\tPRIMARY KEY (scope_id, artifact_id, chunk_ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_topic_memory_revision_publications (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_ordinal_nonnegative CHECK (chunk_ordinal >= 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_start_nonnegative CHECK (start_offset >= 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_offsets CHECK (end_offset > start_offset)\n)" + ], + [ + "table", + "pc_topic_memory_active_topics", + "pc_topic_memory_active_topics", + "CREATE TABLE pc_topic_memory_active_topics (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\ttitle TEXT NOT NULL, \n\tsummary TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tsource_count INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_topic_memory_revision_publications (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_active_topics_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_active_topics_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_topic_memory_active_topics_sources_nonnegative CHECK (source_count >= 0)\n)" + ], + [ + "table", + "pc_topic_memory_chunk_fts", + "pc_topic_memory_chunk_fts", + "CREATE VIRTUAL TABLE pc_topic_memory_chunk_fts USING fts5(\n scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED,\n chunk_ordinal UNINDEXED, start_offset UNINDEXED,\n title UNINDEXED, summary UNINDEXED, chunk_text UNINDEXED,\n searchable_text, tokenize='unicode61'\n)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_config", + "pc_topic_memory_chunk_fts_config", + "CREATE TABLE 'pc_topic_memory_chunk_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_chunk_fts_content", + "pc_topic_memory_chunk_fts_content", + "CREATE TABLE 'pc_topic_memory_chunk_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5, c6, c7, c8)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_data", + "pc_topic_memory_chunk_fts_data", + "CREATE TABLE 'pc_topic_memory_chunk_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_docsize", + "pc_topic_memory_chunk_fts_docsize", + "CREATE TABLE 'pc_topic_memory_chunk_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_idx", + "pc_topic_memory_chunk_fts_idx", + "CREATE TABLE 'pc_topic_memory_chunk_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_fts_index", + "pc_topic_memory_fts_index", + "CREATE TABLE pc_topic_memory_fts_index (\n\tsingleton INTEGER NOT NULL, \n\tschema_version INTEGER NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_topic_memory_fts_index_singleton CHECK (singleton = 1)\n)" + ], + [ + "table", + "pc_topic_memory_processing_targets", + "pc_topic_memory_processing_targets", + "CREATE TABLE pc_topic_memory_processing_targets (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_request_generation BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tcaptured_flush_generation BIGINT NOT NULL, \n\tobserved_dirty_generation BIGINT NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_topic_processing_target CHECK (target_request_generation > 0 AND source_through >= 0 AND captured_flush_generation >= 0 AND observed_dirty_generation >= 0)\n)" + ], + [ + "table", + "pc_topic_memory_retrieval_shape", + "pc_topic_memory_retrieval_shape", + "CREATE TABLE pc_topic_memory_retrieval_shape (\n\tsingleton INTEGER NOT NULL, \n\tshape VARCHAR(16) NOT NULL, \n\tprofile_fingerprint VARCHAR(64), \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_singleton CHECK (singleton = 1), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_value CHECK (shape IN ('fts', 'hybrid')), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_profile CHECK ((shape = 'fts' AND profile_fingerprint IS NULL) OR (shape = 'hybrid' AND profile_fingerprint IS NOT NULL))\n)" + ], + [ + "table", + "pc_topic_memory_revision_publications", + "pc_topic_memory_revision_publications", + "CREATE TABLE pc_topic_memory_revision_publications (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tpublished_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_publications_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_publications_revision_positive CHECK (revision > 0)\n)" + ], + [ + "table", + "pc_topic_memory_topic_fts", + "pc_topic_memory_topic_fts", + "CREATE VIRTUAL TABLE pc_topic_memory_topic_fts USING fts5(\n scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED,\n title UNINDEXED, summary UNINDEXED, searchable_text, tokenize='unicode61'\n)" + ], + [ + "table", + "pc_topic_memory_topic_fts_config", + "pc_topic_memory_topic_fts_config", + "CREATE TABLE 'pc_topic_memory_topic_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_topic_fts_content", + "pc_topic_memory_topic_fts_content", + "CREATE TABLE 'pc_topic_memory_topic_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5)" + ], + [ + "table", + "pc_topic_memory_topic_fts_data", + "pc_topic_memory_topic_fts_data", + "CREATE TABLE 'pc_topic_memory_topic_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_topic_memory_topic_fts_docsize", + "pc_topic_memory_topic_fts_docsize", + "CREATE TABLE 'pc_topic_memory_topic_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_topic_memory_topic_fts_idx", + "pc_topic_memory_topic_fts_idx", + "CREATE TABLE 'pc_topic_memory_topic_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_work_budgets", + "pc_topic_memory_work_budgets", + "CREATE TABLE pc_topic_memory_work_budgets (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tsource_after BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tattempt_id VARCHAR(36) NOT NULL, \n\tattempts INTEGER NOT NULL, \n\trequests BIGINT NOT NULL, \n\ttokens BIGINT NOT NULL, \n\tfailure_code VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, binding_name, source_after), \n\tCONSTRAINT ck_pc_topic_budget_window CHECK (source_after >= 0 AND source_through > source_after), \n\tCONSTRAINT ck_pc_topic_budget_usage CHECK (attempts > 0 AND requests >= 0 AND tokens >= 0)\n)" + ], + [ + "trigger", + "pc_atomic_memory_fts_delete", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_delete AFTER DELETE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_fts WHERE identity_token MATCH ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); END" + ], + [ + "trigger", + "pc_atomic_memory_fts_insert", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_insert AFTER INSERT ON pc_atomic_memory_current BEGIN INSERT INTO pc_atomic_memory_current_fts (scope_id, artifact_id, revision, state_version, content_hash, projection_format, kind, text, searchable_text, tag_keys, identity_token) VALUES (new.scope_id, new.artifact_id, new.revision, new.state_version, new.content_hash, new.projection_format, new.kind, new.text, new.searchable_text, new.tag_keys, 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id))); END" + ], + [ + "trigger", + "pc_atomic_memory_fts_update", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_update AFTER UPDATE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_fts WHERE identity_token MATCH ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); INSERT INTO pc_atomic_memory_current_fts (scope_id, artifact_id, revision, state_version, content_hash, projection_format, kind, text, searchable_text, tag_keys, identity_token) VALUES (new.scope_id, new.artifact_id, new.revision, new.state_version, new.content_hash, new.projection_format, new.kind, new.text, new.searchable_text, new.tag_keys, 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id))); END" + ] + ], + "target": [ + [ + "index", + "ix_pc_artifact_tags_family_key", + "pc_artifact_tags", + "CREATE INDEX ix_pc_artifact_tags_family_key ON pc_artifact_tags (scope_id, family, tag_key_hash, target_type, artifact_id, target_id)" + ], + [ + "index", + "ix_pc_artifact_tags_key", + "pc_artifact_tags", + "CREATE INDEX ix_pc_artifact_tags_key ON pc_artifact_tags (scope_id, tag_key_hash, family, target_type, artifact_id, target_id)" + ], + [ + "index", + "ix_pc_context_bootstrap_receipts_created_at", + "pc_context_bootstrap_receipts", + "CREATE INDEX ix_pc_context_bootstrap_receipts_created_at ON pc_context_bootstrap_receipts (created_at)" + ], + [ + "index", + "ix_pc_decision_observations_operation", + "pc_decision_observations", + "CREATE INDEX ix_pc_decision_observations_operation ON pc_decision_observations (scope_id, operation_id, created_at)" + ], + [ + "index", + "ix_pc_decision_observations_scope_created", + "pc_decision_observations", + "CREATE INDEX ix_pc_decision_observations_scope_created ON pc_decision_observations (scope_id, created_at, observation_id)" + ], + [ + "index", + "ix_pc_dream_dispatch", + "pc_dream_runs", + "CREATE INDEX ix_pc_dream_dispatch ON pc_dream_runs (scope_id, operation, status, request_generation)" + ], + [ + "index", + "ix_pc_dream_list", + "pc_dream_runs", + "CREATE INDEX ix_pc_dream_list ON pc_dream_runs (scope_id, accepted_at, run_id)" + ], + [ + "index", + "ix_pc_dream_runs_proposal_fingerprint", + "pc_dream_runs", + "CREATE INDEX ix_pc_dream_runs_proposal_fingerprint ON pc_dream_runs (scope_id, principal_key, proposal_fingerprint)" + ], + [ + "index", + "ix_pc_processing_intent_binding_sequence", + "pc_artifact_processing_intents", + "CREATE INDEX ix_pc_processing_intent_binding_sequence ON pc_artifact_processing_intents (binding_name, pending_sequence)" + ], + [ + "index", + "ix_pc_recurrence_observation_key", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_key ON pc_recurrence_observation (scope_id, signature_key_hash)" + ], + [ + "index", + "ix_pc_recurrence_observation_outcome", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_outcome ON pc_recurrence_observation (scope_id, task_outcome_source_type, task_outcome_source_id, task_outcome_position)" + ], + [ + "index", + "ix_pc_recurrence_observation_revision", + "pc_recurrence_observation", + "CREATE INDEX ix_pc_recurrence_observation_revision ON pc_recurrence_observation (scope_id, family, artifact_id, revision)" + ], + [ + "table", + "pc_access_audit", + "pc_access_audit", + "CREATE TABLE pc_access_audit (\n\tcursor INTEGER NOT NULL, \n\tevent_id VARCHAR(64) NOT NULL, \n\toccurred_at VARCHAR(32) NOT NULL, \n\trequest_id VARCHAR(128), \n\ttransport VARCHAR(16) NOT NULL, \n\toperation VARCHAR(128) NOT NULL, \n\tprincipal_type VARCHAR(16) NOT NULL, \n\tprincipal_id VARCHAR(255) NOT NULL, \n\tprincipal_description TEXT, \n\tactor_type VARCHAR(16), \n\tactor_id VARCHAR(255), \n\tactor_description TEXT, \n\taction VARCHAR(64) NOT NULL, \n\tresource_type VARCHAR(16) NOT NULL, \n\tdeployment_id VARCHAR(128), \n\tscope_id VARCHAR(256), \n\tfamily VARCHAR(128), \n\tartifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\tallowed BOOLEAN NOT NULL, \n\treason_code VARCHAR(64) NOT NULL, \n\tpolicy_revision VARCHAR(64), \n\tmatched_subject_type VARCHAR(16), \n\tmatched_subject_id VARCHAR(255), \n\tmatched_subject_description TEXT, \n\tbinding_id VARCHAR(64), \n\ttarget_type VARCHAR(16), \n\ttarget_id VARCHAR(255), \n\ttarget_description TEXT, \n\trole VARCHAR(32), \n\texpected_version INTEGER, \n\tresult_version INTEGER, \n\tPRIMARY KEY (cursor), \n\tUNIQUE (event_id)\n)" + ], + [ + "table", + "pc_access_idempotency", + "pc_access_idempotency", + "CREATE TABLE pc_access_idempotency (\n\tactor_id VARCHAR(255) NOT NULL, \n\tidempotency_key_hash VARCHAR(64) NOT NULL, \n\toperation VARCHAR(64) NOT NULL, \n\tpayload_hash VARCHAR(64) NOT NULL, \n\tresult_binding_id VARCHAR(64), \n\tsecondary_binding_id VARCHAR(64), \n\tPRIMARY KEY (actor_id, idempotency_key_hash), \n\tCONSTRAINT uq_pc_access_idempotency_actor_key UNIQUE (actor_id, idempotency_key_hash)\n)" + ], + [ + "table", + "pc_access_owners", + "pc_access_owners", + "CREATE TABLE pc_access_owners (\n\towner_kind VARCHAR(16) NOT NULL, \n\tobject_key_hash VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128), \n\tcandidate_id VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\towner_type VARCHAR(16) NOT NULL, \n\towner_id VARCHAR(255) NOT NULL, \n\towner_description TEXT, \n\testablished_at VARCHAR(32), \n\tpolicy_revision VARCHAR(64), \n\tidempotency_key VARCHAR(255) NOT NULL, \n\tPRIMARY KEY (owner_kind, object_key_hash), \n\tCONSTRAINT ck_pc_access_owners_kind CHECK ((owner_kind = 'artifact' AND artifact_id IS NOT NULL AND candidate_id IS NULL AND target_artifact_id IS NULL AND established_at IS NOT NULL AND policy_revision IS NOT NULL) OR (owner_kind = 'candidate' AND candidate_id IS NOT NULL AND artifact_id IS NULL AND selector_type IS NULL AND selector_entry_id IS NULL AND established_at IS NULL AND policy_revision IS NULL))\n)" + ], + [ + "table", + "pc_access_relationship_heads", + "pc_access_relationship_heads", + "CREATE TABLE pc_access_relationship_heads (\n\tname VARCHAR(32) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tPRIMARY KEY (name), \n\tCONSTRAINT ck_pc_access_relationship_heads_revision_nonnegative CHECK (revision >= 0)\n)" + ], + [ + "table", + "pc_access_relationships", + "pc_access_relationships", + "CREATE TABLE pc_access_relationships (\n\tbinding_id VARCHAR(64) NOT NULL, \n\tsubject_type VARCHAR(16) NOT NULL, \n\tsubject_id VARCHAR(255) NOT NULL, \n\tsubject_description TEXT, \n\tresource_key_hash VARCHAR(64) NOT NULL, \n\tresource_type VARCHAR(16) NOT NULL, \n\tdeployment_id VARCHAR(128), \n\tscope_id VARCHAR(256), \n\tfamily VARCHAR(128), \n\tartifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\trole VARCHAR(32) NOT NULL, \n\tsingleton_key VARCHAR(64), \n\tgranted_by_type VARCHAR(16) NOT NULL, \n\tgranted_by_id VARCHAR(255) NOT NULL, \n\tgranted_by_description TEXT, \n\treason TEXT, \n\tcreated_at VARCHAR(32) NOT NULL, \n\texpires_at VARCHAR(32), \n\tstate VARCHAR(16) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tpolicy_revision VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(255) NOT NULL, \n\trevoked_at VARCHAR(32), \n\trevoked_by_type VARCHAR(16), \n\trevoked_by_id VARCHAR(255), \n\trevoked_by_description TEXT, \n\tPRIMARY KEY (binding_id), \n\tCONSTRAINT ck_pc_access_relationships_version_positive CHECK (version > 0), \n\tUNIQUE (singleton_key)\n)" + ], + [ + "table", + "pc_agent_skill_targets", + "pc_agent_skill_targets", + "CREATE TABLE pc_agent_skill_targets (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_id VARCHAR(64) NOT NULL, \n\tdisplay_name VARCHAR(128) NOT NULL, \n\tagent_kind VARCHAR(32) NOT NULL, \n\tinstallation_scope VARCHAR(16) NOT NULL, \n\tdelivery_mode VARCHAR(16) NOT NULL, \n\tinstallation_id VARCHAR(128), \n\tstate VARCHAR(16) NOT NULL, \n\tenrollment_token_digest VARCHAR(64), \n\tenrollment_expires_at DATETIME, \n\tcredential_subject VARCHAR(128), \n\tcredential_verifier VARCHAR(64), \n\treceiver_version VARCHAR(64), \n\tenvironment_fingerprint VARCHAR(64), \n\tmachine_hostname VARCHAR(255), \n\tworkspace_name VARCHAR(128), \n\tlast_seen_at DATETIME, \n\tgeneration BIGINT NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, target_id), \n\tCONSTRAINT uq_pc_agent_skill_targets_installation UNIQUE (scope_id, agent_kind, installation_scope, installation_id), \n\tCONSTRAINT uq_pc_agent_skill_targets_enrollment_token UNIQUE (enrollment_token_digest), \n\tCONSTRAINT uq_pc_agent_skill_targets_credential_subject UNIQUE (credential_subject), \n\tCONSTRAINT uq_pc_agent_skill_targets_credential_verifier UNIQUE (credential_verifier), \n\tCONSTRAINT ck_pc_agent_skill_targets_agent_kind CHECK (agent_kind IN ('codex', 'claude_code')), \n\tCONSTRAINT ck_pc_agent_skill_targets_installation_scope CHECK (installation_scope IN ('project')), \n\tCONSTRAINT ck_pc_agent_skill_targets_delivery_mode CHECK (delivery_mode = 'agent_pull'), \n\tCONSTRAINT ck_pc_agent_skill_targets_state CHECK (state IN ('pending', 'active', 'revoked')), \n\tCONSTRAINT ck_pc_agent_skill_targets_state_payload CHECK ((state = 'pending' AND enrollment_token_digest IS NOT NULL AND enrollment_expires_at IS NOT NULL AND installation_id IS NULL AND credential_subject IS NULL AND credential_verifier IS NULL) OR (state = 'active' AND enrollment_token_digest IS NULL AND enrollment_expires_at IS NULL AND installation_id IS NOT NULL AND credential_subject IS NOT NULL AND credential_verifier IS NOT NULL) OR (state = 'revoked' AND enrollment_token_digest IS NULL AND enrollment_expires_at IS NULL AND credential_verifier IS NULL)), \n\tCONSTRAINT ck_pc_agent_skill_targets_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_artifact_fts", + "pc_artifact_fts", + "CREATE VIRTUAL TABLE pc_artifact_fts USING fts5(\n scope_id UNINDEXED,\n family UNINDEXED,\n artifact_id UNINDEXED,\n revision UNINDEXED,\n searchable_text,\n tokenize='unicode61'\n)" + ], + [ + "table", + "pc_artifact_fts_config", + "pc_artifact_fts_config", + "CREATE TABLE 'pc_artifact_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_artifact_fts_content", + "pc_artifact_fts_content", + "CREATE TABLE 'pc_artifact_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4)" + ], + [ + "table", + "pc_artifact_fts_data", + "pc_artifact_fts_data", + "CREATE TABLE 'pc_artifact_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_artifact_fts_docsize", + "pc_artifact_fts_docsize", + "CREATE TABLE 'pc_artifact_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_artifact_fts_idx", + "pc_artifact_fts_idx", + "CREATE TABLE 'pc_artifact_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_artifact_handles", + "pc_artifact_handles", + "CREATE TABLE pc_artifact_handles (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\thandle_key VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, family, handle_key), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_handles_revision_positive CHECK (revision > 0)\n)" + ], + [ + "table", + "pc_artifact_heads", + "pc_artifact_heads", + "CREATE TABLE pc_artifact_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsearchable_text TEXT, \n\tlifecycle_state VARCHAR(16) DEFAULT 'active' NOT NULL, \n\treplacement_artifact_id VARCHAR(128), \n\tmerged_into_id VARCHAR(128), \n\tgovernance_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_heads_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_artifact_heads_lifecycle_state CHECK (lifecycle_state IN ('active', 'deprecated', 'retired')), \n\tCONSTRAINT ck_pc_artifact_heads_governance_generation_nonnegative CHECK (governance_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_heads_replacement_deprecated CHECK (replacement_artifact_id IS NULL OR lifecycle_state = 'deprecated'), \n\tCONSTRAINT ck_pc_artifact_heads_merge_target CHECK (merged_into_id IS NULL OR (lifecycle_state = 'deprecated' AND merged_into_id <> artifact_id AND replacement_artifact_id IS NULL))\n)" + ], + [ + "table", + "pc_artifact_lineage_artifacts", + "pc_artifact_lineage_artifacts", + "CREATE TABLE pc_artifact_lineage_artifacts (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tupstream_family VARCHAR(128) NOT NULL, \n\tupstream_artifact_id VARCHAR(128) NOT NULL, \n\tupstream_revision INTEGER NOT NULL, \n\tis_merge_input BOOLEAN DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision, ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, upstream_family, upstream_artifact_id, upstream_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_artifact_lineage_sources", + "pc_artifact_lineage_sources", + "CREATE TABLE pc_artifact_lineage_sources (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tsource_type VARCHAR(128) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision, ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, source_type, source_id) REFERENCES pc_sources (scope_id, source_type, source_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_artifact_processing_auto_wave_targets", + "pc_artifact_processing_auto_wave_targets", + "CREATE TABLE pc_artifact_processing_auto_wave_targets (\n\twave_id VARCHAR(36) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tcompleted BOOLEAN DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (wave_id, binding_name, scope_id), \n\tCONSTRAINT ck_pc_artifact_processing_auto_wave_target_source_positive CHECK (source_through >= 1)\n)" + ], + [ + "table", + "pc_artifact_processing_binding_states", + "pc_artifact_processing_binding_states", + "CREATE TABLE pc_artifact_processing_binding_states (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tlast_auto_wave_completed_at DATETIME, \n\tlast_schedule_checkpoint_at DATETIME, \n\tscan_generation BIGINT DEFAULT '0' NOT NULL, \n\tscan_in_progress BOOLEAN DEFAULT '0' NOT NULL, \n\tscan_upper_pending_sequence BIGINT, \n\tPRIMARY KEY (binding_name), \n\tCONSTRAINT ck_pc_processing_scan_generation CHECK (scan_generation >= 0)\n)" + ], + [ + "table", + "pc_artifact_processing_intents", + "pc_artifact_processing_intents", + "CREATE TABLE pc_artifact_processing_intents (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tpending_sequence BIGINT NOT NULL, \n\tdirty_generation BIGINT DEFAULT '0' NOT NULL, \n\tclean_generation BIGINT DEFAULT '0' NOT NULL, \n\trequested_generation BIGINT DEFAULT '0' NOT NULL, \n\thandled_generation BIGINT DEFAULT '0' NOT NULL, \n\tlast_auto_scan_generation BIGINT DEFAULT '0' NOT NULL, \n\tconsecutive_dream_attempts BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_processing_intent_sequence CHECK (pending_sequence > 0), \n\tCONSTRAINT ck_pc_processing_intent_dirty CHECK (clean_generation >= 0 AND clean_generation <= dirty_generation), \n\tCONSTRAINT ck_pc_processing_intent_request CHECK (handled_generation >= 0 AND handled_generation <= requested_generation), \n\tCONSTRAINT ck_pc_processing_intent_scan CHECK (last_auto_scan_generation >= 0), \n\tUNIQUE (pending_sequence)\n)" + ], + [ + "table", + "pc_artifact_processing_leases", + "pc_artifact_processing_leases", + "CREATE TABLE pc_artifact_processing_leases (\n\tsupervisor_group VARCHAR(64) NOT NULL, \n\tholder_id VARCHAR(36) NOT NULL, \n\tsupervisor_generation BIGINT NOT NULL, \n\tlease_expires_at DATETIME, \n\tPRIMARY KEY (supervisor_group), \n\tCONSTRAINT ck_pc_artifact_processing_leases_generation_positive CHECK (supervisor_generation > 0)\n)" + ], + [ + "table", + "pc_artifact_processing_migration_receipts", + "pc_artifact_processing_migration_receipts", + "CREATE TABLE pc_artifact_processing_migration_receipts (\n\tmigration_id VARCHAR(64) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_snapshot BLOB NOT NULL, \n\trequested_generation BIGINT NOT NULL, \n\tdirty_generation BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tPRIMARY KEY (migration_id, binding_name, scope_id)\n)" + ], + [ + "table", + "pc_artifact_processing_pending", + "pc_artifact_processing_pending", + "CREATE TABLE pc_artifact_processing_pending (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tflush_generation BIGINT DEFAULT '0' NOT NULL, \n\thandled_flush_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_artifact_processing_pending_source_positive CHECK (source_through >= 1), \n\tCONSTRAINT ck_pc_artifact_processing_pending_flush_nonnegative CHECK (flush_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_processing_pending_handled_nonnegative CHECK (handled_flush_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_processing_pending_handled_not_ahead CHECK (handled_flush_generation <= flush_generation)\n)" + ], + [ + "table", + "pc_artifact_processing_schema", + "pc_artifact_processing_schema", + "CREATE TABLE pc_artifact_processing_schema (\n\tsingleton INTEGER NOT NULL, \n\tschema_version INTEGER NOT NULL, \n\tsource_version INTEGER DEFAULT '1490' NOT NULL, \n\tphase VARCHAR(32) NOT NULL, \n\tmigration_id VARCHAR(64) NOT NULL, \n\tconfig_manifest TEXT NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_processing_schema_singleton CHECK (singleton = 1)\n)" + ], + [ + "table", + "pc_artifact_processing_sequences", + "pc_artifact_processing_sequences", + "CREATE TABLE pc_artifact_processing_sequences (\n\tsingleton INTEGER NOT NULL, \n\tsequence BIGINT NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_processing_sequence CHECK (singleton = 1 AND sequence >= 0)\n)" + ], + [ + "table", + "pc_artifact_publications", + "pc_artifact_publications", + "CREATE TABLE pc_artifact_publications (\n\ttarget_scope_id VARCHAR(256) NOT NULL, \n\ttarget_family VARCHAR(128) NOT NULL, \n\ttarget_artifact_id VARCHAR(128) NOT NULL, \n\ttarget_revision INTEGER NOT NULL, \n\tsource_scope_id VARCHAR(256) NOT NULL, \n\tsource_family VARCHAR(128) NOT NULL, \n\tsource_artifact_id VARCHAR(128) NOT NULL, \n\tsource_revision INTEGER NOT NULL, \n\tcontent_digest VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (target_scope_id, target_family, target_artifact_id, target_revision), \n\tFOREIGN KEY(target_scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tFOREIGN KEY(source_scope_id, source_family, source_artifact_id, source_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT uq_pc_artifact_publications_request UNIQUE (target_scope_id, idempotency_key)\n)" + ], + [ + "table", + "pc_artifact_tags", + "pc_artifact_tags", + "CREATE TABLE pc_artifact_tags (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\ttarget_type VARCHAR(12) NOT NULL, \n\ttarget_id VARCHAR(128) NOT NULL, \n\ttag_key_hash BLOB NOT NULL, \n\ttag_key VARCHAR(128) NOT NULL, \n\ttag VARCHAR(64) NOT NULL, \n\tassigned_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, target_type, target_id, tag_key_hash), \n\tFOREIGN KEY(scope_id, family, artifact_id) REFERENCES pc_artifact_heads (scope_id, family, artifact_id) ON DELETE CASCADE, \n\tCONSTRAINT ck_pc_artifact_tags_target CHECK ((target_type = 'artifact' AND target_id = artifact_id) OR (target_type = 'memory_entry' AND family = 'memory'))\n)" + ], + [ + "table", + "pc_artifacts", + "pc_artifacts", + "CREATE TABLE pc_artifacts (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tcontent BLOB NOT NULL, \n\tcreated_at DATETIME, \n\tcreated_by VARCHAR(2048), \n\trestored_from_revision INTEGER, \n\trollback_reason VARCHAR(1024), \n\tPRIMARY KEY (scope_id, family, artifact_id, revision)\n)" + ], + [ + "table", + "pc_atomic_memory_current", + "pc_atomic_memory_current", + "CREATE TABLE pc_atomic_memory_current (\n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision BIGINT NOT NULL, \n\tstate_version BIGINT NOT NULL, \n\tcontent_hash VARCHAR(64) NOT NULL, \n\tprojection_format VARCHAR(64) NOT NULL, \n\tkind TEXT NOT NULL, \n\ttext TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\ttag_keys TEXT NOT NULL, \n\tembedding BLOB, \n\tprofile_fingerprint VARCHAR(64), \n\tembedding_input_hash VARCHAR(64), \n\tPRIMARY KEY (scope_id, artifact_id), \n\tCONSTRAINT ck_pc_atomic_memory_current_revision CHECK (revision > 0), \n\tCONSTRAINT ck_pc_atomic_memory_current_state_version CHECK (state_version >= 0), \n\tCONSTRAINT ck_pc_atomic_memory_current_embedding_metadata CHECK ((embedding IS NULL AND profile_fingerprint IS NULL AND embedding_input_hash IS NULL) OR (embedding IS NOT NULL AND profile_fingerprint IS NOT NULL AND embedding_input_hash IS NOT NULL))\n)" + ], + [ + "table", + "pc_atomic_memory_current_fts", + "pc_atomic_memory_current_fts", + "CREATE VIRTUAL TABLE pc_atomic_memory_current_fts USING fts5(scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED, state_version UNINDEXED, content_hash UNINDEXED, projection_format UNINDEXED, kind UNINDEXED, text UNINDEXED, searchable_text, tag_keys UNINDEXED, identity_token, tokenize='unicode61')" + ], + [ + "table", + "pc_atomic_memory_current_fts_config", + "pc_atomic_memory_current_fts_config", + "CREATE TABLE 'pc_atomic_memory_current_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_atomic_memory_current_fts_content", + "pc_atomic_memory_current_fts_content", + "CREATE TABLE 'pc_atomic_memory_current_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)" + ], + [ + "table", + "pc_atomic_memory_current_fts_data", + "pc_atomic_memory_current_fts_data", + "CREATE TABLE 'pc_atomic_memory_current_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_atomic_memory_current_fts_docsize", + "pc_atomic_memory_current_fts_docsize", + "CREATE TABLE 'pc_atomic_memory_current_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_atomic_memory_current_fts_idx", + "pc_atomic_memory_current_fts_idx", + "CREATE TABLE 'pc_atomic_memory_current_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_candidate_heads", + "pc_candidate_heads", + "CREATE TABLE pc_candidate_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\tcandidate_kind VARCHAR(16) DEFAULT 'artifact' NOT NULL, \n\tresult_payload BLOB, \n\tresult_family VARCHAR(128), \n\tresult_artifact_id VARCHAR(128), \n\tresult_revision INTEGER, \n\tdecision_reason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, candidate_id, version) REFERENCES pc_candidate_versions (scope_id, candidate_id, version) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, result_family, result_artifact_id, result_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_candidate_kind CHECK (candidate_kind IN ('artifact', 'tag')), \n\tCONSTRAINT ck_pc_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_candidate_heads_terminal_result CHECK ((status = 'approved' AND candidate_kind = 'artifact' AND result_payload IS NULL AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'approved' AND candidate_kind = 'tag' AND result_payload IS NOT NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL) OR (status = 'rejected' AND result_payload IS NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_payload IS NULL AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)" + ], + [ + "table", + "pc_candidate_versions", + "pc_candidate_versions", + "CREATE TABLE pc_candidate_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tproposal BLOB NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\treason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)" + ], + [ + "table", + "pc_connector_checkpoints", + "pc_connector_checkpoints", + "CREATE TABLE pc_connector_checkpoints (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_id VARCHAR(256) NOT NULL, \n\tconnector_name VARCHAR(128) NOT NULL, \n\tconnector_version VARCHAR(128) NOT NULL, \n\tcheckpoint BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, binding_id)\n)" + ], + [ + "table", + "pc_context_bootstrap_receipts", + "pc_context_bootstrap_receipts", + "CREATE TABLE pc_context_bootstrap_receipts (\n\treceipt_id VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tevent_key VARCHAR(64), \n\trequest_digest VARCHAR(71) NOT NULL, \n\tintegration VARCHAR(64) NOT NULL, \n\tlifecycle VARCHAR(16) NOT NULL, \n\tprofile VARCHAR(64) NOT NULL, \n\tstate VARCHAR(16) NOT NULL, \n\treason VARCHAR(32), \n\tmax_bytes INTEGER NOT NULL, \n\tpackage_digest VARCHAR(71), \n\tcontent_bytes INTEGER NOT NULL, \n\ttruncated BOOLEAN NOT NULL, \n\titems BLOB NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tPRIMARY KEY (receipt_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_context_bootstrap_receipts_event UNIQUE (event_key), \n\tCONSTRAINT ck_pc_context_bootstrap_receipts_state CHECK (state IN ('pending', 'injected', 'skipped', 'failed')), \n\tCONSTRAINT ck_pc_context_bootstrap_receipts_budget CHECK (max_bytes >= 512 AND max_bytes <= 8192), \n\tCONSTRAINT ck_pc_context_bootstrap_receipts_content_bytes CHECK (content_bytes >= 0)\n)" + ], + [ + "table", + "pc_decision_observations", + "pc_decision_observations", + "CREATE TABLE pc_decision_observations (\n\tscope_id VARCHAR(256) NOT NULL, \n\tobservation_id VARCHAR(128) NOT NULL, \n\toperation_id VARCHAR(256) NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tconsumer VARCHAR(128) NOT NULL, \n\tpolicy_id VARCHAR(256) NOT NULL, \n\tpolicy_version VARCHAR(64) NOT NULL, \n\tmode VARCHAR(16) NOT NULL, \n\tprivacy_boundary VARCHAR(32) NOT NULL, \n\tcoverage VARCHAR(16) NOT NULL, \n\tverdict VARCHAR(16) NOT NULL, \n\tassessment_source VARCHAR(32) NOT NULL, \n\tfinal_action VARCHAR(128) NOT NULL, \n\tused_fallback BOOLEAN NOT NULL, \n\tfallback_reason VARCHAR(128), \n\tprovider_id VARCHAR(128), \n\tbackend_model_id VARCHAR(256), \n\tmodel_policy_id VARCHAR(256), \n\trequests BIGINT NOT NULL, \n\tinput_tokens BIGINT, \n\toutput_tokens BIGINT, \n\tlatency_ms FLOAT, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, observation_id), \n\tCONSTRAINT ck_pc_decision_observations_requests_nonnegative CHECK (requests >= 0), \n\tCONSTRAINT ck_pc_decision_observations_input_tokens_nonnegative CHECK (input_tokens IS NULL OR input_tokens >= 0), \n\tCONSTRAINT ck_pc_decision_observations_output_tokens_nonnegative CHECK (output_tokens IS NULL OR output_tokens >= 0), \n\tCONSTRAINT ck_pc_decision_observations_latency_nonnegative CHECK (latency_ms IS NULL OR latency_ms >= 0)\n)" + ], + [ + "table", + "pc_dream_runs", + "pc_dream_runs", + "CREATE TABLE pc_dream_runs (\n\tscope_id VARCHAR(256) NOT NULL, \n\trun_id VARCHAR(64) NOT NULL, \n\tprincipal_key VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(128) NOT NULL, \n\trequest_digest VARCHAR(71) NOT NULL, \n\tproposal_fingerprint VARCHAR(71), \n\toperation VARCHAR(32) NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\taccepted_at BIGINT NOT NULL, \n\tgeneration INTEGER NOT NULL, \n\trequest_generation BIGINT NOT NULL, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, run_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_dream_idempotency UNIQUE (scope_id, principal_key, idempotency_key), \n\tCONSTRAINT ck_pc_dream_generation CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_external_skill_registrations", + "pc_external_skill_registrations", + "CREATE TABLE pc_external_skill_registrations (\n\tscope_id VARCHAR(256) NOT NULL, \n\texternal_skill_id VARCHAR(128) NOT NULL, \n\tprovider VARCHAR(128) NOT NULL, \n\tagent_kind VARCHAR(128) NOT NULL, \n\thost_id VARCHAR(128) NOT NULL, \n\tinstallation_scope VARCHAR(16) NOT NULL, \n\tlocator VARCHAR(2000) NOT NULL, \n\tlocator_hash VARCHAR(64) NOT NULL, \n\tfingerprint VARCHAR(64) NOT NULL, \n\tname VARCHAR(128) NOT NULL, \n\tdescription VARCHAR(2000) NOT NULL, \n\tPRIMARY KEY (scope_id, external_skill_id), \n\tCONSTRAINT uq_pc_external_skill_registrations_binding UNIQUE (scope_id, provider, host_id, installation_scope, locator_hash), \n\tCONSTRAINT ck_pc_external_skill_registrations_scope CHECK (installation_scope IN ('user', 'project', 'plugin'))\n)" + ], + [ + "table", + "pc_lexical_projection_versions", + "pc_lexical_projection_versions", + "CREATE TABLE pc_lexical_projection_versions (\n\tprojection VARCHAR(128) NOT NULL, \n\tanalyzer_version VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (projection)\n)" + ], + [ + "table", + "pc_memory_entry_heads", + "pc_memory_entry_heads", + "CREATE TABLE pc_memory_entry_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tmemory_artifact_id VARCHAR(128) NOT NULL, \n\thead_revision INTEGER NOT NULL, \n\tentry_id VARCHAR(128) NOT NULL, \n\tentry_version_id VARCHAR(128) NOT NULL, \n\tentry_content_hash VARCHAR(64) NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tPRIMARY KEY (scope_id, memory_artifact_id, entry_id), \n\tFOREIGN KEY(scope_id, memory_artifact_id, entry_id, entry_version_id) REFERENCES pc_memory_entry_versions (scope_id, memory_artifact_id, entry_id, entry_version_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_memory_entry_heads_revision_positive CHECK (head_revision > 0)\n)" + ], + [ + "table", + "pc_memory_entry_versions", + "pc_memory_entry_versions", + "CREATE TABLE pc_memory_entry_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tmemory_artifact_id VARCHAR(128) NOT NULL, \n\tentry_id VARCHAR(128) NOT NULL, \n\tentry_version_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tprevious_version_id VARCHAR(128), \n\tkind VARCHAR(128) NOT NULL, \n\ttext TEXT NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\tentry_content_hash VARCHAR(64) NOT NULL, \n\tcreated_in_revision INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, memory_artifact_id, entry_version_id), \n\tCONSTRAINT uq_pc_memory_entry_versions_logical_version UNIQUE (scope_id, memory_artifact_id, entry_id, version), \n\tCONSTRAINT uq_pc_memory_entry_versions_identity UNIQUE (scope_id, memory_artifact_id, entry_id, entry_version_id), \n\tCONSTRAINT ck_pc_memory_entry_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_memory_entry_versions_revision_positive CHECK (created_in_revision > 0)\n)" + ], + [ + "table", + "pc_memory_source_windows", + "pc_memory_source_windows", + "CREATE TABLE pc_memory_source_windows (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\twindow_limit BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tCONSTRAINT ck_pc_memory_source_window_positive CHECK (source_through > 0 AND window_limit > 0)\n)" + ], + [ + "table", + "pc_model_usage_daily", + "pc_model_usage_daily", + "CREATE TABLE pc_model_usage_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\tpurpose VARCHAR(64) NOT NULL, \n\toperation VARCHAR(16) NOT NULL, \n\trequests BIGINT NOT NULL, \n\tinput_tokens BIGINT NOT NULL, \n\toutput_tokens BIGINT NOT NULL, \n\tinput_complete BOOLEAN NOT NULL, \n\toutput_complete BOOLEAN NOT NULL, \n\tPRIMARY KEY (scope_id, usage_date, purpose, operation), \n\tCONSTRAINT ck_pc_model_usage_daily_operation CHECK (operation IN ('generation', 'embedding')), \n\tCONSTRAINT ck_pc_model_usage_daily_requests_nonnegative CHECK (requests >= 0), \n\tCONSTRAINT ck_pc_model_usage_daily_input_nonnegative CHECK (input_tokens >= 0), \n\tCONSTRAINT ck_pc_model_usage_daily_output_nonnegative CHECK (output_tokens >= 0)\n)" + ], + [ + "table", + "pc_profile_policies", + "pc_profile_policies", + "CREATE TABLE pc_profile_policies (\n\tscope_id VARCHAR(256) NOT NULL, \n\tgeneration_enabled BOOLEAN NOT NULL, \n\tactivation_mode VARCHAR(32) NOT NULL, \n\tpending_candidate_id VARCHAR(128), \n\tversion BIGINT NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, pending_candidate_id) REFERENCES pc_candidate_heads (scope_id, candidate_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_profile_policies_activation_mode CHECK (activation_mode IN ('automatic', 'review_required')), \n\tCONSTRAINT ck_pc_profile_policies_version_positive CHECK (version > 0)\n)" + ], + [ + "table", + "pc_recall_effort_daily", + "pc_recall_effort_daily", + "CREATE TABLE pc_recall_effort_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\tpolicy_id VARCHAR(128) NOT NULL, \n\tassessment VARCHAR(64) NOT NULL, \n\tpreparations BIGINT NOT NULL CONSTRAINT ck_recall_effort_preparations_nonnegative CHECK (preparations >= 0), \n\trounds BIGINT NOT NULL CONSTRAINT ck_recall_effort_rounds_nonnegative CHECK (rounds >= 0), \n\texpanded_preparations BIGINT NOT NULL CONSTRAINT ck_recall_effort_expanded_preparations_nonnegative CHECK (expanded_preparations >= 0), \n\tadmission_expansions BIGINT NOT NULL CONSTRAINT ck_recall_effort_admission_expansions_nonnegative CHECK (admission_expansions >= 0), \n\tpolicy_floor_expansions BIGINT NOT NULL CONSTRAINT ck_recall_effort_policy_floor_expansions_nonnegative CHECK (policy_floor_expansions >= 0), \n\tcandidate_round_samples BIGINT NOT NULL CONSTRAINT ck_recall_effort_candidate_round_samples_nonnegative CHECK (candidate_round_samples >= 0), \n\tcandidates_assessed BIGINT NOT NULL CONSTRAINT ck_recall_effort_candidates_assessed_nonnegative CHECK (candidates_assessed >= 0), \n\tfinal_candidate_pool BIGINT NOT NULL CONSTRAINT ck_recall_effort_final_candidate_pool_nonnegative CHECK (final_candidate_pool >= 0), \n\tadded_embeddings BIGINT NOT NULL CONSTRAINT ck_recall_effort_added_embeddings_nonnegative CHECK (added_embeddings >= 0), \n\tadded_generation_calls BIGINT NOT NULL CONSTRAINT ck_recall_effort_added_generation_calls_nonnegative CHECK (added_generation_calls >= 0), \n\ttruncated_items BIGINT NOT NULL CONSTRAINT ck_recall_effort_truncated_items_nonnegative CHECK (truncated_items >= 0), \n\tdropped_items BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_items_nonnegative CHECK (dropped_items >= 0), \n\tdropped_below_min_bytes BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_below_min_bytes_nonnegative CHECK (dropped_below_min_bytes >= 0), \n\tdropped_no_fitting_truncation BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_no_fitting_truncation_nonnegative CHECK (dropped_no_fitting_truncation >= 0), \n\tPRIMARY KEY (scope_id, usage_date, policy_id, assessment), \n\tCONSTRAINT ck_pc_recall_effort_daily_drop_total CHECK (dropped_items = dropped_below_min_bytes + dropped_no_fitting_truncation)\n)" + ], + [ + "table", + "pc_recall_token_daily", + "pc_recall_token_daily", + "CREATE TABLE pc_recall_token_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\testimator_id VARCHAR(128) NOT NULL, \n\testimator_version VARCHAR(64) NOT NULL, \n\tpreparations BIGINT NOT NULL, \n\tready_preparations BIGINT NOT NULL, \n\tcomparable_preparations BIGINT NOT NULL, \n\tbaseline_tokens BIGINT NOT NULL, \n\trecalled_tokens BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, usage_date, estimator_id, estimator_version), \n\tCONSTRAINT ck_pc_recall_token_daily_preparations_nonnegative CHECK (preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_ready_nonnegative CHECK (ready_preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_comparable_nonnegative CHECK (comparable_preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_comparable_ready CHECK (comparable_preparations <= ready_preparations), \n\tCONSTRAINT ck_pc_recall_token_daily_ready_total CHECK (ready_preparations <= preparations), \n\tCONSTRAINT ck_pc_recall_token_daily_baseline_nonnegative CHECK (baseline_tokens >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_recalled_nonnegative CHECK (recalled_tokens >= 0)\n)" + ], + [ + "table", + "pc_receipt_migration_review", + "pc_receipt_migration_review", + "CREATE TABLE pc_receipt_migration_review (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\treason VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, source_id)\n)" + ], + [ + "table", + "pc_recurrence_match", + "pc_recurrence_match", + "CREATE TABLE pc_recurrence_match (\n\tscope_id VARCHAR(256) NOT NULL, \n\tmatch_key VARCHAR(71) NOT NULL, \n\ttask_outcome_source_type VARCHAR(128) NOT NULL, \n\ttask_outcome_source_id VARCHAR(256) NOT NULL, \n\ttask_outcome_position BIGINT NOT NULL, \n\tfailure_item_kind VARCHAR(16) NOT NULL, \n\tfailure_item_index INTEGER NOT NULL, \n\tfailure_item_digest VARCHAR(71) NOT NULL, \n\tcandidate_set_mode VARCHAR(32) NOT NULL, \n\tcandidate_set_digest VARCHAR(71) NOT NULL, \n\tresult VARCHAR(16) NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\tsignature_key TEXT, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, match_key), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_recurrence_match_identity UNIQUE (scope_id, task_outcome_source_type, task_outcome_source_id, failure_item_kind, failure_item_index), \n\tCONSTRAINT ck_pc_recurrence_match_result CHECK (result IN ('matched', 'unmatched', 'ambiguous')), \n\tCONSTRAINT ck_pc_recurrence_match_candidate_set_mode CHECK (candidate_set_mode IN ('handoff_citations', 'scope_heads')), \n\tCONSTRAINT ck_pc_recurrence_match_failure_item_kind CHECK (failure_item_kind IN ('observation', 'check')), \n\tCONSTRAINT ck_pc_recurrence_match_position_positive CHECK (task_outcome_position > 0), \n\tCONSTRAINT ck_pc_recurrence_match_item_index_nonnegative CHECK (failure_item_index >= 0)\n)" + ], + [ + "table", + "pc_recurrence_observation", + "pc_recurrence_observation", + "CREATE TABLE pc_recurrence_observation (\n\tscope_id VARCHAR(256) NOT NULL, \n\tobservation_id VARCHAR(71) NOT NULL, \n\tselection_key VARCHAR(71) NOT NULL, \n\tverdict_key VARCHAR(71) NOT NULL, \n\tevent VARCHAR(16) NOT NULL, \n\tmatch_basis VARCHAR(8) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsignature_key TEXT NOT NULL, \n\tsignature_key_hash BLOB NOT NULL, \n\ttask_outcome_source_type VARCHAR(128) NOT NULL, \n\ttask_outcome_source_id VARCHAR(256) NOT NULL, \n\ttask_outcome_position BIGINT NOT NULL, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, observation_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_recurrence_observation_selection UNIQUE (scope_id, selection_key), \n\tCONSTRAINT uq_pc_recurrence_observation_verdict UNIQUE (scope_id, verdict_key), \n\tCONSTRAINT ck_pc_recurrence_observation_event CHECK (event IN ('selected', 'recurred', 'avoided')), \n\tCONSTRAINT ck_pc_recurrence_observation_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_recurrence_observation_position_positive CHECK (task_outcome_position > 0)\n)" + ], + [ + "table", + "pc_scope_bindings", + "pc_scope_bindings", + "CREATE TABLE pc_scope_bindings (\n\tintegration VARCHAR(128) NOT NULL, \n\tkind VARCHAR(64) NOT NULL, \n\texternal_id VARCHAR(256) NOT NULL, \n\texternal_id_search TEXT NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (integration, kind, external_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scope_context_references", + "pc_scope_context_references", + "CREATE TABLE pc_scope_context_references (\n\tscope_id VARCHAR(256) NOT NULL, \n\treferenced_scope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (scope_id, referenced_scope_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(referenced_scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_scope_context_references_not_self CHECK (scope_id <> referenced_scope_id)\n)" + ], + [ + "table", + "pc_scope_creation_requests", + "pc_scope_creation_requests", + "CREATE TABLE pc_scope_creation_requests (\n\tidempotency_key VARCHAR(256) NOT NULL, \n\trequest_digest VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (idempotency_key), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scope_external_references", + "pc_scope_external_references", + "CREATE TABLE pc_scope_external_references (\n\tscope_id VARCHAR(256) NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tkind VARCHAR(128) NOT NULL, \n\tvalue VARCHAR(2000) NOT NULL, \n\tvalue_search TEXT NOT NULL, \n\tvalue_digest VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, ordinal), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_scope_external_references_value UNIQUE (scope_id, kind, value_digest), \n\tCONSTRAINT ck_pc_scope_external_references_ordinal_nonnegative CHECK (ordinal >= 0)\n)" + ], + [ + "table", + "pc_scope_settings", + "pc_scope_settings", + "CREATE TABLE pc_scope_settings (\n\tname VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (name), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)" + ], + [ + "table", + "pc_scopes", + "pc_scopes", + "CREATE TABLE pc_scopes (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttitle VARCHAR(256) NOT NULL, \n\tsummary VARCHAR(2000) NOT NULL, \n\tscope_id_search TEXT NOT NULL, \n\ttitle_search TEXT NOT NULL, \n\tsummary_search TEXT NOT NULL, \n\tparent_scope_id VARCHAR(256), \n\tversion INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tFOREIGN KEY(parent_scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_scopes_version_positive CHECK (version > 0)\n)" + ], + [ + "table", + "pc_server_identity", + "pc_server_identity", + "CREATE TABLE pc_server_identity (\n\tsingleton_key INTEGER NOT NULL, \n\tserver_id VARCHAR(36) NOT NULL, \n\tPRIMARY KEY (singleton_key), \n\tCONSTRAINT ck_pc_server_identity_singleton CHECK (singleton_key = 1), \n\tUNIQUE (server_id)\n)" + ], + [ + "table", + "pc_skill_packages", + "pc_skill_packages", + "CREATE TABLE pc_skill_packages (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttree_digest VARCHAR(64) NOT NULL, \n\tarchive_digest VARCHAR(64) NOT NULL, \n\tarchive_bytes BLOB NOT NULL, \n\tmanifest BLOB NOT NULL, \n\tfile_count INTEGER NOT NULL, \n\tuncompressed_size BIGINT NOT NULL, \n\tarchive_size BIGINT NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, tree_digest), \n\tCONSTRAINT ck_pc_skill_packages_file_count CHECK (file_count > 0 AND file_count <= 256), \n\tCONSTRAINT ck_pc_skill_packages_uncompressed_size CHECK (uncompressed_size > 0 AND uncompressed_size <= 4194304), \n\tCONSTRAINT ck_pc_skill_packages_archive_size CHECK (archive_size > 0 AND archive_size <= 5242880)\n)" + ], + [ + "table", + "pc_skill_publications", + "pc_skill_publications", + "CREATE TABLE pc_skill_publications (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_id VARCHAR(64) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\tdesired_state VARCHAR(16) DEFAULT 'published' NOT NULL, \n\tdesired_revision INTEGER NOT NULL, \n\tdesired_tree_digest VARCHAR(64) NOT NULL, \n\tobserved_revision INTEGER, \n\tobserved_tree_digest VARCHAR(64), \n\tobserved_generation BIGINT, \n\tdestination TEXT, \n\tstate VARCHAR(32) NOT NULL, \n\tselected_runtime_variant VARCHAR(128), \n\tenvironment_fingerprint VARCHAR(64), \n\tlast_error_code VARCHAR(128), \n\tobserved_at DATETIME, \n\tgeneration BIGINT NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, target_id, artifact_id), \n\tCONSTRAINT ck_pc_skill_publications_desired_revision_positive CHECK (desired_revision > 0), \n\tCONSTRAINT ck_pc_skill_publications_observed_revision_positive CHECK (observed_revision IS NULL OR observed_revision > 0), \n\tCONSTRAINT ck_pc_skill_publications_desired_state CHECK (desired_state IN ('published', 'unpublished')), \n\tCONSTRAINT ck_pc_skill_publications_state CHECK (state IN ('unpublished', 'pending', 'current', 'update_available', 'delivery_failed', 'conflict', 'drifted', 'incompatible')), \n\tCONSTRAINT ck_pc_skill_publications_observed_generation_nonnegative CHECK (observed_generation IS NULL OR observed_generation >= 0), \n\tCONSTRAINT ck_pc_skill_publications_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_source_cursors", + "pc_source_cursors", + "CREATE TABLE pc_source_cursors (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tcursor BLOB NOT NULL, \n\tgeneration BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, binding_name), \n\tCONSTRAINT ck_pc_source_cursors_generation_nonnegative CHECK (generation >= 0)\n)" + ], + [ + "table", + "pc_source_definition_manifests", + "pc_source_definition_manifests", + "CREATE TABLE pc_source_definition_manifests (\n\tdefinition_name VARCHAR(128) NOT NULL, \n\tdefinition_version VARCHAR(128) NOT NULL, \n\tfingerprint VARCHAR(71) NOT NULL, \n\tmanifest BLOB NOT NULL, \n\tPRIMARY KEY (definition_name, definition_version), \n\tCONSTRAINT uq_pc_source_definition_manifest_fingerprint UNIQUE (definition_name, fingerprint)\n)" + ], + [ + "table", + "pc_source_journal_heads", + "pc_source_journal_heads", + "CREATE TABLE pc_source_journal_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tposition BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tCONSTRAINT ck_pc_source_journal_heads_position_nonnegative CHECK (position >= 0)\n)" + ], + [ + "table", + "pc_sources", + "pc_sources", + "CREATE TABLE pc_sources (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_type VARCHAR(128) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\tpayload BLOB NOT NULL, \n\tjournal_position BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, source_type, source_id), \n\tCONSTRAINT uq_pc_sources_scope_journal_position UNIQUE (scope_id, journal_position)\n)" + ], + [ + "table", + "pc_topic_memory_active_chunks", + "pc_topic_memory_active_chunks", + "CREATE TABLE pc_topic_memory_active_chunks (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tchunk_ordinal INTEGER NOT NULL, \n\tstart_offset INTEGER NOT NULL, \n\tend_offset INTEGER NOT NULL, \n\tchunk_text TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tpolicy_version VARCHAR(32) NOT NULL, \n\tPRIMARY KEY (scope_id, artifact_id, chunk_ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_topic_memory_revision_publications (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_ordinal_nonnegative CHECK (chunk_ordinal >= 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_start_nonnegative CHECK (start_offset >= 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_offsets CHECK (end_offset > start_offset)\n)" + ], + [ + "table", + "pc_topic_memory_active_topics", + "pc_topic_memory_active_topics", + "CREATE TABLE pc_topic_memory_active_topics (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\ttitle TEXT NOT NULL, \n\tsummary TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tsource_count INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_topic_memory_revision_publications (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_active_topics_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_active_topics_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_topic_memory_active_topics_sources_nonnegative CHECK (source_count >= 0)\n)" + ], + [ + "table", + "pc_topic_memory_chunk_fts", + "pc_topic_memory_chunk_fts", + "CREATE VIRTUAL TABLE pc_topic_memory_chunk_fts USING fts5(\n scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED,\n chunk_ordinal UNINDEXED, start_offset UNINDEXED,\n title UNINDEXED, summary UNINDEXED, chunk_text UNINDEXED,\n searchable_text, tokenize='unicode61'\n)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_config", + "pc_topic_memory_chunk_fts_config", + "CREATE TABLE 'pc_topic_memory_chunk_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_chunk_fts_content", + "pc_topic_memory_chunk_fts_content", + "CREATE TABLE 'pc_topic_memory_chunk_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5, c6, c7, c8)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_data", + "pc_topic_memory_chunk_fts_data", + "CREATE TABLE 'pc_topic_memory_chunk_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_docsize", + "pc_topic_memory_chunk_fts_docsize", + "CREATE TABLE 'pc_topic_memory_chunk_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_topic_memory_chunk_fts_idx", + "pc_topic_memory_chunk_fts_idx", + "CREATE TABLE 'pc_topic_memory_chunk_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_fts_index", + "pc_topic_memory_fts_index", + "CREATE TABLE pc_topic_memory_fts_index (\n\tsingleton INTEGER NOT NULL, \n\tschema_version INTEGER NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_topic_memory_fts_index_singleton CHECK (singleton = 1)\n)" + ], + [ + "table", + "pc_topic_memory_processing_targets", + "pc_topic_memory_processing_targets", + "CREATE TABLE pc_topic_memory_processing_targets (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_request_generation BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tcaptured_flush_generation BIGINT NOT NULL, \n\tobserved_dirty_generation BIGINT NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_topic_processing_target CHECK (target_request_generation > 0 AND source_through >= 0 AND captured_flush_generation >= 0 AND observed_dirty_generation >= 0)\n)" + ], + [ + "table", + "pc_topic_memory_retrieval_shape", + "pc_topic_memory_retrieval_shape", + "CREATE TABLE pc_topic_memory_retrieval_shape (\n\tsingleton INTEGER NOT NULL, \n\tshape VARCHAR(16) NOT NULL, \n\tprofile_fingerprint VARCHAR(64), \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_singleton CHECK (singleton = 1), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_value CHECK (shape IN ('fts', 'hybrid')), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_profile CHECK ((shape = 'fts' AND profile_fingerprint IS NULL) OR (shape = 'hybrid' AND profile_fingerprint IS NOT NULL))\n)" + ], + [ + "table", + "pc_topic_memory_revision_publications", + "pc_topic_memory_revision_publications", + "CREATE TABLE pc_topic_memory_revision_publications (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tpublished_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_publications_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_publications_revision_positive CHECK (revision > 0)\n)" + ], + [ + "table", + "pc_topic_memory_topic_fts", + "pc_topic_memory_topic_fts", + "CREATE VIRTUAL TABLE pc_topic_memory_topic_fts USING fts5(\n scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED,\n title UNINDEXED, summary UNINDEXED, searchable_text, tokenize='unicode61'\n)" + ], + [ + "table", + "pc_topic_memory_topic_fts_config", + "pc_topic_memory_topic_fts_config", + "CREATE TABLE 'pc_topic_memory_topic_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_topic_fts_content", + "pc_topic_memory_topic_fts_content", + "CREATE TABLE 'pc_topic_memory_topic_fts_content'(id INTEGER PRIMARY KEY, c0, c1, c2, c3, c4, c5)" + ], + [ + "table", + "pc_topic_memory_topic_fts_data", + "pc_topic_memory_topic_fts_data", + "CREATE TABLE 'pc_topic_memory_topic_fts_data'(id INTEGER PRIMARY KEY, block BLOB)" + ], + [ + "table", + "pc_topic_memory_topic_fts_docsize", + "pc_topic_memory_topic_fts_docsize", + "CREATE TABLE 'pc_topic_memory_topic_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)" + ], + [ + "table", + "pc_topic_memory_topic_fts_idx", + "pc_topic_memory_topic_fts_idx", + "CREATE TABLE 'pc_topic_memory_topic_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID" + ], + [ + "table", + "pc_topic_memory_work_blocks", + "pc_topic_memory_work_blocks", + "CREATE TABLE pc_topic_memory_work_blocks (\n\tblock_id VARCHAR(36) NOT NULL, \n\tattempt_id VARCHAR(36) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tsource_after BIGINT NOT NULL, \n\tsource_through BIGINT, \n\tbudget_source_through BIGINT, \n\tlegacy_source_through BIGINT, \n\ttarget_source_through BIGINT NOT NULL, \n\tattempts INTEGER NOT NULL, \n\trequests BIGINT NOT NULL, \n\ttokens BIGINT NOT NULL, \n\tlast_error_code VARCHAR(64), \n\terror_code VARCHAR(64) NOT NULL, \n\tblocked_at DATETIME NOT NULL, \n\tretried_at DATETIME, \n\tretry_request_generation BIGINT, \n\tPRIMARY KEY (block_id), \n\tCONSTRAINT ck_pc_topic_block_window CHECK (source_after >= 0 AND target_source_through > source_after AND (source_through IS NULL OR (source_through > source_after AND source_through <= target_source_through)) AND (budget_source_through IS NULL OR (budget_source_through > source_after AND budget_source_through <= target_source_through)) AND (source_through IS NULL OR budget_source_through IS NULL OR source_through <= budget_source_through) AND (legacy_source_through IS NULL OR (legacy_source_through > source_after AND legacy_source_through <= target_source_through))), \n\tCONSTRAINT ck_pc_topic_block_usage CHECK (attempts > 0 AND requests >= 0 AND tokens >= 0), \n\tCONSTRAINT ck_pc_topic_block_acceptance CHECK ((retried_at IS NULL AND retry_request_generation IS NULL) OR (retried_at IS NOT NULL AND retry_request_generation IS NOT NULL AND retry_request_generation > 0)), \n\tCONSTRAINT uq_pc_topic_block_identity UNIQUE (scope_id, binding_name, source_after, attempt_id)\n)" + ], + [ + "table", + "pc_topic_memory_work_budgets", + "pc_topic_memory_work_budgets", + "CREATE TABLE pc_topic_memory_work_budgets (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tsource_after BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tattempt_id VARCHAR(36) NOT NULL, \n\tattempts INTEGER NOT NULL, \n\trequests BIGINT NOT NULL, \n\ttokens BIGINT NOT NULL, \n\tfailure_code VARCHAR(64) NOT NULL, \n\tattempt_source_through BIGINT, \n\tstarted_at DATETIME, \n\tfinished_at DATETIME, \n\tsupervisor_group VARCHAR(64), \n\tsupervisor_generation BIGINT, \n\tholder_id VARCHAR(36), \n\tlast_error_code VARCHAR(64), \n\tPRIMARY KEY (scope_id, binding_name, source_after), \n\tCONSTRAINT ck_pc_topic_budget_window CHECK (source_after >= 0 AND source_through > source_after), \n\tCONSTRAINT ck_pc_topic_budget_usage CHECK (attempts > 0 AND requests >= 0 AND tokens >= 0), \n\tCONSTRAINT ck_pc_topic_budget_attempt_range CHECK (attempt_source_through IS NULL OR (attempt_source_through > source_after AND attempt_source_through <= source_through))\n)" + ], + [ + "trigger", + "pc_atomic_memory_fts_delete", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_delete AFTER DELETE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_fts WHERE identity_token MATCH ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); END" + ], + [ + "trigger", + "pc_atomic_memory_fts_insert", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_insert AFTER INSERT ON pc_atomic_memory_current BEGIN INSERT INTO pc_atomic_memory_current_fts (scope_id, artifact_id, revision, state_version, content_hash, projection_format, kind, text, searchable_text, tag_keys, identity_token) VALUES (new.scope_id, new.artifact_id, new.revision, new.state_version, new.content_hash, new.projection_format, new.kind, new.text, new.searchable_text, new.tag_keys, 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id))); END" + ], + [ + "trigger", + "pc_atomic_memory_fts_update", + "pc_atomic_memory_current", + "CREATE TRIGGER pc_atomic_memory_fts_update AFTER UPDATE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_fts WHERE identity_token MATCH ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); INSERT INTO pc_atomic_memory_current_fts (scope_id, artifact_id, revision, state_version, content_hash, projection_format, kind, text, searchable_text, tag_keys, identity_token) VALUES (new.scope_id, new.artifact_id, new.revision, new.state_version, new.content_hash, new.projection_format, new.kind, new.text, new.searchable_text, new.tag_keys, 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id))); END" + ] + ], + "initialize": [ + "CREATE TABLE pc_access_audit (\n\tcursor INTEGER NOT NULL, \n\tevent_id VARCHAR(64) NOT NULL, \n\toccurred_at VARCHAR(32) NOT NULL, \n\trequest_id VARCHAR(128), \n\ttransport VARCHAR(16) NOT NULL, \n\toperation VARCHAR(128) NOT NULL, \n\tprincipal_type VARCHAR(16) NOT NULL, \n\tprincipal_id VARCHAR(255) NOT NULL, \n\tprincipal_description TEXT, \n\tactor_type VARCHAR(16), \n\tactor_id VARCHAR(255), \n\tactor_description TEXT, \n\taction VARCHAR(64) NOT NULL, \n\tresource_type VARCHAR(16) NOT NULL, \n\tdeployment_id VARCHAR(128), \n\tscope_id VARCHAR(256), \n\tfamily VARCHAR(128), \n\tartifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\tallowed BOOLEAN NOT NULL, \n\treason_code VARCHAR(64) NOT NULL, \n\tpolicy_revision VARCHAR(64), \n\tmatched_subject_type VARCHAR(16), \n\tmatched_subject_id VARCHAR(255), \n\tmatched_subject_description TEXT, \n\tbinding_id VARCHAR(64), \n\ttarget_type VARCHAR(16), \n\ttarget_id VARCHAR(255), \n\ttarget_description TEXT, \n\trole VARCHAR(32), \n\texpected_version INTEGER, \n\tresult_version INTEGER, \n\tPRIMARY KEY (cursor), \n\tUNIQUE (event_id)\n)", + "CREATE TABLE pc_access_idempotency (\n\tactor_id VARCHAR(255) NOT NULL, \n\tidempotency_key_hash VARCHAR(64) NOT NULL, \n\toperation VARCHAR(64) NOT NULL, \n\tpayload_hash VARCHAR(64) NOT NULL, \n\tresult_binding_id VARCHAR(64), \n\tsecondary_binding_id VARCHAR(64), \n\tPRIMARY KEY (actor_id, idempotency_key_hash), \n\tCONSTRAINT uq_pc_access_idempotency_actor_key UNIQUE (actor_id, idempotency_key_hash)\n)", + "CREATE TABLE pc_access_owners (\n\towner_kind VARCHAR(16) NOT NULL, \n\tobject_key_hash VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128), \n\tcandidate_id VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\towner_type VARCHAR(16) NOT NULL, \n\towner_id VARCHAR(255) NOT NULL, \n\towner_description TEXT, \n\testablished_at VARCHAR(32), \n\tpolicy_revision VARCHAR(64), \n\tidempotency_key VARCHAR(255) NOT NULL, \n\tPRIMARY KEY (owner_kind, object_key_hash), \n\tCONSTRAINT ck_pc_access_owners_kind CHECK ((owner_kind = 'artifact' AND artifact_id IS NOT NULL AND candidate_id IS NULL AND target_artifact_id IS NULL AND established_at IS NOT NULL AND policy_revision IS NOT NULL) OR (owner_kind = 'candidate' AND candidate_id IS NOT NULL AND artifact_id IS NULL AND selector_type IS NULL AND selector_entry_id IS NULL AND established_at IS NULL AND policy_revision IS NULL))\n)", + "CREATE TABLE pc_access_relationship_heads (\n\tname VARCHAR(32) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tPRIMARY KEY (name), \n\tCONSTRAINT ck_pc_access_relationship_heads_revision_nonnegative CHECK (revision >= 0)\n)", + "CREATE TABLE pc_access_relationships (\n\tbinding_id VARCHAR(64) NOT NULL, \n\tsubject_type VARCHAR(16) NOT NULL, \n\tsubject_id VARCHAR(255) NOT NULL, \n\tsubject_description TEXT, \n\tresource_key_hash VARCHAR(64) NOT NULL, \n\tresource_type VARCHAR(16) NOT NULL, \n\tdeployment_id VARCHAR(128), \n\tscope_id VARCHAR(256), \n\tfamily VARCHAR(128), \n\tartifact_id VARCHAR(128), \n\tselector_type VARCHAR(32), \n\tselector_entry_id VARCHAR(128), \n\trole VARCHAR(32) NOT NULL, \n\tsingleton_key VARCHAR(64), \n\tgranted_by_type VARCHAR(16) NOT NULL, \n\tgranted_by_id VARCHAR(255) NOT NULL, \n\tgranted_by_description TEXT, \n\treason TEXT, \n\tcreated_at VARCHAR(32) NOT NULL, \n\texpires_at VARCHAR(32), \n\tstate VARCHAR(16) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tpolicy_revision VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(255) NOT NULL, \n\trevoked_at VARCHAR(32), \n\trevoked_by_type VARCHAR(16), \n\trevoked_by_id VARCHAR(255), \n\trevoked_by_description TEXT, \n\tPRIMARY KEY (binding_id), \n\tCONSTRAINT ck_pc_access_relationships_version_positive CHECK (version > 0), \n\tUNIQUE (singleton_key)\n)", + "CREATE TABLE pc_agent_skill_targets (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_id VARCHAR(64) NOT NULL, \n\tdisplay_name VARCHAR(128) NOT NULL, \n\tagent_kind VARCHAR(32) NOT NULL, \n\tinstallation_scope VARCHAR(16) NOT NULL, \n\tdelivery_mode VARCHAR(16) NOT NULL, \n\tinstallation_id VARCHAR(128), \n\tstate VARCHAR(16) NOT NULL, \n\tenrollment_token_digest VARCHAR(64), \n\tenrollment_expires_at DATETIME, \n\tcredential_subject VARCHAR(128), \n\tcredential_verifier VARCHAR(64), \n\treceiver_version VARCHAR(64), \n\tenvironment_fingerprint VARCHAR(64), \n\tmachine_hostname VARCHAR(255), \n\tworkspace_name VARCHAR(128), \n\tlast_seen_at DATETIME, \n\tgeneration BIGINT NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, target_id), \n\tCONSTRAINT uq_pc_agent_skill_targets_installation UNIQUE (scope_id, agent_kind, installation_scope, installation_id), \n\tCONSTRAINT uq_pc_agent_skill_targets_enrollment_token UNIQUE (enrollment_token_digest), \n\tCONSTRAINT uq_pc_agent_skill_targets_credential_subject UNIQUE (credential_subject), \n\tCONSTRAINT uq_pc_agent_skill_targets_credential_verifier UNIQUE (credential_verifier), \n\tCONSTRAINT ck_pc_agent_skill_targets_agent_kind CHECK (agent_kind IN ('codex', 'claude_code')), \n\tCONSTRAINT ck_pc_agent_skill_targets_installation_scope CHECK (installation_scope IN ('project')), \n\tCONSTRAINT ck_pc_agent_skill_targets_delivery_mode CHECK (delivery_mode = 'agent_pull'), \n\tCONSTRAINT ck_pc_agent_skill_targets_state CHECK (state IN ('pending', 'active', 'revoked')), \n\tCONSTRAINT ck_pc_agent_skill_targets_state_payload CHECK ((state = 'pending' AND enrollment_token_digest IS NOT NULL AND enrollment_expires_at IS NOT NULL AND installation_id IS NULL AND credential_subject IS NULL AND credential_verifier IS NULL) OR (state = 'active' AND enrollment_token_digest IS NULL AND enrollment_expires_at IS NULL AND installation_id IS NOT NULL AND credential_subject IS NOT NULL AND credential_verifier IS NOT NULL) OR (state = 'revoked' AND enrollment_token_digest IS NULL AND enrollment_expires_at IS NULL AND credential_verifier IS NULL)), \n\tCONSTRAINT ck_pc_agent_skill_targets_generation_nonnegative CHECK (generation >= 0)\n)", + "CREATE TABLE pc_artifact_candidate_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\tresult_family VARCHAR(128), \n\tresult_artifact_id VARCHAR(128), \n\tresult_revision INTEGER, \n\tdecision_reason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id), \n\tFOREIGN KEY(scope_id, candidate_id, version) REFERENCES pc_artifact_candidate_versions (scope_id, candidate_id, version) ON DELETE RESTRICT, \n\tFOREIGN KEY(scope_id, result_family, result_artifact_id, result_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_heads_status CHECK (status IN ('pending', 'approved', 'rejected')), \n\tCONSTRAINT ck_pc_artifact_candidate_heads_terminal_result CHECK ((status = 'approved' AND result_family IS NOT NULL AND result_artifact_id IS NOT NULL AND result_revision > 0 AND decision_reason IS NULL) OR (status = 'rejected' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NOT NULL) OR (status = 'pending' AND result_family IS NULL AND result_artifact_id IS NULL AND result_revision IS NULL AND decision_reason IS NULL))\n)", + "CREATE TABLE pc_artifact_candidate_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tcandidate_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tproposal BLOB NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\treason TEXT, \n\tPRIMARY KEY (scope_id, candidate_id, version), \n\tFOREIGN KEY(scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_candidate_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_artifact_candidate_versions_target_complete CHECK ((target_family IS NULL AND target_artifact_id IS NULL AND target_revision IS NULL) OR (target_family IS NOT NULL AND target_artifact_id IS NOT NULL AND target_revision > 0))\n)", + "CREATE VIRTUAL TABLE pc_artifact_fts USING fts5(\n scope_id UNINDEXED,\n family UNINDEXED,\n artifact_id UNINDEXED,\n revision UNINDEXED,\n searchable_text,\n tokenize='unicode61'\n)", + "CREATE TABLE pc_artifact_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsearchable_text TEXT, \n\tlifecycle_state VARCHAR(16) DEFAULT 'active' NOT NULL, \n\treplacement_artifact_id VARCHAR(128), \n\tmerged_into_id VARCHAR(128), \n\tgovernance_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_artifact_heads_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_artifact_heads_lifecycle_state CHECK (lifecycle_state IN ('active', 'deprecated', 'retired')), \n\tCONSTRAINT ck_pc_artifact_heads_governance_generation_nonnegative CHECK (governance_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_heads_replacement_deprecated CHECK (replacement_artifact_id IS NULL OR lifecycle_state = 'deprecated'), \n\tCONSTRAINT ck_pc_artifact_heads_merge_target CHECK (merged_into_id IS NULL OR (lifecycle_state = 'deprecated' AND merged_into_id <> artifact_id AND replacement_artifact_id IS NULL))\n)", + "CREATE TABLE pc_artifact_lineage_artifacts (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tupstream_family VARCHAR(128) NOT NULL, \n\tupstream_artifact_id VARCHAR(128) NOT NULL, \n\tupstream_revision INTEGER NOT NULL, \n\tis_merge_input BOOLEAN DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision, ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, upstream_family, upstream_artifact_id, upstream_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT\n)", + "CREATE TABLE pc_artifact_lineage_sources (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tsource_type VARCHAR(128) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision, ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, source_type, source_id) REFERENCES pc_sources (scope_id, source_type, source_id) ON DELETE RESTRICT\n)", + "CREATE TABLE pc_artifact_processing_auto_wave_targets (\n\twave_id VARCHAR(36) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tcompleted BOOLEAN DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (wave_id, binding_name, scope_id), \n\tCONSTRAINT ck_pc_artifact_processing_auto_wave_target_source_positive CHECK (source_through >= 1)\n)", + "CREATE TABLE pc_artifact_processing_binding_states (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tlast_auto_wave_completed_at DATETIME, \n\tlast_schedule_checkpoint_at DATETIME, \n\tscan_generation BIGINT DEFAULT '0' NOT NULL, \n\tscan_in_progress BOOLEAN DEFAULT '0' NOT NULL, \n\tscan_upper_pending_sequence BIGINT, \n\tPRIMARY KEY (binding_name), \n\tCONSTRAINT ck_pc_processing_scan_generation CHECK (scan_generation >= 0)\n)", + "CREATE TABLE pc_artifact_processing_intents (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tpending_sequence BIGINT NOT NULL, \n\tdirty_generation BIGINT DEFAULT '0' NOT NULL, \n\tclean_generation BIGINT DEFAULT '0' NOT NULL, \n\trequested_generation BIGINT DEFAULT '0' NOT NULL, \n\thandled_generation BIGINT DEFAULT '0' NOT NULL, \n\tlast_auto_scan_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_processing_intent_sequence CHECK (pending_sequence > 0), \n\tCONSTRAINT ck_pc_processing_intent_dirty CHECK (clean_generation >= 0 AND clean_generation <= dirty_generation), \n\tCONSTRAINT ck_pc_processing_intent_request CHECK (handled_generation >= 0 AND handled_generation <= requested_generation), \n\tCONSTRAINT ck_pc_processing_intent_scan CHECK (last_auto_scan_generation >= 0), \n\tUNIQUE (pending_sequence)\n)", + "CREATE TABLE pc_artifact_processing_leases (\n\tsupervisor_group VARCHAR(64) NOT NULL, \n\tholder_id VARCHAR(36) NOT NULL, \n\tsupervisor_generation BIGINT NOT NULL, \n\tlease_expires_at DATETIME, \n\tPRIMARY KEY (supervisor_group), \n\tCONSTRAINT ck_pc_artifact_processing_leases_generation_positive CHECK (supervisor_generation > 0)\n)", + "CREATE TABLE pc_artifact_processing_migration_receipts (\n\tmigration_id VARCHAR(64) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_snapshot BLOB NOT NULL, \n\trequested_generation BIGINT NOT NULL, \n\tdirty_generation BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tPRIMARY KEY (migration_id, binding_name, scope_id)\n)", + "CREATE TABLE pc_artifact_processing_pending (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tflush_generation BIGINT DEFAULT '0' NOT NULL, \n\thandled_flush_generation BIGINT DEFAULT '0' NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_artifact_processing_pending_source_positive CHECK (source_through >= 1), \n\tCONSTRAINT ck_pc_artifact_processing_pending_flush_nonnegative CHECK (flush_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_processing_pending_handled_nonnegative CHECK (handled_flush_generation >= 0), \n\tCONSTRAINT ck_pc_artifact_processing_pending_handled_not_ahead CHECK (handled_flush_generation <= flush_generation)\n)", + "CREATE TABLE pc_artifact_processing_schema (\n\tsingleton INTEGER NOT NULL, \n\tschema_version INTEGER NOT NULL, \n\tsource_version INTEGER DEFAULT '1490' NOT NULL, \n\tphase VARCHAR(32) NOT NULL, \n\tmigration_id VARCHAR(64) NOT NULL, \n\tconfig_manifest TEXT NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_processing_schema_singleton CHECK (singleton = 1)\n)", + "CREATE TABLE pc_artifact_processing_sequences (\n\tsingleton INTEGER NOT NULL, \n\tsequence BIGINT NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_processing_sequence CHECK (singleton = 1 AND sequence >= 0)\n)", + "CREATE TABLE pc_artifact_publications (\n\ttarget_scope_id VARCHAR(256) NOT NULL, \n\ttarget_family VARCHAR(128) NOT NULL, \n\ttarget_artifact_id VARCHAR(128) NOT NULL, \n\ttarget_revision INTEGER NOT NULL, \n\tsource_scope_id VARCHAR(256) NOT NULL, \n\tsource_family VARCHAR(128) NOT NULL, \n\tsource_artifact_id VARCHAR(128) NOT NULL, \n\tsource_revision INTEGER NOT NULL, \n\tcontent_digest VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (target_scope_id, target_family, target_artifact_id, target_revision), \n\tFOREIGN KEY(target_scope_id, target_family, target_artifact_id, target_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tFOREIGN KEY(source_scope_id, source_family, source_artifact_id, source_revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT uq_pc_artifact_publications_request UNIQUE (target_scope_id, idempotency_key)\n)", + "CREATE TABLE pc_artifact_tags (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\ttarget_type VARCHAR(12) NOT NULL, \n\ttarget_id VARCHAR(128) NOT NULL, \n\ttag_key_hash BLOB NOT NULL, \n\ttag_key VARCHAR(128) NOT NULL, \n\ttag VARCHAR(64) NOT NULL, \n\tassigned_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, target_type, target_id, tag_key_hash), \n\tFOREIGN KEY(scope_id, family, artifact_id) REFERENCES pc_artifact_heads (scope_id, family, artifact_id) ON DELETE CASCADE, \n\tCONSTRAINT ck_pc_artifact_tags_target CHECK ((target_type = 'artifact' AND target_id = artifact_id) OR (target_type = 'memory_entry' AND family = 'memory'))\n)", + "CREATE TABLE pc_artifacts (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tcontent BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision)\n)", + "CREATE TABLE pc_atomic_memory_current (\n\tscope_id VARCHAR(256) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision BIGINT NOT NULL, \n\tstate_version BIGINT NOT NULL, \n\tcontent_hash VARCHAR(64) NOT NULL, \n\tprojection_format VARCHAR(64) NOT NULL, \n\tkind TEXT NOT NULL, \n\ttext TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\ttag_keys TEXT NOT NULL, \n\tembedding BLOB, \n\tprofile_fingerprint VARCHAR(64), \n\tembedding_input_hash VARCHAR(64), \n\tPRIMARY KEY (scope_id, artifact_id), \n\tCONSTRAINT ck_pc_atomic_memory_current_revision CHECK (revision > 0), \n\tCONSTRAINT ck_pc_atomic_memory_current_state_version CHECK (state_version >= 0), \n\tCONSTRAINT ck_pc_atomic_memory_current_embedding_metadata CHECK ((embedding IS NULL AND profile_fingerprint IS NULL AND embedding_input_hash IS NULL) OR (embedding IS NOT NULL AND profile_fingerprint IS NOT NULL AND embedding_input_hash IS NOT NULL))\n)", + "CREATE VIRTUAL TABLE pc_atomic_memory_current_fts USING fts5(scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED, state_version UNINDEXED, content_hash UNINDEXED, projection_format UNINDEXED, kind UNINDEXED, text UNINDEXED, searchable_text, tag_keys UNINDEXED, identity_token, tokenize='unicode61')", + "CREATE TABLE pc_connector_checkpoints (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_id VARCHAR(256) NOT NULL, \n\tconnector_name VARCHAR(128) NOT NULL, \n\tconnector_version VARCHAR(128) NOT NULL, \n\tcheckpoint BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, binding_id)\n)", + "CREATE TABLE pc_decision_observations (\n\tscope_id VARCHAR(256) NOT NULL, \n\tobservation_id VARCHAR(128) NOT NULL, \n\toperation_id VARCHAR(256) NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tconsumer VARCHAR(128) NOT NULL, \n\tpolicy_id VARCHAR(256) NOT NULL, \n\tpolicy_version VARCHAR(64) NOT NULL, \n\tmode VARCHAR(16) NOT NULL, \n\tprivacy_boundary VARCHAR(32) NOT NULL, \n\tcoverage VARCHAR(16) NOT NULL, \n\tverdict VARCHAR(16) NOT NULL, \n\tassessment_source VARCHAR(32) NOT NULL, \n\tfinal_action VARCHAR(128) NOT NULL, \n\tused_fallback BOOLEAN NOT NULL, \n\tfallback_reason VARCHAR(128), \n\tprovider_id VARCHAR(128), \n\tbackend_model_id VARCHAR(256), \n\tmodel_policy_id VARCHAR(256), \n\trequests BIGINT NOT NULL, \n\tinput_tokens BIGINT, \n\toutput_tokens BIGINT, \n\tlatency_ms FLOAT, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, observation_id), \n\tCONSTRAINT ck_pc_decision_observations_requests_nonnegative CHECK (requests >= 0), \n\tCONSTRAINT ck_pc_decision_observations_input_tokens_nonnegative CHECK (input_tokens IS NULL OR input_tokens >= 0), \n\tCONSTRAINT ck_pc_decision_observations_output_tokens_nonnegative CHECK (output_tokens IS NULL OR output_tokens >= 0), \n\tCONSTRAINT ck_pc_decision_observations_latency_nonnegative CHECK (latency_ms IS NULL OR latency_ms >= 0)\n)", + "CREATE TABLE pc_dream_runs (\n\tscope_id VARCHAR(256) NOT NULL, \n\trun_id VARCHAR(64) NOT NULL, \n\tprincipal_key VARCHAR(64) NOT NULL, \n\tidempotency_key VARCHAR(128) NOT NULL, \n\trequest_digest VARCHAR(71) NOT NULL, \n\toperation VARCHAR(32) NOT NULL, \n\tstatus VARCHAR(16) NOT NULL, \n\taccepted_at BIGINT NOT NULL, \n\tgeneration INTEGER NOT NULL, \n\trequest_generation BIGINT NOT NULL, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, run_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_dream_idempotency UNIQUE (scope_id, principal_key, idempotency_key), \n\tCONSTRAINT ck_pc_dream_generation CHECK (generation >= 0)\n)", + "CREATE TABLE pc_external_skill_registrations (\n\tscope_id VARCHAR(256) NOT NULL, \n\texternal_skill_id VARCHAR(128) NOT NULL, \n\tprovider VARCHAR(128) NOT NULL, \n\tagent_kind VARCHAR(128) NOT NULL, \n\thost_id VARCHAR(128) NOT NULL, \n\tinstallation_scope VARCHAR(16) NOT NULL, \n\tlocator VARCHAR(2000) NOT NULL, \n\tlocator_hash VARCHAR(64) NOT NULL, \n\tfingerprint VARCHAR(64) NOT NULL, \n\tname VARCHAR(128) NOT NULL, \n\tdescription VARCHAR(2000) NOT NULL, \n\tPRIMARY KEY (scope_id, external_skill_id), \n\tCONSTRAINT uq_pc_external_skill_registrations_binding UNIQUE (scope_id, provider, host_id, installation_scope, locator_hash), \n\tCONSTRAINT ck_pc_external_skill_registrations_scope CHECK (installation_scope IN ('user', 'project', 'plugin'))\n)", + "CREATE TABLE pc_memory_entry_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tmemory_artifact_id VARCHAR(128) NOT NULL, \n\thead_revision INTEGER NOT NULL, \n\tentry_id VARCHAR(128) NOT NULL, \n\tentry_version_id VARCHAR(128) NOT NULL, \n\tentry_content_hash VARCHAR(64) NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tPRIMARY KEY (scope_id, memory_artifact_id, entry_id), \n\tFOREIGN KEY(scope_id, memory_artifact_id, entry_id, entry_version_id) REFERENCES pc_memory_entry_versions (scope_id, memory_artifact_id, entry_id, entry_version_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_memory_entry_heads_revision_positive CHECK (head_revision > 0)\n)", + "CREATE TABLE pc_memory_entry_versions (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tmemory_artifact_id VARCHAR(128) NOT NULL, \n\tentry_id VARCHAR(128) NOT NULL, \n\tentry_version_id VARCHAR(128) NOT NULL, \n\tversion INTEGER NOT NULL, \n\tprevious_version_id VARCHAR(128), \n\tkind VARCHAR(128) NOT NULL, \n\ttext TEXT NOT NULL, \n\tsource_refs BLOB NOT NULL, \n\tartifact_refs BLOB NOT NULL, \n\tentry_content_hash VARCHAR(64) NOT NULL, \n\tcreated_in_revision INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, memory_artifact_id, entry_version_id), \n\tCONSTRAINT uq_pc_memory_entry_versions_logical_version UNIQUE (scope_id, memory_artifact_id, entry_id, version), \n\tCONSTRAINT uq_pc_memory_entry_versions_identity UNIQUE (scope_id, memory_artifact_id, entry_id, entry_version_id), \n\tCONSTRAINT ck_pc_memory_entry_versions_version_positive CHECK (version > 0), \n\tCONSTRAINT ck_pc_memory_entry_versions_revision_positive CHECK (created_in_revision > 0)\n)", + "CREATE TABLE pc_memory_source_windows (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\twindow_limit BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tCONSTRAINT ck_pc_memory_source_window_positive CHECK (source_through > 0 AND window_limit > 0)\n)", + "CREATE TABLE pc_model_usage_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\tpurpose VARCHAR(64) NOT NULL, \n\toperation VARCHAR(16) NOT NULL, \n\trequests BIGINT NOT NULL, \n\tinput_tokens BIGINT NOT NULL, \n\toutput_tokens BIGINT NOT NULL, \n\tinput_complete BOOLEAN NOT NULL, \n\toutput_complete BOOLEAN NOT NULL, \n\tPRIMARY KEY (scope_id, usage_date, purpose, operation), \n\tCONSTRAINT ck_pc_model_usage_daily_operation CHECK (operation IN ('generation', 'embedding')), \n\tCONSTRAINT ck_pc_model_usage_daily_requests_nonnegative CHECK (requests >= 0), \n\tCONSTRAINT ck_pc_model_usage_daily_input_nonnegative CHECK (input_tokens >= 0), \n\tCONSTRAINT ck_pc_model_usage_daily_output_nonnegative CHECK (output_tokens >= 0)\n)", + "CREATE TABLE pc_profile_policies (\n\tscope_id VARCHAR(256) NOT NULL, \n\tgeneration_enabled BOOLEAN NOT NULL, \n\tactivation_mode VARCHAR(32) NOT NULL, \n\tpending_candidate_id VARCHAR(128), \n\tversion BIGINT NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(scope_id, pending_candidate_id) REFERENCES pc_artifact_candidate_heads (scope_id, candidate_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_profile_policies_activation_mode CHECK (activation_mode IN ('automatic', 'review_required')), \n\tCONSTRAINT ck_pc_profile_policies_version_positive CHECK (version > 0)\n)", + "CREATE TABLE pc_recall_effort_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\tpolicy_id VARCHAR(128) NOT NULL, \n\tassessment VARCHAR(64) NOT NULL, \n\tpreparations BIGINT NOT NULL CONSTRAINT ck_recall_effort_preparations_nonnegative CHECK (preparations >= 0), \n\trounds BIGINT NOT NULL CONSTRAINT ck_recall_effort_rounds_nonnegative CHECK (rounds >= 0), \n\texpanded_preparations BIGINT NOT NULL CONSTRAINT ck_recall_effort_expanded_preparations_nonnegative CHECK (expanded_preparations >= 0), \n\tadmission_expansions BIGINT NOT NULL CONSTRAINT ck_recall_effort_admission_expansions_nonnegative CHECK (admission_expansions >= 0), \n\tpolicy_floor_expansions BIGINT NOT NULL CONSTRAINT ck_recall_effort_policy_floor_expansions_nonnegative CHECK (policy_floor_expansions >= 0), \n\tcandidate_round_samples BIGINT NOT NULL CONSTRAINT ck_recall_effort_candidate_round_samples_nonnegative CHECK (candidate_round_samples >= 0), \n\tcandidates_assessed BIGINT NOT NULL CONSTRAINT ck_recall_effort_candidates_assessed_nonnegative CHECK (candidates_assessed >= 0), \n\tfinal_candidate_pool BIGINT NOT NULL CONSTRAINT ck_recall_effort_final_candidate_pool_nonnegative CHECK (final_candidate_pool >= 0), \n\tadded_embeddings BIGINT NOT NULL CONSTRAINT ck_recall_effort_added_embeddings_nonnegative CHECK (added_embeddings >= 0), \n\tadded_generation_calls BIGINT NOT NULL CONSTRAINT ck_recall_effort_added_generation_calls_nonnegative CHECK (added_generation_calls >= 0), \n\ttruncated_items BIGINT NOT NULL CONSTRAINT ck_recall_effort_truncated_items_nonnegative CHECK (truncated_items >= 0), \n\tdropped_items BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_items_nonnegative CHECK (dropped_items >= 0), \n\tdropped_below_min_bytes BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_below_min_bytes_nonnegative CHECK (dropped_below_min_bytes >= 0), \n\tdropped_no_fitting_truncation BIGINT NOT NULL CONSTRAINT ck_recall_effort_dropped_no_fitting_truncation_nonnegative CHECK (dropped_no_fitting_truncation >= 0), \n\tPRIMARY KEY (scope_id, usage_date, policy_id, assessment), \n\tCONSTRAINT ck_pc_recall_effort_daily_drop_total CHECK (dropped_items = dropped_below_min_bytes + dropped_no_fitting_truncation)\n)", + "CREATE TABLE pc_recall_token_daily (\n\tscope_id VARCHAR(256) NOT NULL, \n\tusage_date DATE NOT NULL, \n\testimator_id VARCHAR(128) NOT NULL, \n\testimator_version VARCHAR(64) NOT NULL, \n\tpreparations BIGINT NOT NULL, \n\tready_preparations BIGINT NOT NULL, \n\tcomparable_preparations BIGINT NOT NULL, \n\tbaseline_tokens BIGINT NOT NULL, \n\trecalled_tokens BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, usage_date, estimator_id, estimator_version), \n\tCONSTRAINT ck_pc_recall_token_daily_preparations_nonnegative CHECK (preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_ready_nonnegative CHECK (ready_preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_comparable_nonnegative CHECK (comparable_preparations >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_comparable_ready CHECK (comparable_preparations <= ready_preparations), \n\tCONSTRAINT ck_pc_recall_token_daily_ready_total CHECK (ready_preparations <= preparations), \n\tCONSTRAINT ck_pc_recall_token_daily_baseline_nonnegative CHECK (baseline_tokens >= 0), \n\tCONSTRAINT ck_pc_recall_token_daily_recalled_nonnegative CHECK (recalled_tokens >= 0)\n)", + "CREATE TABLE pc_receipt_migration_review (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\treason VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, source_id)\n)", + "CREATE TABLE pc_recurrence_match (\n\tscope_id VARCHAR(256) NOT NULL, \n\tmatch_key VARCHAR(71) NOT NULL, \n\ttask_outcome_source_type VARCHAR(128) NOT NULL, \n\ttask_outcome_source_id VARCHAR(256) NOT NULL, \n\ttask_outcome_position BIGINT NOT NULL, \n\tfailure_item_kind VARCHAR(16) NOT NULL, \n\tfailure_item_index INTEGER NOT NULL, \n\tfailure_item_digest VARCHAR(71) NOT NULL, \n\tcandidate_set_mode VARCHAR(32) NOT NULL, \n\tcandidate_set_digest VARCHAR(71) NOT NULL, \n\tresult VARCHAR(16) NOT NULL, \n\ttarget_family VARCHAR(128), \n\ttarget_artifact_id VARCHAR(128), \n\ttarget_revision INTEGER, \n\tsignature_key TEXT, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, match_key), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_recurrence_match_identity UNIQUE (scope_id, task_outcome_source_type, task_outcome_source_id, failure_item_kind, failure_item_index), \n\tCONSTRAINT ck_pc_recurrence_match_result CHECK (result IN ('matched', 'unmatched', 'ambiguous')), \n\tCONSTRAINT ck_pc_recurrence_match_candidate_set_mode CHECK (candidate_set_mode IN ('handoff_citations', 'scope_heads')), \n\tCONSTRAINT ck_pc_recurrence_match_failure_item_kind CHECK (failure_item_kind IN ('observation', 'check')), \n\tCONSTRAINT ck_pc_recurrence_match_position_positive CHECK (task_outcome_position > 0), \n\tCONSTRAINT ck_pc_recurrence_match_item_index_nonnegative CHECK (failure_item_index >= 0)\n)", + "CREATE TABLE pc_recurrence_observation (\n\tscope_id VARCHAR(256) NOT NULL, \n\tobservation_id VARCHAR(71) NOT NULL, \n\tselection_key VARCHAR(71) NOT NULL, \n\tverdict_key VARCHAR(71) NOT NULL, \n\tevent VARCHAR(16) NOT NULL, \n\tmatch_basis VARCHAR(8) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tsignature_key TEXT NOT NULL, \n\tsignature_key_hash BLOB NOT NULL, \n\ttask_outcome_source_type VARCHAR(128) NOT NULL, \n\ttask_outcome_source_id VARCHAR(256) NOT NULL, \n\ttask_outcome_position BIGINT NOT NULL, \n\tpayload BLOB NOT NULL, \n\tPRIMARY KEY (scope_id, observation_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_recurrence_observation_selection UNIQUE (scope_id, selection_key), \n\tCONSTRAINT uq_pc_recurrence_observation_verdict UNIQUE (scope_id, verdict_key), \n\tCONSTRAINT ck_pc_recurrence_observation_event CHECK (event IN ('selected', 'recurred', 'avoided')), \n\tCONSTRAINT ck_pc_recurrence_observation_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_recurrence_observation_position_positive CHECK (task_outcome_position > 0)\n)", + "CREATE TABLE pc_scope_bindings (\n\tintegration VARCHAR(128) NOT NULL, \n\tkind VARCHAR(64) NOT NULL, \n\texternal_id VARCHAR(256) NOT NULL, \n\texternal_id_search TEXT NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (integration, kind, external_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)", + "CREATE TABLE pc_scope_context_references (\n\tscope_id VARCHAR(256) NOT NULL, \n\treferenced_scope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (scope_id, referenced_scope_id), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tFOREIGN KEY(referenced_scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_scope_context_references_not_self CHECK (scope_id <> referenced_scope_id)\n)", + "CREATE TABLE pc_scope_creation_requests (\n\tidempotency_key VARCHAR(256) NOT NULL, \n\trequest_digest VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (idempotency_key), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)", + "CREATE TABLE pc_scope_external_references (\n\tscope_id VARCHAR(256) NOT NULL, \n\tordinal INTEGER NOT NULL, \n\tkind VARCHAR(128) NOT NULL, \n\tvalue VARCHAR(2000) NOT NULL, \n\tvalue_search TEXT NOT NULL, \n\tvalue_digest VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, ordinal), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE CASCADE, \n\tCONSTRAINT uq_pc_scope_external_references_value UNIQUE (scope_id, kind, value_digest), \n\tCONSTRAINT ck_pc_scope_external_references_ordinal_nonnegative CHECK (ordinal >= 0)\n)", + "CREATE TABLE pc_scope_settings (\n\tname VARCHAR(64) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\tPRIMARY KEY (name), \n\tFOREIGN KEY(scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT\n)", + "CREATE TABLE pc_scopes (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttitle VARCHAR(256) NOT NULL, \n\tsummary VARCHAR(2000) NOT NULL, \n\tscope_id_search TEXT NOT NULL, \n\ttitle_search TEXT NOT NULL, \n\tsummary_search TEXT NOT NULL, \n\tparent_scope_id VARCHAR(256), \n\tversion INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tFOREIGN KEY(parent_scope_id) REFERENCES pc_scopes (scope_id) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_scopes_version_positive CHECK (version > 0)\n)", + "CREATE TABLE pc_server_identity (\n\tsingleton_key INTEGER NOT NULL, \n\tserver_id VARCHAR(36) NOT NULL, \n\tPRIMARY KEY (singleton_key), \n\tCONSTRAINT ck_pc_server_identity_singleton CHECK (singleton_key = 1), \n\tUNIQUE (server_id)\n)", + "CREATE TABLE pc_skill_packages (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttree_digest VARCHAR(64) NOT NULL, \n\tarchive_digest VARCHAR(64) NOT NULL, \n\tarchive_bytes BLOB NOT NULL, \n\tmanifest BLOB NOT NULL, \n\tfile_count INTEGER NOT NULL, \n\tuncompressed_size BIGINT NOT NULL, \n\tarchive_size BIGINT NOT NULL, \n\tcreated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, tree_digest), \n\tCONSTRAINT ck_pc_skill_packages_file_count CHECK (file_count > 0 AND file_count <= 256), \n\tCONSTRAINT ck_pc_skill_packages_uncompressed_size CHECK (uncompressed_size > 0 AND uncompressed_size <= 4194304), \n\tCONSTRAINT ck_pc_skill_packages_archive_size CHECK (archive_size > 0 AND archive_size <= 5242880)\n)", + "CREATE TABLE pc_skill_publications (\n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_id VARCHAR(64) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\tdesired_state VARCHAR(16) DEFAULT 'published' NOT NULL, \n\tdesired_revision INTEGER NOT NULL, \n\tdesired_tree_digest VARCHAR(64) NOT NULL, \n\tobserved_revision INTEGER, \n\tobserved_tree_digest VARCHAR(64), \n\tobserved_generation BIGINT, \n\tdestination TEXT, \n\tstate VARCHAR(32) NOT NULL, \n\tselected_runtime_variant VARCHAR(128), \n\tenvironment_fingerprint VARCHAR(64), \n\tlast_error_code VARCHAR(128), \n\tobserved_at DATETIME, \n\tgeneration BIGINT NOT NULL, \n\tupdated_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, target_id, artifact_id), \n\tCONSTRAINT ck_pc_skill_publications_desired_revision_positive CHECK (desired_revision > 0), \n\tCONSTRAINT ck_pc_skill_publications_observed_revision_positive CHECK (observed_revision IS NULL OR observed_revision > 0), \n\tCONSTRAINT ck_pc_skill_publications_desired_state CHECK (desired_state IN ('published', 'unpublished')), \n\tCONSTRAINT ck_pc_skill_publications_state CHECK (state IN ('unpublished', 'pending', 'current', 'update_available', 'delivery_failed', 'conflict', 'drifted', 'incompatible')), \n\tCONSTRAINT ck_pc_skill_publications_observed_generation_nonnegative CHECK (observed_generation IS NULL OR observed_generation >= 0), \n\tCONSTRAINT ck_pc_skill_publications_generation_nonnegative CHECK (generation >= 0)\n)", + "CREATE TABLE pc_source_cursors (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tcursor BLOB NOT NULL, \n\tgeneration BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, binding_name), \n\tCONSTRAINT ck_pc_source_cursors_generation_nonnegative CHECK (generation >= 0)\n)", + "CREATE TABLE pc_source_definition_manifests (\n\tdefinition_name VARCHAR(128) NOT NULL, \n\tdefinition_version VARCHAR(128) NOT NULL, \n\tfingerprint VARCHAR(71) NOT NULL, \n\tmanifest BLOB NOT NULL, \n\tPRIMARY KEY (definition_name, definition_version), \n\tCONSTRAINT uq_pc_source_definition_manifest_fingerprint UNIQUE (definition_name, fingerprint)\n)", + "CREATE TABLE pc_source_journal_heads (\n\tscope_id VARCHAR(256) NOT NULL, \n\tposition BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id), \n\tCONSTRAINT ck_pc_source_journal_heads_position_nonnegative CHECK (position >= 0)\n)", + "CREATE TABLE pc_sources (\n\tscope_id VARCHAR(256) NOT NULL, \n\tsource_type VARCHAR(128) NOT NULL, \n\tsource_id VARCHAR(256) NOT NULL, \n\tpayload BLOB NOT NULL, \n\tjournal_position BIGINT NOT NULL, \n\tPRIMARY KEY (scope_id, source_type, source_id), \n\tCONSTRAINT uq_pc_sources_scope_journal_position UNIQUE (scope_id, journal_position)\n)", + "CREATE TABLE pc_topic_memory_active_chunks (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tchunk_ordinal INTEGER NOT NULL, \n\tstart_offset INTEGER NOT NULL, \n\tend_offset INTEGER NOT NULL, \n\tchunk_text TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tpolicy_version VARCHAR(32) NOT NULL, \n\tPRIMARY KEY (scope_id, artifact_id, chunk_ordinal), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_topic_memory_revision_publications (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_ordinal_nonnegative CHECK (chunk_ordinal >= 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_start_nonnegative CHECK (start_offset >= 0), \n\tCONSTRAINT ck_pc_topic_memory_active_chunks_offsets CHECK (end_offset > start_offset)\n)", + "CREATE TABLE pc_topic_memory_active_topics (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\ttitle TEXT NOT NULL, \n\tsummary TEXT NOT NULL, \n\tsearchable_text TEXT NOT NULL, \n\tsource_count INTEGER NOT NULL, \n\tPRIMARY KEY (scope_id, artifact_id), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_topic_memory_revision_publications (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_active_topics_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_active_topics_revision_positive CHECK (revision > 0), \n\tCONSTRAINT ck_pc_topic_memory_active_topics_sources_nonnegative CHECK (source_count >= 0)\n)", + "CREATE VIRTUAL TABLE pc_topic_memory_chunk_fts USING fts5(\n scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED,\n chunk_ordinal UNINDEXED, start_offset UNINDEXED,\n title UNINDEXED, summary UNINDEXED, chunk_text UNINDEXED,\n searchable_text, tokenize='unicode61'\n)", + "CREATE TABLE pc_topic_memory_fts_index (\n\tsingleton INTEGER NOT NULL, \n\tschema_version INTEGER NOT NULL, \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_topic_memory_fts_index_singleton CHECK (singleton = 1)\n)", + "CREATE TABLE pc_topic_memory_processing_targets (\n\tbinding_name VARCHAR(128) NOT NULL, \n\tscope_id VARCHAR(256) NOT NULL, \n\ttarget_request_generation BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tcaptured_flush_generation BIGINT NOT NULL, \n\tobserved_dirty_generation BIGINT NOT NULL, \n\tPRIMARY KEY (binding_name, scope_id), \n\tCONSTRAINT ck_pc_topic_processing_target CHECK (target_request_generation > 0 AND source_through >= 0 AND captured_flush_generation >= 0 AND observed_dirty_generation >= 0)\n)", + "CREATE TABLE pc_topic_memory_retrieval_shape (\n\tsingleton INTEGER NOT NULL, \n\tshape VARCHAR(16) NOT NULL, \n\tprofile_fingerprint VARCHAR(64), \n\tPRIMARY KEY (singleton), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_singleton CHECK (singleton = 1), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_value CHECK (shape IN ('fts', 'hybrid')), \n\tCONSTRAINT ck_pc_topic_memory_retrieval_shape_profile CHECK ((shape = 'fts' AND profile_fingerprint IS NULL) OR (shape = 'hybrid' AND profile_fingerprint IS NOT NULL))\n)", + "CREATE TABLE pc_topic_memory_revision_publications (\n\tscope_id VARCHAR(256) NOT NULL, \n\tfamily VARCHAR(128) NOT NULL, \n\tartifact_id VARCHAR(128) NOT NULL, \n\trevision INTEGER NOT NULL, \n\tpublished_at DATETIME NOT NULL, \n\tPRIMARY KEY (scope_id, family, artifact_id, revision), \n\tFOREIGN KEY(scope_id, family, artifact_id, revision) REFERENCES pc_artifacts (scope_id, family, artifact_id, revision) ON DELETE RESTRICT, \n\tCONSTRAINT ck_pc_topic_memory_publications_family CHECK (family = 'topic-memory'), \n\tCONSTRAINT ck_pc_topic_memory_publications_revision_positive CHECK (revision > 0)\n)", + "CREATE VIRTUAL TABLE pc_topic_memory_topic_fts USING fts5(\n scope_id UNINDEXED, artifact_id UNINDEXED, revision UNINDEXED,\n title UNINDEXED, summary UNINDEXED, searchable_text, tokenize='unicode61'\n)", + "CREATE TABLE pc_topic_memory_work_budgets (\n\tscope_id VARCHAR(256) NOT NULL, \n\tbinding_name VARCHAR(128) NOT NULL, \n\tsource_after BIGINT NOT NULL, \n\tsource_through BIGINT NOT NULL, \n\tattempt_id VARCHAR(36) NOT NULL, \n\tattempts INTEGER NOT NULL, \n\trequests BIGINT NOT NULL, \n\ttokens BIGINT NOT NULL, \n\tfailure_code VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (scope_id, binding_name, source_after), \n\tCONSTRAINT ck_pc_topic_budget_window CHECK (source_after >= 0 AND source_through > source_after), \n\tCONSTRAINT ck_pc_topic_budget_usage CHECK (attempts > 0 AND requests >= 0 AND tokens >= 0)\n)", + "CREATE INDEX ix_pc_artifact_tags_family_key ON pc_artifact_tags (scope_id, family, tag_key_hash, target_type, artifact_id, target_id)", + "CREATE INDEX ix_pc_artifact_tags_key ON pc_artifact_tags (scope_id, tag_key_hash, family, target_type, artifact_id, target_id)", + "CREATE INDEX ix_pc_decision_observations_operation ON pc_decision_observations (scope_id, operation_id, created_at)", + "CREATE INDEX ix_pc_decision_observations_scope_created ON pc_decision_observations (scope_id, created_at, observation_id)", + "CREATE INDEX ix_pc_dream_dispatch ON pc_dream_runs (scope_id, operation, status, request_generation)", + "CREATE INDEX ix_pc_dream_list ON pc_dream_runs (scope_id, accepted_at, run_id)", + "CREATE INDEX ix_pc_processing_intent_binding_sequence ON pc_artifact_processing_intents (binding_name, pending_sequence)", + "CREATE INDEX ix_pc_recurrence_observation_key ON pc_recurrence_observation (scope_id, signature_key_hash)", + "CREATE INDEX ix_pc_recurrence_observation_outcome ON pc_recurrence_observation (scope_id, task_outcome_source_type, task_outcome_source_id, task_outcome_position)", + "CREATE INDEX ix_pc_recurrence_observation_revision ON pc_recurrence_observation (scope_id, family, artifact_id, revision)", + "CREATE TRIGGER pc_atomic_memory_fts_delete AFTER DELETE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_fts WHERE identity_token MATCH ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); END", + "CREATE TRIGGER pc_atomic_memory_fts_insert AFTER INSERT ON pc_atomic_memory_current BEGIN INSERT INTO pc_atomic_memory_current_fts (scope_id, artifact_id, revision, state_version, content_hash, projection_format, kind, text, searchable_text, tag_keys, identity_token) VALUES (new.scope_id, new.artifact_id, new.revision, new.state_version, new.content_hash, new.projection_format, new.kind, new.text, new.searchable_text, new.tag_keys, 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id))); END", + "CREATE TRIGGER pc_atomic_memory_fts_update AFTER UPDATE ON pc_atomic_memory_current BEGIN DELETE FROM pc_atomic_memory_current_fts WHERE identity_token MATCH ('i' || lower(hex(old.scope_id)) || 'x' || lower(hex(old.artifact_id))); INSERT INTO pc_atomic_memory_current_fts (scope_id, artifact_id, revision, state_version, content_hash, projection_format, kind, text, searchable_text, tag_keys, identity_token) VALUES (new.scope_id, new.artifact_id, new.revision, new.state_version, new.content_hash, new.projection_format, new.kind, new.text, new.searchable_text, new.tag_keys, 'i' || lower(hex(new.scope_id)) || 'x' || lower(hex(new.artifact_id))); END" + ] +} diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/server_domain.py b/src/powercontext/builtin/persistence/migrations/server_resources/server_domain.py new file mode 100644 index 0000000000..c7c95a53f1 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/server_domain.py @@ -0,0 +1,242 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Registered Server 1.3 maintenance and live readiness requirements.""" + +import hashlib +import json +import runpy +from pathlib import Path + +from sqlalchemy import inspect, text + +from powercontext.builtin.persistence.experience_index import ( + APPROVED_ARTIFACT_PROJECTION_VERSION, + rebuild_experience_projections, + rebuild_skill_projections, +) +from powercontext.builtin.persistence.lexical_projection import ( + LEXICAL_ANALYZER_VERSION, + record_lexical_projection_version, + refresh_lexical_projections, +) +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.pinned import finish, pinned + +assert_atomic_memory_migration_ready = runpy.run_path(str(Path(__file__).parent / "atomic_memory_readiness_1300.py"))[ + "assert_atomic_memory_migration_ready" +] + + +def dream(): + return runpy.run_path(str(Path(__file__).parent / "dream/upgrade.py")) + + +def preflight(connection, *, configuration): + names = set(inspect(connection).get_table_names()) + if not names or names == {"pc_schema_revision"}: + return + if configuration.get("embedding_enabled"): + raise MigrationError( + "unsupported_upgrade_configuration", "This registered full Server path requires no vector provider." + ) + # Real 1.3.0 publishes Atomic Memory already. An old schema with pre-1.3 + # collection data needs its separately declared conversion, never a stamp. + finish(assert_atomic_memory_migration_ready(pinned(connection))) + if "pc_artifact_candidate_versions" in names: + _validate_intent_bindings(connection) + module = dream() + try: + module["validate_dream_tasks"](connection) + module["validate_dream_processing_manifest"]( + connection, target_manifest=configuration["processing_manifest"] + ) + except module["DreamUpgradeError"] as error: + raise MigrationError( + error.reason, "The legacy Dream task or Processing configuration needs maintenance." + ) from error + + +def apply(connection, *, configuration): + # Dream's schema revision preserves the legacy manifest; this is the only + # permitted ownership transformation, not an unconditional target overwrite. + old = json.loads( + connection.exec_driver_sql( + "SELECT config_manifest FROM pc_artifact_processing_schema WHERE singleton=1" + ).scalar_one() + ) + if "handoff.dream.v1" not in old["bindings"]: + upgraded = dream()["validate_dream_processing_manifest"]( + connection, target_manifest=configuration["processing_manifest"] + ) + connection.execute( + text("UPDATE pc_artifact_processing_schema SET config_manifest=:value WHERE singleton=1"), + {"value": json.dumps(upgraded, sort_keys=True, separators=(",", ":"))}, + ) + connection.exec_driver_sql( + "INSERT INTO pc_access_relationship_heads (name,revision) SELECT 'authorization',0 WHERE NOT EXISTS (SELECT 1 FROM pc_access_relationship_heads WHERE name='authorization')" + ) + async_connection = pinned(connection) + from powercontext.builtin.persistence.atomic_memory_index_schema import atomic_memory_current_table + + finish(refresh_lexical_projections(async_connection, atomic_memory_current_table())) + finish(rebuild_experience_projections(async_connection)) + finish(rebuild_skill_projections(async_connection)) + finish( + record_lexical_projection_version( + async_connection, "pc_artifact_heads", version=APPROVED_ARTIFACT_PROJECTION_VERSION + ) + ) + if connection.dialect.name == "sqlite": + from powercontext.builtin.persistence.sqlite.atomic_memory_index import SQLiteAtomicMemoryIndex + + finish(SQLiteAtomicMemoryIndex().rebuild_fts(async_connection)) + connection.exec_driver_sql("DELETE FROM pc_artifact_fts") + connection.exec_driver_sql( + "INSERT INTO pc_artifact_fts (scope_id,family,artifact_id,revision,searchable_text) SELECT scope_id,family,artifact_id,revision,searchable_text FROM pc_artifact_heads WHERE family IN ('experience','skill') AND searchable_text IS NOT NULL" + ) + # Both FTS tables are registered schema; this initializer only refreshes + # their rows and existing marker when the analyzer has changed. + from powercontext.builtin.persistence.sqlite.topic_memory_index import SQLiteTopicMemoryFTSIndex + from powercontext.builtin.persistence.topic_memory import TopicMemoryRepository + + finish(TopicMemoryRepository(index=SQLiteTopicMemoryFTSIndex()).initialize(async_connection)) + else: + from powercontext.builtin.persistence.oceanbase.topic_memory_index import OceanBaseTopicMemoryFTSIndex + from powercontext.builtin.persistence.topic_memory import TopicMemoryRepository + + finish(TopicMemoryRepository(index=OceanBaseTopicMemoryFTSIndex()).initialize(async_connection)) + + +def verify(connection, *, configuration): + dream()["verify_dream_storage"](connection) + validate(connection, configuration=configuration) + + +def validate(connection, *, configuration): + finish(assert_atomic_memory_migration_ready(pinned(connection))) + expected = { + "pc_atomic_memory_current": LEXICAL_ANALYZER_VERSION, + "pc_topic_memory_active_topics": LEXICAL_ANALYZER_VERSION, + "pc_topic_memory_active_chunks": LEXICAL_ANALYZER_VERSION, + "pc_artifact_heads": APPROVED_ARTIFACT_PROJECTION_VERSION, + } + actual = dict( + connection + .exec_driver_sql("SELECT projection, analyzer_version FROM pc_lexical_projection_versions") + .tuples() + .all() + ) + if actual != expected: + raise MigrationError("projection_migration_required", "The registered search projections are not current.") + frozen = dream() + manifest = frozen["_processing_manifest"](connection, frozen["_schema"](), upgraded=True) + _validate_intent_bindings(connection) + target = configuration.get("processing_manifest") + if target and any(manifest[key] != target[key] for key in ("bindings", "mode", "capabilities")): + raise MigrationError( + "incompatible_processing_configuration", "Processing ownership differs from the migrated declaration." + ) + from powercontext.builtin.persistence.dream import DreamRepository + + for row in connection.exec_driver_sql( + "SELECT scope_id,run_id,status,operation,generation,request_generation,principal_key,idempotency_key FROM pc_dream_runs" + ).mappings(): + record = finish(DreamRepository().get(pinned(connection), row["scope_id"], row["run_id"])) + if ( + any(getattr(record.run, key) != row[key] for key in ("scope_id", "run_id", "status", "operation")) + or record.generation != row["generation"] + or record.request_generation != row["request_generation"] + or hashlib.sha256(record.principal_id.encode()).hexdigest() != row["principal_key"] + or (record.request is not None and record.request.idempotency_key != row["idempotency_key"]) + ): + raise MigrationError("unsupported_task_format", "A Dream payload disagrees with its durable task identity.") + from powercontext.builtin.persistence.processing_intents import ArtifactProcessingIntentRepository + + for scope, binding in connection.exec_driver_sql( + "SELECT scope_id,binding_name FROM pc_artifact_processing_intents" + ): + finish(ArtifactProcessingIntentRepository().load(pinned(connection), scope, binding)) + from powercontext.builtin.persistence.oceanbase.atomic_memory_index import OceanBaseAtomicMemoryIndex + from powercontext.builtin.persistence.sqlite.atomic_memory_index import SQLiteAtomicMemoryIndex + from powercontext.builtin.runtime.atomic_memory_rebuild import verify_atomic_memory_current + + atomic_index = SQLiteAtomicMemoryIndex() if connection.dialect.name == "sqlite" else OceanBaseAtomicMemoryIndex() + report = finish(verify_atomic_memory_current(pinned(connection), atomic_index)) + if not report.ready: + raise MigrationError( + "projection_verification_failed", "Atomic Memory projections differ from their authoritative records." + ) + + from powercontext.builtin.persistence.topic_memory import TopicMemoryRepository + + if connection.dialect.name == "sqlite": + from powercontext.builtin.persistence.sqlite.topic_memory_index import SQLiteTopicMemoryFTSIndex + + index = SQLiteTopicMemoryFTSIndex() + _validate_sqlite_fts(connection) + else: + from powercontext.builtin.persistence.oceanbase.topic_memory_index import OceanBaseTopicMemoryFTSIndex + + index = OceanBaseTopicMemoryFTSIndex() + finish(TopicMemoryRepository(index=index).initialize(pinned(connection), validate_only=True)) + + +def _validate_sqlite_fts(connection): + from powercontext.builtin.persistence.sqlite.atomic_memory_index import SQLiteAtomicMemoryIndex + + if not finish(SQLiteAtomicMemoryIndex().fts_matches_current(pinned(connection))): + raise MigrationError( + "projection_verification_failed", "The Atomic Memory full-text helper differs from its current rows." + ) + # Read-only multiset comparison also catches duplicate/stale helper rows. + projections = ( + ( + "pc_artifact_heads", + "pc_artifact_fts", + "scope_id,family,artifact_id,revision,searchable_text", + " WHERE family IN ('experience','skill') AND searchable_text IS NOT NULL", + ), + ( + "pc_topic_memory_active_topics", + "pc_topic_memory_topic_fts", + "scope_id,artifact_id,revision,searchable_text", + "", + ), + ( + "pc_topic_memory_active_chunks", + "pc_topic_memory_chunk_fts", + "scope_id,artifact_id,revision,chunk_ordinal,searchable_text", + "", + ), + ) + for source, helper, columns, predicate in projections: + expected = f"SELECT {columns},COUNT(*) FROM {source}{predicate} GROUP BY {columns}" # noqa: S608 -- Only frozen schema names and projections are interpolated. + actual = f"SELECT {columns},COUNT(*) FROM {helper} GROUP BY {columns}" # noqa: S608 -- Only frozen schema names and projections are interpolated. + for left, right in ((expected, actual), (actual, expected)): + if connection.exec_driver_sql(f"SELECT 1 FROM ({left} EXCEPT {right}) LIMIT 1").first(): # noqa: S608 -- Only frozen schema names and projections are interpolated. + raise MigrationError( + "projection_verification_failed", "A full-text helper differs from its authoritative current rows." + ) + + +def _validate_intent_bindings(connection): + manifest = json.loads( + connection.exec_driver_sql( + "SELECT config_manifest FROM pc_artifact_processing_schema WHERE singleton=1" + ).scalar_one() + ) + for (binding,) in connection.exec_driver_sql("SELECT DISTINCT binding_name FROM pc_artifact_processing_intents"): + if binding not in manifest["bindings"]: + raise MigrationError("unsupported_task_format", "A pending Processing intent has no registered binding.") diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/server_vector_domain.py b/src/powercontext/builtin/persistence/migrations/server_resources/server_vector_domain.py new file mode 100644 index 0000000000..0c6982da37 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/server_vector_domain.py @@ -0,0 +1,384 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Registered Server vector maintenance and live readiness requirements.""" + +import hashlib +import json +import runpy +from pathlib import Path + +from sqlalchemy import inspect, text + +from powercontext.builtin.persistence.experience_index import ( + APPROVED_ARTIFACT_PROJECTION_VERSION, + rebuild_experience_projections, + rebuild_skill_projections, +) +from powercontext.builtin.persistence.lexical_projection import ( + LEXICAL_ANALYZER_VERSION, + record_lexical_projection_version, + refresh_lexical_projections, +) +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.pinned import finish, pinned + +assert_atomic_memory_migration_ready = runpy.run_path(str(Path(__file__).parent / "atomic_memory_readiness_1300.py"))[ + "assert_atomic_memory_migration_ready" +] + + +def dream(): + return runpy.run_path(str(Path(__file__).parent / "dream/upgrade.py")) + + +def preflight(connection, *, configuration): + names = set(inspect(connection).get_table_names()) + if not names or names == {"pc_schema_revision"}: + return + _validate_vector_configuration(connection, configuration) + # Real 1.3.0 publishes Atomic Memory already. An old schema with pre-1.3 + # collection data needs its separately declared conversion, never a stamp. + finish(assert_atomic_memory_migration_ready(pinned(connection))) + if "pc_artifact_candidate_versions" in names: + _validate_intent_bindings(connection) + module = dream() + try: + module["validate_dream_tasks"](connection) + module["validate_dream_processing_manifest"]( + connection, target_manifest=configuration["processing_manifest"] + ) + except module["DreamUpgradeError"] as error: + raise MigrationError( + error.reason, "The legacy Dream task or Processing configuration needs maintenance." + ) from error + + +def apply(connection, *, configuration): + _record_profile(connection, configuration) + # Dream's schema revision preserves the legacy manifest; this is the only + # permitted ownership transformation, not an unconditional target overwrite. + old = json.loads( + connection.exec_driver_sql( + "SELECT config_manifest FROM pc_artifact_processing_schema WHERE singleton=1" + ).scalar_one() + ) + if "handoff.dream.v1" not in old["bindings"]: + upgraded = dream()["validate_dream_processing_manifest"]( + connection, target_manifest=configuration["processing_manifest"] + ) + connection.execute( + text("UPDATE pc_artifact_processing_schema SET config_manifest=:value WHERE singleton=1"), + {"value": json.dumps(upgraded, sort_keys=True, separators=(",", ":"))}, + ) + connection.exec_driver_sql( + "INSERT INTO pc_access_relationship_heads (name,revision) SELECT 'authorization',0 WHERE NOT EXISTS (SELECT 1 FROM pc_access_relationship_heads WHERE name='authorization')" + ) + async_connection = pinned(connection) + from powercontext.builtin.persistence.atomic_memory_index_schema import atomic_memory_current_table + + finish(refresh_lexical_projections(async_connection, atomic_memory_current_table())) + finish(rebuild_experience_projections(async_connection)) + finish(rebuild_skill_projections(async_connection)) + finish( + record_lexical_projection_version( + async_connection, "pc_artifact_heads", version=APPROVED_ARTIFACT_PROJECTION_VERSION + ) + ) + if connection.dialect.name == "sqlite": + from powercontext.builtin.persistence.sqlite.atomic_memory_index import SQLiteAtomicMemoryIndex + + finish(SQLiteAtomicMemoryIndex().rebuild_fts(async_connection)) + connection.exec_driver_sql("DELETE FROM pc_artifact_fts") + connection.exec_driver_sql( + "INSERT INTO pc_artifact_fts (scope_id,family,artifact_id,revision,searchable_text) SELECT scope_id,family,artifact_id,revision,searchable_text FROM pc_artifact_heads WHERE family IN ('experience','skill') AND searchable_text IS NOT NULL" + ) + # Both FTS tables are registered schema; this initializer only refreshes + # their rows and existing marker when the analyzer has changed. + from powercontext.builtin.persistence.sqlite.topic_memory_index import SQLiteTopicMemoryFTSIndex + from powercontext.builtin.persistence.topic_memory import TopicMemoryRepository + + finish(SQLiteTopicMemoryFTSIndex().initialize(async_connection)) + _backfill_atomic_vectors(connection, configuration) + else: + from powercontext.builtin.persistence.oceanbase.topic_memory_index import OceanBaseTopicMemoryFTSIndex + + finish(OceanBaseTopicMemoryFTSIndex().initialize(async_connection)) + _ensure_shape(connection, configuration) + from powercontext.builtin.persistence.topic_memory import TopicMemoryRepository + + finish( + TopicMemoryRepository(index=_topic_index(connection, configuration)).initialize( + async_connection, validate_only=True + ) + ) + + +def verify(connection, *, configuration): + dream()["verify_dream_storage"](connection) + validate(connection, configuration=configuration) + + +def validate(connection, *, configuration): + _validate_vector_configuration(connection, configuration) + _require_declaration(connection, configuration) + finish(assert_atomic_memory_migration_ready(pinned(connection))) + expected = { + "pc_atomic_memory_current": LEXICAL_ANALYZER_VERSION, + "pc_topic_memory_active_topics": LEXICAL_ANALYZER_VERSION, + "pc_topic_memory_active_chunks": LEXICAL_ANALYZER_VERSION, + "pc_artifact_heads": APPROVED_ARTIFACT_PROJECTION_VERSION, + } + actual = dict( + connection + .exec_driver_sql("SELECT projection, analyzer_version FROM pc_lexical_projection_versions") + .tuples() + .all() + ) + if actual != expected: + raise MigrationError("projection_migration_required", "The registered search projections are not current.") + frozen = dream() + manifest = frozen["_processing_manifest"](connection, frozen["_schema"](), upgraded=True) + _validate_intent_bindings(connection) + target = configuration.get("processing_manifest") + if target and any(manifest[key] != target[key] for key in ("bindings", "mode", "capabilities")): + raise MigrationError( + "incompatible_processing_configuration", "Processing ownership differs from the migrated declaration." + ) + from powercontext.builtin.persistence.dream import DreamRepository + + for row in connection.exec_driver_sql( + "SELECT scope_id,run_id,status,operation,generation,request_generation,principal_key,idempotency_key FROM pc_dream_runs" + ).mappings(): + record = finish(DreamRepository().get(pinned(connection), row["scope_id"], row["run_id"])) + if ( + any(getattr(record.run, key) != row[key] for key in ("scope_id", "run_id", "status", "operation")) + or record.generation != row["generation"] + or record.request_generation != row["request_generation"] + or hashlib.sha256(record.principal_id.encode()).hexdigest() != row["principal_key"] + or (record.request is not None and record.request.idempotency_key != row["idempotency_key"]) + ): + raise MigrationError("unsupported_task_format", "A Dream payload disagrees with its durable task identity.") + from powercontext.builtin.persistence.processing_intents import ArtifactProcessingIntentRepository + + for scope, binding in connection.exec_driver_sql( + "SELECT scope_id,binding_name FROM pc_artifact_processing_intents" + ): + finish(ArtifactProcessingIntentRepository().load(pinned(connection), scope, binding)) + from powercontext.builtin.persistence.oceanbase.atomic_memory_index import OceanBaseAtomicMemoryIndex + from powercontext.builtin.persistence.sqlite.atomic_memory_index import SQLiteAtomicMemoryIndex + from powercontext.builtin.runtime.atomic_memory_rebuild import verify_atomic_memory_current + + profile = _profile(configuration) + atomic_index = ( + SQLiteAtomicMemoryIndex(profile) if connection.dialect.name == "sqlite" else OceanBaseAtomicMemoryIndex(profile) + ) + report = finish(verify_atomic_memory_current(pinned(connection), atomic_index)) + if not report.ready: + raise MigrationError( + "projection_verification_failed", "Atomic Memory projections differ from their authoritative records." + ) + + from powercontext.builtin.persistence.topic_memory import TopicMemoryRepository + + if connection.dialect.name == "sqlite": + _validate_sqlite_fts(connection) + _validate_atomic_helper(connection, configuration) + finish( + TopicMemoryRepository(index=_topic_index(connection, configuration)).initialize( + pinned(connection), validate_only=True + ) + ) + + +def _validate_sqlite_fts(connection): + from powercontext.builtin.persistence.sqlite.atomic_memory_index import SQLiteAtomicMemoryIndex + + if not finish(SQLiteAtomicMemoryIndex().fts_matches_current(pinned(connection))): + raise MigrationError( + "projection_verification_failed", "The Atomic Memory full-text helper differs from its current rows." + ) + # Read-only multiset comparison also catches duplicate/stale helper rows. + projections = ( + ( + "pc_artifact_heads", + "pc_artifact_fts", + "scope_id,family,artifact_id,revision,searchable_text", + " WHERE family IN ('experience','skill') AND searchable_text IS NOT NULL", + ), + ( + "pc_topic_memory_active_topics", + "pc_topic_memory_topic_fts", + "scope_id,artifact_id,revision,searchable_text", + "", + ), + ( + "pc_topic_memory_active_chunks", + "pc_topic_memory_chunk_fts", + "scope_id,artifact_id,revision,chunk_ordinal,searchable_text", + "", + ), + ) + for source, helper, columns, predicate in projections: + expected = f"SELECT {columns},COUNT(*) FROM {source}{predicate} GROUP BY {columns}" # noqa: S608 -- Only frozen schema names and projections are interpolated. + actual = f"SELECT {columns},COUNT(*) FROM {helper} GROUP BY {columns}" # noqa: S608 -- Only frozen schema names and projections are interpolated. + for left, right in ((expected, actual), (actual, expected)): + if connection.exec_driver_sql(f"SELECT 1 FROM ({left} EXCEPT {right}) LIMIT 1").first(): # noqa: S608 -- Only frozen schema names and projections are interpolated. + raise MigrationError( + "projection_verification_failed", "A full-text helper differs from its authoritative current rows." + ) + + +def _validate_intent_bindings(connection): + manifest = json.loads( + connection.exec_driver_sql( + "SELECT config_manifest FROM pc_artifact_processing_schema WHERE singleton=1" + ).scalar_one() + ) + for (binding,) in connection.exec_driver_sql("SELECT DISTINCT binding_name FROM pc_artifact_processing_intents"): + if binding not in manifest["bindings"]: + raise MigrationError("unsupported_task_format", "A pending Processing intent has no registered binding.") + + +def _profile(configuration): + from powercontext.builtin.artifacts.memory import EmbeddingProfile + + try: + profile = EmbeddingProfile.model_validate(configuration["embedding_profile"]) + except (ValueError, KeyError) as error: + raise MigrationError("embedding_profile_mismatch", "The exact vector profile must be declared.") from error + if profile.dimension < 1 or profile.distance != "l2" or profile.normalization != "unit": + raise MigrationError( + "unsupported_upgrade_configuration", "Vectors require a positive unit-normalized L2 profile." + ) + return profile + + +def _validate_vector_configuration(connection, configuration): + from powercontext.builtin.persistence.atomic_memory_index import atomic_memory_profile_fingerprint + from powercontext.builtin.persistence.topic_memory_index import topic_memory_embedding_profile_fingerprint + + profile = _profile(configuration) + atomic = atomic_memory_profile_fingerprint(profile) + topic = topic_memory_embedding_profile_fingerprint(profile) + row = connection.exec_driver_sql("SELECT shape,profile_fingerprint FROM pc_topic_memory_retrieval_shape").first() + if row is not None and tuple(row) != ("hybrid", topic): + raise MigrationError( + "embedding_profile_mismatch", "The historical Topic retrieval space differs from configuration." + ) + if row is None and connection.exec_driver_sql("SELECT 1 FROM pc_topic_memory_active_topics LIMIT 1").first(): + raise MigrationError("embedding_profile_mismatch", "Historical Topics have no declared retrieval space.") + invalid = "embedding IS NOT NULL AND (profile_fingerprint IS NULL OR profile_fingerprint<>:profile)" + if connection.dialect.name == "sqlite": + invalid += " OR (embedding IS NOT NULL AND length(embedding)<>:length)" + if connection.execute( + text("SELECT 1 FROM pc_atomic_memory_current WHERE " + invalid + " LIMIT 1"), # noqa: S608 -- Only two fixed backend predicates are concatenated. + {"profile": atomic, "length": profile.dimension * 4}, + ).first(): + raise MigrationError( + "embedding_profile_mismatch", "Historical Atomic Memory vectors differ from configuration." + ) + for name in ("pc_topic_memory_vector_topics", "pc_topic_memory_vector_chunks"): + if connection.execute( + text(f"SELECT 1 FROM {name} WHERE profile_fingerprint<>:profile LIMIT 1"), # noqa: S608 -- Both table names are frozen above. + {"profile": topic}, + ).first(): + raise MigrationError("embedding_profile_mismatch", "Historical Topic vectors differ from configuration.") + + +def _require_declaration(connection, configuration): + from powercontext.builtin.persistence.migrations.vector_profile import read_embedding_profile + + if read_embedding_profile(connection) != _profile(configuration): + raise MigrationError("embedding_profile_mismatch", "The managed vector configuration declaration differs.") + + +def _record_profile(connection, configuration): + row = connection.exec_driver_sql("SELECT embedding_profile FROM pc_server_storage_profile").first() + if row is None: + connection.execute( + text("INSERT INTO pc_server_storage_profile (singleton,embedding_profile) VALUES (1,:profile)"), + {"profile": _profile(configuration).model_dump_json()}, + ) + _require_declaration(connection, configuration) + + +def _ensure_shape(connection, configuration): + from powercontext.builtin.persistence.topic_memory_index import topic_memory_embedding_profile_fingerprint + + if connection.exec_driver_sql("SELECT 1 FROM pc_topic_memory_retrieval_shape").first() is None: + if connection.exec_driver_sql("SELECT 1 FROM pc_topic_memory_active_topics LIMIT 1").first(): + raise MigrationError("embedding_profile_mismatch", "Historical Topics have no declared retrieval space.") + connection.execute( + text( + "INSERT INTO pc_topic_memory_retrieval_shape (singleton,shape,profile_fingerprint) VALUES (1,'hybrid',:profile)" + ), + {"profile": topic_memory_embedding_profile_fingerprint(_profile(configuration))}, + ) + + +def _topic_index(connection, configuration): + from powercontext.builtin.persistence.topic_memory_index import CompositeTopicMemoryIndex + + if connection.dialect.name == "sqlite": + from powercontext.builtin.persistence.sqlite.topic_memory_index import ( + SQLiteTopicMemoryFTSIndex, + SQLiteTopicMemoryVectorIndex, + ) + + return CompositeTopicMemoryIndex( + SQLiteTopicMemoryFTSIndex(), SQLiteTopicMemoryVectorIndex(_profile(configuration)) + ) + from powercontext.builtin.persistence.oceanbase.topic_memory_index import ( + OceanBaseTopicMemoryFTSIndex, + OceanBaseTopicMemoryVectorIndex, + ) + + return CompositeTopicMemoryIndex( + OceanBaseTopicMemoryFTSIndex(), OceanBaseTopicMemoryVectorIndex(_profile(configuration)) + ) + + +def _backfill_atomic_vectors(connection, configuration): + from powercontext.builtin.persistence.atomic_memory_index import atomic_memory_profile_fingerprint + + profile = _profile(configuration) + connection.exec_driver_sql("DELETE FROM pc_atomic_memory_current_vec") + connection.execute( + text( + "INSERT INTO pc_atomic_memory_current_vec (identity,scope_id,kind,embedding,artifact_id,revision,state_version,text) " + "SELECT 'i'||lower(hex(scope_id))||'x'||lower(hex(artifact_id)),scope_id,kind,embedding,artifact_id,revision,state_version,text " + "FROM pc_atomic_memory_current WHERE embedding IS NOT NULL AND embedding_input_hash IS NOT NULL " + "AND profile_fingerprint=:profile AND projection_format='powercontext.atomic-memory.current.v1' AND length(embedding)=:length" + ), + {"profile": atomic_memory_profile_fingerprint(profile), "length": profile.dimension * 4}, + ) + + +def _validate_atomic_helper(connection, configuration): + from powercontext.builtin.persistence.atomic_memory_index import atomic_memory_profile_fingerprint + + profile = _profile(configuration) + expected = ( + "SELECT 'i'||lower(hex(scope_id))||'x'||lower(hex(artifact_id)),scope_id,kind,embedding,artifact_id,revision,state_version,text " + "FROM pc_atomic_memory_current WHERE embedding IS NOT NULL AND embedding_input_hash IS NOT NULL " + "AND profile_fingerprint=:profile AND projection_format='powercontext.atomic-memory.current.v1' AND length(embedding)=:length" + ) + actual = "SELECT identity,scope_id,kind,embedding,artifact_id,revision,state_version,text FROM pc_atomic_memory_current_vec" + values = {"profile": atomic_memory_profile_fingerprint(profile), "length": profile.dimension * 4} + for left, right in ((expected, actual), (actual, expected)): + if connection.execute(text(f"SELECT 1 FROM ({left} EXCEPT {right}) LIMIT 1"), values).first(): # noqa: S608 -- Both complete projections are frozen above. + raise MigrationError( + "projection_verification_failed", "The Atomic vector helper differs from current rows." + ) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1300_server_baseline.py b/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1300_server_baseline.py new file mode 100644 index 0000000000..7b14dd95bf --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1300_server_baseline.py @@ -0,0 +1,42 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Complete released Server 1.3.0, no-vector profile.""" + +import json +from pathlib import Path + +from alembic import context, op +from sqlalchemy import text + +revision = "s1300" +down_revision = None + + +def upgrade(): + connection = op.get_bind() + resource = json.loads((Path(__file__).parents[1] / "schemas/sqlite.json").read_text()) + for statement in resource["initialize"]: + connection.exec_driver_sql(statement) + manifest = dict(context.config.attributes["configuration"]["processing_manifest"]) + manifest["bindings"] = { + key: value for key, value in manifest["bindings"].items() if value not in {"handoff", "prompt"} + } + manifest["capabilities"] = [value for value in manifest["capabilities"] if value not in {"handoff", "prompt"}] + connection.execute( + text( + "INSERT INTO pc_artifact_processing_schema (singleton,schema_version,source_version,phase,migration_id,config_manifest) VALUES (1,1515,1515,'complete','fresh',:manifest)" + ), + {"manifest": json.dumps(manifest, sort_keys=True, separators=(",", ":"))}, + ) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1400_full_server.py b/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1400_full_server.py new file mode 100644 index 0000000000..4526ca6191 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1400_full_server.py @@ -0,0 +1,74 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Complete 1.3.0 -> 1.4 Server storage transition on the pinned transaction.""" + +import json +import runpy +from pathlib import Path + +from alembic import context, op +from sqlalchemy import inspect + +revision = "s1400" +down_revision = "s1300" + + +def upgrade(): + connection = op.get_bind() + root = Path(__file__).parents[1] + dream = runpy.run_path(str(root / "dream/upgrade.py")) + dream["upgrade_dream_storage"]( + connection, target_manifest=context.config.attributes["configuration"]["processing_manifest"] + ) + target = json.loads((root / "schemas/sqlite.json").read_text())["target"] + # The canonical rebuild fixes constraint names and column order as well as + # additions. Explicit column lists preserve every existing value; new nullable + # provenance stays unknown, never synthesized from migration time. + rebuild = { + "pc_candidate_heads", + "pc_candidate_versions", + "pc_profile_policies", + "pc_artifacts", + "pc_artifact_processing_intents", + "pc_dream_runs", + "pc_topic_memory_work_budgets", + } + for kind, name, _owner, ddl in target: + if kind != "table" or name not in rebuild: + continue + columns = [item["name"] for item in inspect(connection).get_columns(name)] + fields = ",".join('"' + item + '"' for item in columns) + temporary = "pc_full_upgrade_" + name + connection.exec_driver_sql(ddl.replace("CREATE TABLE " + name, "CREATE TABLE " + temporary, 1)) + connection.exec_driver_sql(f"INSERT INTO {temporary} ({fields}) SELECT {fields} FROM {name}") # noqa: S608 -- frozen identifiers + connection.exec_driver_sql(f"DROP TABLE {name}") + connection.exec_driver_sql(f"ALTER TABLE {temporary} RENAME TO {name}") + added = { + "pc_artifact_handles", + "pc_context_bootstrap_receipts", + "pc_lexical_projection_versions", + "pc_topic_memory_work_blocks", + } + for kind, name, _owner, ddl in target: + if kind == "table" and name in added: + connection.exec_driver_sql(ddl) + for kind, _name, owner, ddl in target: + if kind == "index" and owner in rebuild | added: + connection.exec_driver_sql(ddl) + # Revoke the old fence and leave an expired term for timed-lease takeover. + # NULL denotes a non-expiring single-process term and blocks OceanBase. + connection.exec_driver_sql( + "UPDATE pc_artifact_processing_leases SET supervisor_generation=supervisor_generation+1, holder_id='migration-s1400', lease_expires_at='1970-01-01 00:00:00.000000'" + ) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1401_memory_archive.py b/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1401_memory_archive.py new file mode 100644 index 0000000000..b75645dfee --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1401_memory_archive.py @@ -0,0 +1,28 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Register offline Memory history without exposing it through live Artifacts.""" + +import json +from pathlib import Path + +from alembic import op + +revision = "s1401" +down_revision = "s1400" + + +def upgrade(): + resource = Path(__file__).parents[1] / "schemas/sqlite-s1401.json" + op.get_bind().exec_driver_sql(json.loads(resource.read_text())["create"]) diff --git a/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1402_portable_storage.py b/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1402_portable_storage.py new file mode 100644 index 0000000000..77608a1b19 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_resources/versions/s1402_portable_storage.py @@ -0,0 +1,28 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Register portable restore receipts without changing released Server layouts.""" + +import json +from pathlib import Path + +from alembic import op + +revision = "s1402" +down_revision = "s1401" + + +def upgrade(): + resource = Path(__file__).parents[1] / "schemas/sqlite-s1402.json" + op.get_bind().exec_driver_sql(json.loads(resource.read_text())["create"]) diff --git a/src/powercontext/builtin/persistence/migrations/sqlite.py b/src/powercontext/builtin/persistence/migrations/sqlite.py index 2664f2f440..213b6796dc 100644 --- a/src/powercontext/builtin/persistence/migrations/sqlite.py +++ b/src/powercontext/builtin/persistence/migrations/sqlite.py @@ -39,6 +39,7 @@ from .bundle import ALEMBIC_CONTEXT_LOCK, MigrationBundle, sqlite_inventory from .locking import local_migration_lock from .models import MigrationError, MigrationPlan, MigrationResult, digest +from .server_baselines import recognize_sqlite_server_baseline _VERSION_DDL = ( "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) NOT NULL, " @@ -107,7 +108,8 @@ def __init__( ) -> None: self.database = database.expanduser().resolve() self.bundle = bundle - self.configuration_digest = digest(configuration or {}) + self.configuration = json.loads(json.dumps(configuration or {})) + self.configuration_digest = digest(self.configuration) self.backup_provider = backup_provider or SQLiteBackupProvider(self.database) directory = evidence_directory or self.database.with_name(self.database.name + ".pc-migration-state") self.evidence_path = directory.expanduser().resolve() / "maintenance.json" @@ -144,7 +146,7 @@ def verify(self) -> MigrationResult: plan = self.plan(backup_policy=policy) if plan.state != "ready": raise MigrationError(plan.state, "Inspect and explicitly apply the registered bundle before verification.") - return MigrationResult(revision=self.bundle.head, changed=False) + return MigrationResult(revision=self.bundle.head, changed=False, readiness_scope=self.bundle.readiness_scope) def apply( self, @@ -166,7 +168,9 @@ def apply( } initial = self.plan(**options) if initial.state == "ready": - return MigrationResult(revision=self.bundle.head, changed=False) + return MigrationResult( + revision=self.bundle.head, changed=False, readiness_scope=self.bundle.readiness_scope + ) self._require_acceptance(initial, plan_id, accepted, maintenance_confirmed, backup_confirmed, accept_no_backup) with local_migration_lock(self.database): if self.plan(**options) != initial: @@ -179,6 +183,9 @@ def apply( raise MigrationError("plan_changed", "State changed before acquiring the write transaction.") evidence = self._prepare_evidence(initial, backup_ref) self.bundle.assert_unchanged() + self.bundle.domains.preflight( + connection, initial.source_revision, backend="sqlite", configuration=self.configuration + ) version_exists = ( connection.exec_driver_sql("SELECT name FROM sqlite_schema WHERE name='pc_schema_revision'").first() is not None @@ -187,7 +194,7 @@ def apply( connection.exec_driver_sql(_VERSION_DDL) if initial.adopt_baseline and initial.source_revision is not None: with ALEMBIC_CONTEXT_LOCK: - command.stamp(self.bundle.config(connection), initial.source_revision) + command.stamp(self._config(connection), initial.source_revision) connection.commit() self._upgrade(connection) self._begin(connection) @@ -204,6 +211,7 @@ def apply( return MigrationResult( revision=self.bundle.head, changed=True, + readiness_scope=self.bundle.readiness_scope, backup_state=evidence["backup_state"], backup_ref=ref["location"] if ref else evidence.get("manual_ref"), ) @@ -244,9 +252,20 @@ def _check_file(self) -> None: @contextmanager def _connect(self, *, read_only: bool) -> Iterator[Connection]: uri = self.database.as_uri() + ("?mode=ro" if read_only else "?mode=rwc") - engine = create_engine( - "sqlite://", creator=lambda: sqlite3.connect(uri, uri=True, isolation_level=None, timeout=0) - ) + + def connect() -> sqlite3.Connection: + driver = sqlite3.connect(uri, uri=True, isolation_level=None, timeout=0) + if self.bundle.vector_profile is not None: + import sqlite_vec + + driver.enable_load_extension(True) + try: + sqlite_vec.load(driver) + finally: + driver.enable_load_extension(False) + return driver + + engine = create_engine("sqlite://", creator=connect) try: with engine.connect() as connection: if read_only: @@ -285,11 +304,20 @@ def _inspect(self, connection: Connection, **options: Any) -> MigrationPlan: elif inventory: revision = self.bundle.baselines.get(fingerprint) if revision is None: + source = recognize_sqlite_server_baseline(connection) + if source is not None: + raise MigrationError( + "server_migration_unavailable", + f"Complete Server {source.release} schema recognized; " + "the installed bundle has no registered full Server upgrade path. No changes were made.", + ) raise MigrationError("unknown_baseline", "Schema is not registered; no changes were made.") self._verify_retained(inventory, revision) self._verify_integrity(connection) + self.bundle.domains.validate(connection, revision, backend="sqlite", configuration=self.configuration) else: revision = None + self.bundle.domains.preflight(connection, revision, backend="sqlite", configuration=self.configuration) return self._plan(revision, fingerprint, initialized=initialized, **options) def _plan( @@ -303,6 +331,7 @@ def _plan( shared_database: bool, ) -> MigrationPlan: pending = self.bundle.pending(revision) + domain_steps = self.bundle.domains.pending(revision, "sqlite") adopt = not initialized and revision is not None state = "migration_required" if pending or adopt else "ready" if not initialized and revision is None: @@ -335,11 +364,14 @@ def _plan( "database_id": self._database_id(), "source_revision": revision, "target_revision": self.bundle.head, + "readiness_scope": self.bundle.readiness_scope, "schema_fingerprint": fingerprint, "bundle_checksum": self.bundle.checksum, "configuration_digest": self.configuration_digest, "state": state, "revisions": pending, + "domain_steps": tuple(step.id for step in domain_steps), + "domain_validators": self.bundle.domains.readiness.get(self.bundle.head, ()), "adopt_baseline": adopt, "backup_policy": backup_policy, "backup_available": available, @@ -501,6 +533,11 @@ def _begin(connection: Connection) -> None: raise MigrationError("active_writers", "SQLite is busy; stop writers before retrying.") from error raise + def _config(self, connection): + config = self.bundle.config(connection) + config.attributes["configuration"] = self.configuration + return config + def _upgrade(self, connection: Connection) -> None: current = connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalar_one_or_none() connection.commit() @@ -508,17 +545,23 @@ def _upgrade(self, connection: Connection) -> None: self._begin(connection) self._verify_schema(connection, current) with ALEMBIC_CONTEXT_LOCK: - command.upgrade(self.bundle.config(connection), revision) + command.upgrade(self._config(connection), revision) + if self.bundle.vector_profile is not None: + self.bundle.vector_profile.sqlite_upgrade(connection, revision) + self.bundle.domains.apply(connection, revision, backend="sqlite", configuration=self.configuration) self._verify_schema(connection, revision) connection.commit() current = revision def _verify_schema(self, connection: Connection, revision: str | None) -> None: + if self.bundle.vector_profile is not None: + self.bundle.vector_profile.verify_declaration(connection, required=False) excluded = _CONTROL_NAMES | frozenset(self.bundle.retained_objects) inventory = sqlite_inventory(connection) self._verify_retained(inventory, revision) self.bundle.verify(revision, digest(sqlite_inventory(connection, exclude=excluded))) self._verify_integrity(connection) + self.bundle.domains.validate(connection, revision, backend="sqlite", configuration=self.configuration) def _verify_retained(self, inventory: list[list[Any]], revision: str | None) -> None: for name in self.bundle.retained_objects: diff --git a/src/powercontext/builtin/persistence/migrations/topic_memory_recovery.py b/src/powercontext/builtin/persistence/migrations/topic_memory_recovery.py index 15feb5e41b..eb0aa69e11 100644 --- a/src/powercontext/builtin/persistence/migrations/topic_memory_recovery.py +++ b/src/powercontext/builtin/persistence/migrations/topic_memory_recovery.py @@ -357,6 +357,11 @@ def _provider(self, connection: Connection, identity: BackendIdentity) -> Backup def _inspect(self, connection: Connection | None, identity: BackendIdentity, **options) -> MigrationPlan: if _resource_checksum() != self.checksum: raise MigrationError("plan_changed", "Topic recovery migration resources changed after planning.") + if connection is not None and inspect(connection).has_table("pc_schema_revision"): + raise MigrationError( + "unsupported_target", + "Managed Server storage requires the full Server bundle or registered maintenance.", + ) evidence = self._evidence(identity) active = evidence is not None and evidence.get("state") != "complete" if evidence and ( diff --git a/src/powercontext/builtin/persistence/migrations/vector_profile.py b/src/powercontext/builtin/persistence/migrations/vector_profile.py new file mode 100644 index 0000000000..a73f6b3ace --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/vector_profile.py @@ -0,0 +1,149 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Render the registered vector layout without invoking runtime initializers.""" + +from __future__ import annotations + +import copy +import json +import sqlite3 +from pathlib import Path +from typing import Any + +from sqlalchemy import Connection, inspect +from sqlalchemy.engine import AdaptedConnection +from sqlalchemy.exc import OperationalError + +from powercontext.builtin.artifacts.memory import EmbeddingProfile +from powercontext.builtin.persistence.atomic_memory_index import atomic_memory_profile_fingerprint + +from .models import MigrationError, digest + + +class ServerVectorProfile: + """Bind frozen schema templates to one exact embedding space.""" + + resource = "profiles/vector.json" + + def __init__(self, directory: Path, profile: EmbeddingProfile): + if profile.dimension < 1 or profile.distance != "l2" or profile.normalization != "unit": + raise MigrationError( + "unsupported_upgrade_configuration", "Server vectors require a positive unit-normalized L2 profile." + ) + self.profile = profile.model_copy(deep=True) + source = (directory / self.resource).read_text(encoding="utf-8") + self.layout: dict[str, Any] = json.loads( + source + .replace("__DIMENSION__", str(profile.dimension)) + .replace("__VECTOR_BYTES__", str(profile.dimension * 4)) + .replace("__ATOMIC_FINGERPRINT__", atomic_memory_profile_fingerprint(profile)) + ) + if self.layout.get("format") != "powercontext.server-vector-profile.v1": + raise MigrationError("invalid_bundle", "The Server vector schema registration is invalid.") + self.checksum = digest({ + "template": json.loads(source), + "profile": profile.model_dump(mode="json"), + }) + + def sqlite_layouts(self, directory: Path) -> dict[str, list[list[Any]]]: + core = json.loads((directory / "schemas/sqlite.json").read_text(encoding="utf-8")) + archive = json.loads((directory / "schemas/sqlite-s1401.json").read_text(encoding="utf-8"))["create"] + layouts = { + "s1300": core["source"] + self.layout["sqlite"]["source"], + "s1400": core["target"] + self.layout["sqlite"]["target"], + } + # The frozen s1400 table rebuild uses ALTER TABLE RENAME, whose stored + # definition quotes the final name. Match those exact reviewed bytes. + for row in layouts["s1400"]: + if row[0] == "table" and row[1] in self.layout["sqlite"]["rebuilt_tables"]: + row[3] = row[3].replace(f"CREATE TABLE {row[1]} ", f'CREATE TABLE "{row[1]}" ', 1) + layouts["s1401"] = layouts["s1400"] + [ + ["table", "pc_memory_artifact_archive", "pc_memory_artifact_archive", archive] + ] + manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8")) + if "s1402" in manifest["revision_resources"]: + receipt = json.loads((directory / "schemas/sqlite-s1402.json").read_text(encoding="utf-8"))["create"] + layouts["s1402"] = layouts["s1401"] + [ + ["table", "pc_portable_restore_receipts", "pc_portable_restore_receipts", receipt] + ] + for entries in layouts.values(): + entries.sort(key=lambda row: (row[0], row[1])) + return layouts + + def sqlite_upgrade(self, connection: Connection, revision: str) -> None: + operations = {"s1300": "initialize", "s1400": "upgrade"} + if revision in operations: + for statement in self.layout["sqlite"][operations[revision]]: + connection.exec_driver_sql(statement) + + def verify_declaration(self, connection: Connection, *, required: bool) -> None: + stored = read_embedding_profile(connection) + if stored is None: + if required: + raise MigrationError("embedding_profile_mismatch", "The managed vector profile declaration is missing.") + elif stored != self.profile: + raise MigrationError( + "embedding_profile_mismatch", "The configured embedding profile differs from the managed database." + ) + + def mysql_tables(self) -> dict[str, str]: + return copy.deepcopy(self.layout["mysql"]) + + +def read_embedding_profile(connection: Connection) -> EmbeddingProfile | None: + """Read a managed configuration declaration; never infer it from vector bytes.""" + if "pc_server_storage_profile" not in inspect(connection).get_table_names(): + return None + try: + rows = connection.exec_driver_sql("SELECT singleton,embedding_profile FROM pc_server_storage_profile").all() + if len(rows) != 1 or rows[0][0] != 1: + raise ValueError("The profile declaration must contain one singleton") # noqa: TRY301, TRY003 + return EmbeddingProfile.model_validate_json(rows[0][1]) + except (ValueError, TypeError) as error: + raise MigrationError( + "embedding_profile_mismatch", "The managed vector profile declaration is invalid." + ) from error + + +def load_sqlite_vector_extension(connection: Connection) -> None: + """Allow generic storage readers to validate a managed vector database.""" + try: + connection.exec_driver_sql("SELECT vec_version()") + except OperationalError as error: + if not isinstance(error.orig, sqlite3.Error) or "no such function: vec_version" not in str(error.orig): + raise + else: + return + import sqlite_vec + + driver = connection.connection.dbapi_connection + if isinstance(driver, AdaptedConnection): + + async def load(raw): + await raw.enable_load_extension(True) + try: + await raw.load_extension(sqlite_vec.loadable_path()) + finally: + await raw.enable_load_extension(False) + + driver.run_async(load) + elif isinstance(driver, sqlite3.Connection): + driver.enable_load_extension(True) + try: + sqlite_vec.load(driver) + finally: + driver.enable_load_extension(False) + else: + raise MigrationError("unsupported_backend", "The managed SQLite vector profile needs a supported driver.") diff --git a/src/powercontext/builtin/persistence/oceanbase/profile.py b/src/powercontext/builtin/persistence/oceanbase/profile.py index c8c0a88879..bf8b779b90 100644 --- a/src/powercontext/builtin/persistence/oceanbase/profile.py +++ b/src/powercontext/builtin/persistence/oceanbase/profile.py @@ -121,6 +121,7 @@ async def open( config: OceanBaseConfig, *, tables: tuple[Table, ...], + _archive_recovery: bool = False, ) -> AsyncIterator[OceanBaseProfile]: """Create, initialize and exclusively own an official OceanBase engine.""" @@ -137,7 +138,9 @@ async def open( pool_pre_ping=config.pool_pre_ping, ) database = AsyncDatabase.own(engine) - async with _initialized_profile(cls(database=database, tables=tables)) as profile: + async with _initialized_profile( + cls(database=database, tables=tables), _archive_recovery=_archive_recovery + ) as profile: yield profile @classmethod @@ -157,11 +160,13 @@ async def attach( @asynccontextmanager -async def _initialized_profile(profile: OceanBaseProfile) -> AsyncIterator[OceanBaseProfile]: +async def _initialized_profile( + profile: OceanBaseProfile, *, _archive_recovery: bool = False +) -> AsyncIterator[OceanBaseProfile]: try: async with profile.database.transaction() as connection: await _require_mysql_tenant(connection) - await assert_dream_schema_ready(profile.database.engine) + await assert_dream_schema_ready(profile.database.engine, _archive_recovery=_archive_recovery) if profile.tables: await prepare_topic_memory_recovery_schema( profile.database.engine, initialize=any(table.name == BUDGET for table in profile.tables) diff --git a/src/powercontext/builtin/persistence/schema.py b/src/powercontext/builtin/persistence/schema.py index 57c0f97112..527dfbb27d 100644 --- a/src/powercontext/builtin/persistence/schema.py +++ b/src/powercontext/builtin/persistence/schema.py @@ -18,13 +18,22 @@ from collections.abc import Sequence -from sqlalchemy import MetaData, Table, text +from sqlalchemy import MetaData, Table, inspect, text from sqlalchemy.ext.asyncio import AsyncConnection async def create_tables(connection: AsyncConnection, tables: Sequence[Table], /) -> None: - """Create missing tables and indexes using SQLAlchemy's native metadata support.""" + """Create selected tables only for unmanaged storage; managed schemas are read-only.""" + names = await connection.run_sync(lambda sync: set(inspect(sync).get_table_names())) + if "pc_schema_revision" in names: + if not {table.name for table in tables} <= names: + from powercontext.builtin.persistence.migrations.models import MigrationError + + raise MigrationError( + "unsupported_configuration", "The versioned database does not cover the requested feature tables." + ) + return groups: dict[MetaData, list[Table]] = {} for table in tables: groups.setdefault(table.metadata, []).append(table) diff --git a/src/powercontext/builtin/persistence/seekdb/profile.py b/src/powercontext/builtin/persistence/seekdb/profile.py index e3a39f05c9..3e6227c302 100644 --- a/src/powercontext/builtin/persistence/seekdb/profile.py +++ b/src/powercontext/builtin/persistence/seekdb/profile.py @@ -109,6 +109,7 @@ async def open( config: SeekDBConfig, *, tables: tuple[Table, ...], + _archive_recovery: bool = False, ) -> AsyncIterator[SeekDBProfile]: """Start seekdb locally and connect through its async Unix socket.""" @@ -121,7 +122,7 @@ async def open( database = AsyncDatabase.own(engine) profile = cls(database=database, tables=tables, connection_options=instance.connection_options()) try: - await assert_dream_schema_ready(engine) + await assert_dream_schema_ready(engine, _archive_recovery=_archive_recovery) if tables: await prepare_topic_memory_recovery_schema( engine, initialize=any(table.name == BUDGET for table in tables) diff --git a/src/powercontext/builtin/persistence/sqlite/atomic_memory_index.py b/src/powercontext/builtin/persistence/sqlite/atomic_memory_index.py index f11d9298e0..da9007df74 100644 --- a/src/powercontext/builtin/persistence/sqlite/atomic_memory_index.py +++ b/src/powercontext/builtin/persistence/sqlite/atomic_memory_index.py @@ -281,6 +281,17 @@ async def rebuild_fts(self, connection: AsyncConnection, /) -> None: f"INSERT INTO {_FTS_TABLE} ({names}) SELECT {values} FROM pc_atomic_memory_current" # noqa: S608 ) + async def fts_matches_current(self, connection: AsyncConnection, /) -> bool: + """Compare all helper values and multiplicities, including stable identity tokens.""" + names = ", ".join(_FTS_COLUMNS) + values = ", ".join(_FTS_IDENTITY_SQL if name == "identity_token" else name for name in _FTS_COLUMNS) + expected = f"SELECT {values}, COUNT(*) FROM pc_atomic_memory_current GROUP BY {values}" # noqa: S608 + actual = f"SELECT {names}, COUNT(*) FROM {_FTS_TABLE} GROUP BY {names}" # noqa: S608 + for left, right in ((expected, actual), (actual, expected)): + if (await connection.exec_driver_sql(f"SELECT 1 FROM ({left} EXCEPT {right}) LIMIT 1")).first(): # noqa: S608 + return False + return True + async def search( self, connection: AsyncConnection, scope_id: str, request: AtomicMemorySearchRequest, / ) -> AtomicMemorySearchChannels: diff --git a/src/powercontext/builtin/persistence/sqlite/profile.py b/src/powercontext/builtin/persistence/sqlite/profile.py index e1eb513f66..236c4a0d57 100644 --- a/src/powercontext/builtin/persistence/sqlite/profile.py +++ b/src/powercontext/builtin/persistence/sqlite/profile.py @@ -97,6 +97,7 @@ async def open( *, tables: tuple[Table, ...], load_vector_extension: bool = False, + _archive_recovery: bool = False, ) -> AsyncIterator[SQLiteProfile]: """Create, initialize and exclusively own one SQLite engine.""" @@ -114,7 +115,7 @@ async def open( database = AsyncDatabase.own(engine, shared_connection=not config.is_persistent) profile = cls(database=database, tables=tables) try: - await assert_dream_schema_ready(engine) + await assert_dream_schema_ready(engine, _archive_recovery=_archive_recovery) if tables: await prepare_topic_memory_recovery_schema( engine, initialize=any(table.name == BUDGET for table in tables) diff --git a/src/powercontext/builtin/persistence/tag_schema.py b/src/powercontext/builtin/persistence/tag_schema.py index b0b98a64d5..aac906140e 100644 --- a/src/powercontext/builtin/persistence/tag_schema.py +++ b/src/powercontext/builtin/persistence/tag_schema.py @@ -22,7 +22,7 @@ async def ensure_topic_memory_tag_schema(connection: AsyncConnection) -> None: - """Upgrade the existing tag table during serialized deployment startup.""" + """Remove the legacy family constraint inside the caller's write transaction.""" if connection.dialect.name == "sqlite": # Start a real write transaction before DDL, including legacy sqlite3 mode. @@ -31,15 +31,15 @@ async def ensure_topic_memory_tag_schema(connection: AsyncConnection) -> None: if ddl is None or "ck_pc_artifact_tags_family" not in ddl: return create = str(CreateTable(ARTIFACT_TAGS_TABLE).compile(dialect=connection.dialect)) - await connection.exec_driver_sql( - create.replace("CREATE TABLE pc_artifact_tags", "CREATE TABLE pc_artifact_tags_topic_memory") - ) + # Create the final name directly: SQLite's rename rewrites its DDL with + # quotes, which would differ from the registered Server schema. + await connection.exec_driver_sql("ALTER TABLE pc_artifact_tags RENAME TO pc_artifact_tags_topic_memory") + await connection.exec_driver_sql(create) columns = ", ".join(column.name for column in ARTIFACT_TAGS_TABLE.columns) await connection.exec_driver_sql( - f"INSERT INTO pc_artifact_tags_topic_memory ({columns}) SELECT {columns} FROM pc_artifact_tags" # noqa: S608 + f"INSERT INTO pc_artifact_tags ({columns}) SELECT {columns} FROM pc_artifact_tags_topic_memory" # noqa: S608 ) - await connection.exec_driver_sql("DROP TABLE pc_artifact_tags") - await connection.exec_driver_sql("ALTER TABLE pc_artifact_tags_topic_memory RENAME TO pc_artifact_tags") + await connection.exec_driver_sql("DROP TABLE pc_artifact_tags_topic_memory") for index in ARTIFACT_TAGS_TABLE.indexes: await connection.run_sync(index.create) return diff --git a/src/powercontext/builtin/persistence/topic_memory.py b/src/powercontext/builtin/persistence/topic_memory.py index 970c18c973..cc795869df 100644 --- a/src/powercontext/builtin/persistence/topic_memory.py +++ b/src/powercontext/builtin/persistence/topic_memory.py @@ -94,7 +94,9 @@ def __init__( self.index = NoTopicMemoryIndex() if index is None else index self.recall_channel_weights = recall_channel_weights - async def initialize(self, connection: AsyncConnection, /, *, configure_retrieval_shape: bool = True) -> None: + async def initialize( + self, connection: AsyncConnection, /, *, configure_retrieval_shape: bool = True, validate_only: bool = False + ) -> None: """Initialize indexes and reject incomplete historical Topic projections. Only deployment startup may configure an empty store. Workers must @@ -125,11 +127,12 @@ async def initialize(self, connection: AsyncConnection, /, *, configure_retrieva if missing_publication is not None: raise TopicMemoryStorageInvariantError("missing-publication", tuple(missing_publication)) - if configure_retrieval_shape: + if configure_retrieval_shape and not validate_only: await self._ensure_retrieval_shape(connection, allow_empty_change=True) else: await self._check_retrieval_shape(connection) - await self.index.initialize(connection) + if not validate_only: + await self.index.initialize(connection) orphan_active = ( await connection.execute( diff --git a/src/powercontext/builtin/runtime/atomic_memory_rebuild.py b/src/powercontext/builtin/runtime/atomic_memory_rebuild.py index 716480b6e3..a02c6d36e8 100644 --- a/src/powercontext/builtin/runtime/atomic_memory_rebuild.py +++ b/src/powercontext/builtin/runtime/atomic_memory_rebuild.py @@ -17,6 +17,7 @@ from __future__ import annotations import json +from collections.abc import Callable from hashlib import sha256 from time import perf_counter @@ -236,6 +237,37 @@ async def _initialize_rebuild(connection: AsyncConnection, index: AtomicMemoryIn await index.reconfigure_vector_column(connection) +async def rebuild_atomic_memory_current( + connection: AsyncConnection, + index: AtomicMemoryIndex, + *, + batch_size: int = 100, + guard: Callable[[], None] = lambda: None, +) -> AtomicMemoryProjectionRebuildReport: + """Repair registered no-vector rows inside the maintenance owner's transaction. + + The caller validates the existing schema, owns backup/recovery and commits + only after full Server validation. No initialization or external I/O occurs. + """ + if index.capabilities.embedding_profile is not None: + raise AtomicMemoryIndexError("embedding-profile", "Registered repair requires a no-vector profile") + await _require_authority(connection) + artifacts = ArtifactRepository((AtomicMemory,)) + publisher = AtomicMemoryProjectionPublisher(index) + after = None + while batch := await _identity_batch(connection, after, batch_size): + guard() + for scope_id, artifact_id in batch: + record = await _record(connection, artifacts, scope_id, artifact_id) + if record.state.state is AtomicMemoryStateValue.ACTIVE: + await publisher.publish(connection, scope_id, record, await publisher.prepare(record.artifact.content)) + else: + await publisher.remove(connection, scope_id, artifact_id) + after = scope_id, artifact_id + await connection.execute(index.table.delete().where(_obsolete_current(index))) + return await verify_atomic_memory_current(connection, index, batch_size=batch_size) + + async def rebuild_atomic_memory_projection( database: AsyncDatabase, index: AtomicMemoryIndex, @@ -325,4 +357,9 @@ async def rebuild_atomic_memory_projection( ) -__all__ = ["AtomicMemoryProjectionRebuildReport", "rebuild_atomic_memory_projection", "verify_atomic_memory_current"] +__all__ = [ + "AtomicMemoryProjectionRebuildReport", + "rebuild_atomic_memory_current", + "rebuild_atomic_memory_projection", + "verify_atomic_memory_current", +] diff --git a/src/powercontext/builtin/runtime/composition.py b/src/powercontext/builtin/runtime/composition.py index 34a8ba23e8..f21913b68f 100644 --- a/src/powercontext/builtin/runtime/composition.py +++ b/src/powercontext/builtin/runtime/composition.py @@ -29,7 +29,7 @@ from urllib.parse import urlsplit from pydantic import AnyHttpUrl, JsonValue, SecretStr -from sqlalchemy import select +from sqlalchemy import inspect, select from sqlalchemy.ext.asyncio import AsyncConnection from typing_extensions import override @@ -1103,7 +1103,7 @@ async def _open_artifact_processing_supervisor( @asynccontextmanager -async def open_builtin_contexts( +async def open_builtin_contexts( # noqa: C901 -- Backend admission and resource lifetimes stay together. config: BuiltinConfig, *, candidate_pipeline: CandidatePipeline | AtomicMemoryGenerationPipeline | None = None, @@ -1146,36 +1146,54 @@ async def open_builtin_contexts( database, tables=BUILTIN_TABLES + topic_index.tables + atomic_index.tables, load_vector_extension=embedding_model is not None, + _archive_recovery=_archive_recovery, ) as profile: async with profile.database.transaction() as connection: - # This block is SQLite-only. Reserve SQLite's single-writer slot - # before startup schema reads, so concurrent Runtime openings cannot - # both establish a read snapshot and fail while upgrading to a write. - await connection.exec_driver_sql("BEGIN IMMEDIATE") - await bootstrap_processing_schema(connection, canonical_processing_manifest(config)) - await assert_processing_schema_ready(connection, canonical_processing_manifest(config)) - await ensure_skill_distribution_schema(connection) - await ensure_topic_memory_tag_schema(connection) - await ensure_scope_search_schema(connection) - await ensure_artifact_revision_metadata_schema(connection) - await ensure_portable_timestamp_precision(connection, BUILTIN_TABLES) - # A Topic child reuses its parent's schema. It never reads or - # writes Memory/Experience projections; rebuilding their FTS - # indexes here would take the shared SQLite write lock once - # per Window. Normal runtime startup retains index recovery. - if not _topic_memory_worker: - await _initialize_atomic_memory_authority(connection) - await atomic_index.initialize(connection) + managed = await connection.run_sync(lambda sync: inspect(sync).has_table("pc_schema_revision")) + if managed: + # Managed startup only validates; keep one read snapshot + # without reserving SQLite's writer slot. + await connection.exec_driver_sql("BEGIN") + else: + # Unmanaged bootstrap may write. Recheck after taking the + # writer slot in case a concurrent initializer registered it. + await connection.exec_driver_sql("BEGIN IMMEDIATE") + managed = await connection.run_sync(lambda sync: inspect(sync).has_table("pc_schema_revision")) + if managed: + await assert_processing_schema_ready(connection, canonical_processing_manifest(config)) await atomic_index.validate_current(connection) - await refresh_lexical_projections( - connection, atomic_index.table if atomic_index.table in atomic_index.tables else None + await _initialize_archive_aware_topics( + connection, + topic_index, + worker=_topic_memory_worker, + recovery=_archive_recovery, + validate_only=True, + ) + else: + await bootstrap_processing_schema(connection, canonical_processing_manifest(config)) + await assert_processing_schema_ready(connection, canonical_processing_manifest(config)) + await ensure_skill_distribution_schema(connection) + await ensure_topic_memory_tag_schema(connection) + await ensure_scope_search_schema(connection) + await ensure_artifact_revision_metadata_schema(connection) + await ensure_portable_timestamp_precision(connection, BUILTIN_TABLES) + # A Topic child reuses its parent's schema. It never reads or + # writes Memory/Experience projections; rebuilding their FTS + # indexes here would take the shared SQLite write lock once + # per Window. Normal runtime startup retains index recovery. + if not _topic_memory_worker: + await _initialize_atomic_memory_authority(connection) + await atomic_index.initialize(connection) + await atomic_index.validate_current(connection) + await refresh_lexical_projections( + connection, atomic_index.table if atomic_index.table in atomic_index.tables else None + ) + await ensure_artifact_head_searchable_text(connection) + await assert_atomic_memory_migration_ready(connection) + await experience_index.initialize(connection) + await _initialize_archive_aware_topics( + connection, topic_index, worker=_topic_memory_worker, recovery=_archive_recovery ) - await ensure_artifact_head_searchable_text(connection) - await assert_atomic_memory_migration_ready(connection) - await experience_index.initialize(connection) - await _initialize_archive_aware_topics( - connection, topic_index, worker=_topic_memory_worker, recovery=_archive_recovery - ) contexts = RelationalContexts( model_usage_observer=model_usage_observer, database=profile.database, @@ -1226,34 +1244,42 @@ async def open_builtin_contexts( atomic_index = OceanBaseAtomicMemoryIndex(None if embedding_model is None else embedding_model.profile) tables = BUILTIN_TABLES + topic_index.tables + atomic_index.tables if isinstance(database, OceanBaseConfig): - profile_context = OceanBaseProfile.open(database, tables=tables) + profile_context = OceanBaseProfile.open(database, tables=tables, _archive_recovery=_archive_recovery) elif isinstance(database, SeekDBConfig): - profile_context = SeekDBProfile.open(database, tables=tables) + profile_context = SeekDBProfile.open(database, tables=tables, _archive_recovery=_archive_recovery) else: raise BuiltinConfigurationError("database") async with profile_context as profile: async with profile.database.transaction() as connection: await ensure_supported_atomic_memory_schema(connection) - await bootstrap_processing_schema(connection, canonical_processing_manifest(config)) - await assert_processing_schema_ready(connection, canonical_processing_manifest(config)) - await ensure_skill_distribution_schema(connection) - await ensure_topic_memory_tag_schema(connection) - await ensure_scope_search_schema(connection) - await ensure_artifact_revision_metadata_schema(connection) - await ensure_portable_timestamp_precision(connection, BUILTIN_TABLES) - if not _topic_memory_worker: - await _initialize_atomic_memory_authority(connection) - await atomic_index.initialize(connection) + managed = await connection.run_sync(lambda sync: inspect(sync).has_table("pc_schema_revision")) + if managed: + await assert_processing_schema_ready(connection, canonical_processing_manifest(config)) await atomic_index.validate_current(connection) - await refresh_lexical_projections( - connection, atomic_index.table if atomic_index.table in atomic_index.tables else None + await _initialize_archive_aware_topics( + connection, topic_index, worker=_topic_memory_worker, recovery=_archive_recovery, validate_only=True + ) + else: + await bootstrap_processing_schema(connection, canonical_processing_manifest(config)) + await assert_processing_schema_ready(connection, canonical_processing_manifest(config)) + await ensure_skill_distribution_schema(connection) + await ensure_topic_memory_tag_schema(connection) + await ensure_scope_search_schema(connection) + await ensure_artifact_revision_metadata_schema(connection) + await ensure_portable_timestamp_precision(connection, BUILTIN_TABLES) + if not _topic_memory_worker: + await _initialize_atomic_memory_authority(connection) + await atomic_index.initialize(connection) + await atomic_index.validate_current(connection) + await refresh_lexical_projections( + connection, atomic_index.table if atomic_index.table in atomic_index.tables else None + ) + await ensure_artifact_head_searchable_text(connection) + await assert_atomic_memory_migration_ready(connection) + await experience_index.initialize(connection) + await _initialize_archive_aware_topics( + connection, topic_index, worker=_topic_memory_worker, recovery=_archive_recovery ) - await ensure_artifact_head_searchable_text(connection) - await assert_atomic_memory_migration_ready(connection) - await experience_index.initialize(connection) - await _initialize_archive_aware_topics( - connection, topic_index, worker=_topic_memory_worker, recovery=_archive_recovery - ) contexts = RelationalContexts( model_usage_observer=model_usage_observer, database=profile.database, @@ -1308,7 +1334,7 @@ async def _contexts_lifecycle(contexts: RelationalContexts, /, *, recovery: bool async def _initialize_archive_aware_topics( - connection: AsyncConnection, index: TopicMemoryIndex, *, worker: bool, recovery: bool + connection: AsyncConnection, index: TopicMemoryIndex, *, worker: bool, recovery: bool, validate_only: bool = False ) -> None: pending = await connection.scalar( select(PORTABLE_RESTORE_RECEIPTS_TABLE.c.bundle_id) @@ -1318,9 +1344,21 @@ async def _initialize_archive_aware_topics( if pending and (not recovery or worker): raise BundleRecoveryRequiredError() if pending: - await index.initialize(connection) + if validate_only: + from powercontext.builtin.persistence.migrations.models import MigrationError + from powercontext.builtin.persistence.migrations.vector_profile import read_embedding_profile + + stored = await connection.run_sync(read_embedding_profile) + if stored != index.capabilities.embedding_profile: + raise MigrationError( + "embedding_profile_mismatch", "Archive recovery must use the managed vector profile." + ) + else: + await index.initialize(connection) else: - await TopicMemoryRepository(index=index).initialize(connection, configure_retrieval_shape=not worker) + await TopicMemoryRepository(index=index).initialize( + connection, configure_retrieval_shape=not worker, validate_only=validate_only + ) def _register_prompt_demonstrators( diff --git a/src/powercontext/server/cli.py b/src/powercontext/server/cli.py index 9e65dca352..782ff5afa3 100644 --- a/src/powercontext/server/cli.py +++ b/src/powercontext/server/cli.py @@ -26,6 +26,8 @@ import typer from pydantic import ValidationError +from sqlalchemy import inspect +from sqlalchemy.engine import make_url from sqlalchemy.exc import OperationalError from powercontext.builtin.persistence.oceanbase import OceanBaseConfig, OceanBaseProfile @@ -44,6 +46,7 @@ from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest from powercontext.cli.env_file import environment_context from powercontext.cli.inference_notice import write_inference_capability_notice +from powercontext.server import database_migration from powercontext.server.app import access_readiness_checks from powercontext.server.authz import PrincipalRef from powercontext.server.authz.composition import open_builtin_access_control @@ -117,17 +120,122 @@ def processing_migrate( str, typer.Option(help="Stable resume ID; use a new ID for an offline mode switch.") ] = "rfc1515", batch_size: Annotated[int, typer.Option(min=1, max=10000, help="Maximum rows committed per migration step.")] = 100, + evidence_dir: database_migration.EvidenceDir = None, + lock_coordination: database_migration.LockCoordination = None, + backup: Annotated[ + database_migration.BackupPolicy | None, typer.Option(help="Managed storage backup policy.") + ] = None, + plan_id: Annotated[str | None, typer.Option(help="Exact managed Processing plan to accept.")] = None, + yes: Annotated[bool, typer.Option("--yes", help="Accept the managed Processing plan.")] = False, + backup_confirmed: Annotated[bool, typer.Option(help="Declare a manual backup exists.")] = False, + accept_no_backup: Annotated[bool, typer.Option(help="Accept the no-backup risk.")] = False, + backup_ref: Annotated[str | None, typer.Option(help="Operator manual backup reference.")] = None, + acknowledge_previous_run: Annotated[ + str | None, typer.Option(help="OceanBase token confirming the previous remote maintenance ended.") + ] = None, ) -> None: """Plan, apply or verify the resumable artifact-processing schema migration.""" if action == "apply" and not maintenance_confirmed: raise typer.BadParameter("apply requires --maintenance-confirmed after stopping old workers and writes") # noqa: TRY003 + env_file = resolve_server_environment_file(env_file, discover=True) + with database_migration._operator_errors(), server_settings_context(env_file=env_file) as settings: + managed = _managed_storage(settings.database) + if managed: + if action == "plan": + database_migration.plan( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + backup=backup or "auto", + bundle="processing", + migration_id=migration_id, + batch_size=batch_size, + ) + elif action == "verify": + database_migration.verify( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + bundle="processing", + migration_id=migration_id, + batch_size=batch_size, + ) + else: + database_migration.apply( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + bundle="processing", + migration_id=migration_id, + batch_size=batch_size, + backup=backup, + plan_id=plan_id, + yes=yes, + maintenance_confirmed=maintenance_confirmed, + backup_confirmed=backup_confirmed, + accept_no_backup=accept_no_backup, + backup_ref=backup_ref, + acknowledge_previous_run=acknowledge_previous_run, + ) + return with server_settings_context(env_file=env_file) as settings: ready = asyncio.run(_processing_maintenance(settings, action, migration_id=migration_id, batch_size=batch_size)) if not ready: raise typer.Exit(code=1) +def _managed_storage( + database: SQLiteConfig | SeekDBConfig | OceanBaseConfig, *, allow_historical: bool = False +) -> bool: + """Inspect ownership before a compatibility command may open a writable profile.""" + from powercontext.builtin.persistence.migrations.connections import MaintenanceConnections + from powercontext.builtin.persistence.migrations.mysql_schema import mysql_inventory + from powercontext.builtin.persistence.migrations.server_baselines import ( + recognize_mysql_server_baseline, + recognize_sqlite_server_baseline, + ) + + def owned(connection, backend): + if connection is None: + return False + if "pc_schema_revision" in inspect(connection).get_table_names(): + return True + source = ( + recognize_sqlite_server_baseline(connection) + if backend == "sqlite" + else recognize_mysql_server_baseline(mysql_inventory(connection), backend=backend) + ) + if source is not None and not allow_historical: + raise database_migration.MigrationError( + "migration_required", + "Complete the registered full Server upgrade before Processing configuration maintenance.", + ) + return source is not None + + if isinstance(database, SQLiteConfig) and not database.is_persistent: + return False + if not isinstance(database, SQLiteConfig) or not make_url(database.url).query: + return MaintenanceConnections(database).run( + lambda connection, identity, guard: owned(connection, identity.product) + ) + + async def inspect_sqlite(): + try: + async with ( + SQLiteProfile.open_readonly(database) as profile, + profile.database.transaction() as connection, + ): + return await connection.run_sync(lambda sync: owned(sync, "sqlite")) + except OperationalError as error: + code = getattr(error.orig, "sqlite_errorcode", None) + if isinstance(code, int) and code & 0xFF == SQLITE_CANTOPEN: + return False + raise + + return asyncio.run(inspect_sqlite()) + + async def _processing_maintenance( settings: ServerSettings, action: Literal["plan", "apply", "verify"], @@ -230,6 +338,19 @@ def atomic_memory_migrate( Path | None, typer.Option(help="JSON attestations establishing Owners for ownerless legacy Memory entries."), ] = None, + evidence_dir: database_migration.EvidenceDir = None, + lock_coordination: database_migration.LockCoordination = None, + backup: Annotated[ + database_migration.BackupPolicy | None, typer.Option(help="Managed storage backup policy.") + ] = None, + plan_id: Annotated[str | None, typer.Option(help="Exact managed Server plan to accept.")] = None, + yes: Annotated[bool, typer.Option("--yes", help="Accept the managed Server plan.")] = False, + backup_confirmed: Annotated[bool, typer.Option(help="Declare a manual backup exists.")] = False, + accept_no_backup: Annotated[bool, typer.Option(help="Accept the no-backup risk.")] = False, + backup_ref: Annotated[str | None, typer.Option(help="Operator manual backup reference.")] = None, + acknowledge_previous_run: Annotated[ + str | None, typer.Option(help="OceanBase token confirming the previous remote maintenance ended.") + ] = None, ) -> None: """Archive Memory v1, convert its history and references to Atomic Memory, and remove it from public tables.""" @@ -247,6 +368,45 @@ def atomic_memory_migrate( ) except (OSError, ValueError, TypeError, KeyError) as error: raise typer.BadParameter(f"invalid decision file: {error}") from error # noqa: TRY003 + env_file = resolve_server_environment_file(env_file, discover=True) + with database_migration._operator_errors(), server_settings_context(env_file=env_file) as settings: + managed = _managed_storage(settings.database, allow_historical=True) + if managed: + if decisions is not None or owner_decisions is not None: + raise typer.BadParameter( # noqa: TRY003 + "decision files apply only to the unmanaged legacy Memory conversion, not the registered Server chain" + ) + if action == "plan": + database_migration.plan( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + backup=backup or "auto", + bundle="server", + ) + elif action == "verify": + database_migration.verify( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + bundle="server", + ) + else: + database_migration.apply( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + bundle="server", + backup=backup, + plan_id=plan_id, + yes=yes, + maintenance_confirmed=maintenance_confirmed, + backup_confirmed=backup_confirmed, + accept_no_backup=accept_no_backup, + backup_ref=backup_ref, + acknowledge_previous_run=acknowledge_previous_run, + ) + return with server_settings_context(env_file=env_file) as settings: try: ready = asyncio.run( @@ -276,13 +436,69 @@ def atomic_memory_migrate( @app.command("atomic-memory-rebuild-projection") def atomic_memory_rebuild_projection( env_file: Annotated[Path | None, typer.Option(help="Load the deployment's settings.")] = None, + action: Annotated[ + Literal["plan", "apply", "verify"], typer.Option(help="Managed projection maintenance action.") + ] = "apply", maintenance_confirmed: Annotated[ bool, typer.Option(help="Confirm every API, host and Worker is stopped and input writes are paused.") ] = False, batch_size: Annotated[int, typer.Option(min=1, max=1000, help="Identities read per maintenance batch.")] = 100, + evidence_dir: database_migration.EvidenceDir = None, + lock_coordination: database_migration.LockCoordination = None, + backup: Annotated[ + database_migration.BackupPolicy | None, typer.Option(help="Managed storage backup policy.") + ] = None, + plan_id: Annotated[str | None, typer.Option(help="Exact managed projection plan to accept.")] = None, + yes: Annotated[bool, typer.Option("--yes", help="Accept the managed projection plan.")] = False, + backup_confirmed: Annotated[bool, typer.Option(help="Declare a manual backup exists.")] = False, + accept_no_backup: Annotated[bool, typer.Option(help="Accept the no-backup risk.")] = False, + backup_ref: Annotated[str | None, typer.Option(help="Operator manual backup reference.")] = None, + acknowledge_previous_run: Annotated[ + str | None, typer.Option(help="OceanBase token confirming the previous remote maintenance ended.") + ] = None, ) -> None: """Rebuild active Atomic Memory search rows from existing authority.""" + env_file = resolve_server_environment_file(env_file, discover=True) + with database_migration._operator_errors(), server_settings_context(env_file=env_file) as settings: + managed = _managed_storage(settings.database) + if managed: + if action == "plan": + database_migration.plan( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + backup=backup or "auto", + bundle="atomic-projection", + batch_size=batch_size, + ) + elif action == "verify": + database_migration.verify( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + bundle="atomic-projection", + batch_size=batch_size, + ) + else: + database_migration.apply( + env_file=env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + bundle="atomic-projection", + batch_size=batch_size, + backup=backup, + plan_id=plan_id, + yes=yes, + maintenance_confirmed=maintenance_confirmed, + backup_confirmed=backup_confirmed, + accept_no_backup=accept_no_backup, + backup_ref=backup_ref, + acknowledge_previous_run=acknowledge_previous_run, + ) + return + if action != "apply": + raise typer.BadParameter("plan and verify require managed Server storage") # noqa: TRY003 if not maintenance_confirmed: raise typer.BadParameter("projection rebuild requires --maintenance-confirmed after stopping every writer") # noqa: TRY003 with server_settings_context(env_file=env_file) as settings: @@ -541,6 +757,9 @@ async def _run_background_async(settings: ServerSettings, tracing: Any) -> None: inference=settings.inference, external_skills=settings.external_skills, ) + from powercontext.builtin.persistence.migrations.server_readiness import prepare_server_storage + + await asyncio.to_thread(prepare_server_storage, settings) metrics = ServerMetrics(role="background") if settings.metrics.enabled else None stopped = asyncio.Event() loop = asyncio.get_running_loop() diff --git a/src/powercontext/server/database_migration.py b/src/powercontext/server/database_migration.py index 26775ca5fc..3242833b3a 100644 --- a/src/powercontext/server/database_migration.py +++ b/src/powercontext/server/database_migration.py @@ -33,12 +33,14 @@ resolve_server_environment_file, server_settings_context, ) +from powercontext.server.settings import ServerSettings from powercontext.service.controller import ServiceController +from powercontext.service.environment import ProtectedEnvironmentFileError, load_protected_environment_file from powercontext.service.model import ServiceError BackupPolicy = Literal["auto", "manual", "skip"] Bundle = Annotated[ - Literal["artifacts", "topic-memory-recovery"], + Literal["server", "artifacts", "topic-memory-recovery", "processing", "atomic-projection"], typer.Option(help="Select the registered domain bundle; readiness covers only that bundle."), ] EnvFile = Annotated[Path | None, typer.Option(help="Read the same environment file as the deployed Server.")] @@ -54,12 +56,16 @@ ] ManageService = Annotated[ bool, - typer.Option(help="Request local service maintenance; unavailable for the current partial acceptance bundle."), + typer.Option(help="Coordinate the owned local service through a full Server migration and readiness check."), ] SharedDatabase = Annotated[ bool, typer.Option(help="Declare other Servers share this database; coordinate all writers through deployment tooling."), ] +MigrationID = Annotated[ + str, typer.Option(help="Processing maintenance resume ID; choose a new ID for each mode switch.") +] +BatchSize = Annotated[int, typer.Option(min=1, max=10000, help="Rows per Processing maintenance step.")] app = typer.Typer( name="db-migrate", @@ -79,7 +85,7 @@ def status( env_file: EnvFile = None, evidence_dir: EvidenceDir = None, lock_coordination: LockCoordination = None, - bundle: Bundle = "artifacts", + bundle: Bundle = "server", ) -> None: """Inspect the schema version without creating a database or control tables.""" _inspect(env_file, evidence_dir=evidence_dir, lock_coordination=lock_coordination, verify=False, bundle=bundle) @@ -90,10 +96,20 @@ def verify( env_file: EnvFile = None, evidence_dir: EvidenceDir = None, lock_coordination: LockCoordination = None, - bundle: Bundle = "artifacts", + bundle: Bundle = "server", + migration_id: MigrationID = "rfc1515", + batch_size: BatchSize = 100, ) -> None: """Verify database readiness; this does not assert Server or cluster readiness.""" - _inspect(env_file, evidence_dir=evidence_dir, lock_coordination=lock_coordination, verify=True, bundle=bundle) + _inspect( + env_file, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + verify=True, + bundle=bundle, + migration_id=migration_id, + batch_size=batch_size, + ) @app.command() @@ -104,14 +120,23 @@ def plan( backup: Annotated[BackupPolicy, typer.Option(help="Bind the selected backup policy to the plan.")] = "auto", manage_service: ManageService = False, shared_database: SharedDatabase = False, - bundle: Bundle = "artifacts", + bundle: Bundle = "server", + migration_id: MigrationID = "rfc1515", + batch_size: BatchSize = 100, ) -> None: """Preview the target, resource digests, backup policy, and maintenance scope.""" with _operator_errors(), server_settings_context(env_file=_environment_file(env_file)) as settings: runner = deployment_runner( - settings, evidence_dir=evidence_dir, lock_coordination=lock_coordination, bundle=bundle + settings, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + bundle=bundle, + migration_id=migration_id, + batch_size=batch_size, + ) + service = ( + _service_scope(_environment_file(env_file), bundle=bundle, settings=settings) if manage_service else None ) - service = _service_scope(_environment_file(env_file)) if manage_service else None reviewed = runner.plan(backup_policy=backup, service=service, shared_database=shared_database) _write_payload(reviewed) @@ -150,7 +175,9 @@ def apply( backup_ref: Annotated[str | None, typer.Option(help="Optional reference for the operator's manual backup.")] = None, manage_service: ManageService = False, shared_database: SharedDatabase = False, - bundle: Bundle = "artifacts", + bundle: Bundle = "server", + migration_id: MigrationID = "rfc1515", + batch_size: BatchSize = 100, ) -> None: """Review once, then stop writes, back up, migrate, verify, and optionally switch a local service.""" with _operator_errors(), server_settings_context(env_file=_environment_file(env_file)) as settings: @@ -160,14 +187,18 @@ def apply( lock_coordination=lock_coordination, acknowledge_previous_run=acknowledge_previous_run, bundle=bundle, + migration_id=migration_id, + batch_size=batch_size, ) policy: BackupPolicy = backup or "auto" initial = runner.plan(backup_policy=policy, shared_database=shared_database) _check_previous_run(initial, acknowledge_previous_run) - if initial.state == "ready": + if initial.state == "ready" and (not manage_service or bundle != "server"): _write_payload(runner.verify()) return - service = _service_scope(_environment_file(env_file)) if manage_service else None + service = ( + _service_scope(_environment_file(env_file), bundle=bundle, settings=settings) if manage_service else None + ) reviewed = runner.plan(backup_policy=policy, service=service, shared_database=shared_database) _check_previous_run(reviewed, acknowledge_previous_run) if policy == "auto" and not reviewed.backup_available and reviewed.state != "uninitialized": @@ -214,11 +245,18 @@ def _inspect( evidence_dir: Path | None, lock_coordination: Literal["single-host"] | None, verify: bool, - bundle: Literal["artifacts", "topic-memory-recovery"] = "artifacts", + bundle: Literal["server", "artifacts", "topic-memory-recovery", "processing", "atomic-projection"] = "server", + migration_id: str = "rfc1515", + batch_size: int = 100, ) -> None: with _operator_errors(), server_settings_context(env_file=_environment_file(env_file)) as settings: runner = deployment_runner( - settings, evidence_dir=evidence_dir, lock_coordination=lock_coordination, bundle=bundle + settings, + evidence_dir=evidence_dir, + lock_coordination=lock_coordination, + bundle=bundle, + migration_id=migration_id, + batch_size=batch_size, ) result = runner.verify() if verify else runner.plan() _write_payload(result) @@ -235,7 +273,10 @@ def _interactive() -> bool: def _write_payload(value: MigrationPlan | MigrationResult, *, indent: int | None = None) -> None: typer.echo( json.dumps( - {**value.model_dump(mode="json"), "server_ready": False}, + { + **value.model_dump(mode="json"), + "server_ready": value.readiness_scope == "server" and value.state == "ready", + }, indent=indent, ) ) @@ -243,9 +284,8 @@ def _write_payload(value: MigrationPlan | MigrationResult, *, indent: int | None def _write_confirmation(reviewed: MigrationPlan, policy: BackupPolicy) -> None: _write_payload(reviewed, indent=2) - typer.echo( - "This acceptance bundle does not verify complete Server schema, task formats, indexes or startup readiness." - ) + if reviewed.readiness_scope != "server": + typer.echo("This partial bundle does not verify full Server readiness.") typer.echo("Maintenance pauses writes. Stop all other Servers, Workers, SDK clients and automatic restarts first.") typer.echo("PC cannot automatically discover every node or client connected to this database.") if reviewed.coordination and reviewed.coordination.get("mode") == "single-host": @@ -271,12 +311,31 @@ def _write_confirmation(reviewed: MigrationPlan, policy: BackupPolicy) -> None: typer.echo("Old tables and recovery points are retained.") -def _service_scope(env_file: Path | None) -> dict[str, object]: - del env_file - raise MigrationError( - "service_unsupported", - "This partial acceptance bundle cannot establish complete Server readiness or switch a business service.", - ) +def _service_scope(env_file: Path | None, *, bundle: str, settings: ServerSettings) -> dict[str, object]: + if bundle != "server": + raise MigrationError( + "service_unsupported", + "This partial acceptance bundle cannot establish complete Server readiness or switch a business service.", + ) + summary = ServiceController().maintenance_summary(env_file=env_file) + registered = summary.definition + try: + environment = ( + {} + if registered.env_file is None + else load_protected_environment_file(Path(registered.env_file.path), expected=registered.env_file).values + ) + except ProtectedEnvironmentFileError as error: + raise MigrationError( + "service_configuration_mismatch", "The registered service environment changed; review its configuration." + ) from error + with server_settings_context(environment=environment, data_dir=Path(registered.data_dir)) as service_settings: + if settings != service_settings: + raise MigrationError( + "service_configuration_mismatch", + "Use the registered service environment and data directory for its database migration.", + ) + return summary.as_dict() def _apply_reviewed( @@ -309,6 +368,7 @@ def migrate() -> MigrationResult: raise MigrationError("service_unsupported", "The local service does not provide a stable maintenance identity.") with ServiceController().maintenance(expected_fingerprint=fingerprint, env_file=env_file) as session: result = migrate() + runner.verify() try: session.complete() except (ServiceError, OSError): diff --git a/src/powercontext/server/factory.py b/src/powercontext/server/factory.py index af56ad2ee7..a58a725eb9 100644 --- a/src/powercontext/server/factory.py +++ b/src/powercontext/server/factory.py @@ -180,6 +180,13 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: _log_insecure_remote_http_warning() if isinstance(config.database, SQLiteConfig) and config.database.is_in_memory: _log_in_memory_database_warning() + from powercontext.builtin.persistence.migrations.server_readiness import prepare_server_storage + + await asyncio.to_thread( + prepare_server_storage, + resolved, + embedding_profile=None if embedding_model is None else embedding_model.profile, + ) configured_cursor_secret = ( None if resolved.cursor_signing_secret is None else resolved.cursor_signing_secret.get_secret_value() ) diff --git a/src/powercontext/server/identity.py b/src/powercontext/server/identity.py index f98bcc50f1..c351bce867 100644 --- a/src/powercontext/server/identity.py +++ b/src/powercontext/server/identity.py @@ -22,12 +22,13 @@ from typing import TypeVar from uuid import uuid4 -from sqlalchemy import CheckConstraint, Column, Integer, MetaData, String, Table, insert, select, update +from sqlalchemy import CheckConstraint, Column, Integer, MetaData, String, Table, insert, inspect, select, update from sqlalchemy.exc import IntegrityError, OperationalError from sqlalchemy.schema import CreateTable from powercontext.builtin.persistence.database import AsyncDatabase from powercontext.builtin.persistence.oceanbase import OceanBaseConfig, OceanBaseProfile +from powercontext.builtin.persistence.schema import create_tables from powercontext.builtin.persistence.seekdb import SeekDBConfig, SeekDBProfile from powercontext.builtin.persistence.sqlite import SQLiteConfig, SQLiteProfile, is_sqlite_lock_error from powercontext.builtin.runtime.config import DatabaseConfig @@ -54,12 +55,17 @@ def __init__(self, database: AsyncDatabase) -> None: self._database = database async def initialize(self) -> None: - """Create the identity schema safely across concurrent initializers.""" + """Check managed tables or safely initialize an unmanaged identity schema.""" await _retry_initialization(self._initialize) async def _initialize(self) -> None: async with self._database.transaction() as connection: + if await connection.run_sync(lambda sync: inspect(sync).has_table("pc_schema_revision")): + # Managed profiles validate the full bundle before admission. + # This shared check refuses missing tables without startup DDL. + await create_tables(connection, (SERVER_IDENTITY_TABLE,)) + return await connection.execute(CreateTable(SERVER_IDENTITY_TABLE, if_not_exists=True)) async def load_or_create(self) -> str: diff --git a/tests/builtin/persistence/test_database_migration_domains.py b/tests/builtin/persistence/test_database_migration_domains.py new file mode 100644 index 0000000000..41d53e3bcd --- /dev/null +++ b/tests/builtin/persistence/test_database_migration_domains.py @@ -0,0 +1,306 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Registered domain work using real SQLite revisions, data and recovery evidence. + +This isolated extensibility fixture is not a historical Server release baseline. +""" + +from __future__ import annotations + +import json +import shutil +import sqlite3 +from pathlib import Path + +import pytest +from sqlalchemy import create_engine + +from powercontext.builtin.persistence.migrations import MigrationBundle, MigrationError, SQLiteMigrationRunner +from powercontext.builtin.persistence.migrations.bundle import sqlite_inventory +from powercontext.builtin.persistence.migrations.models import digest + +_NORMALIZE = """ +from powercontext.builtin.persistence.migrations.models import MigrationError + +def preflight(connection, *, configuration): + if configuration.get("format") != "lower": + raise MigrationError("unsupported_configuration", "Select the registered text format.") + present = connection.exec_driver_sql("SELECT name FROM sqlite_schema WHERE name = 'items'").first() + if present and connection.exec_driver_sql("SELECT 1 FROM items WHERE value = 'UNSUPPORTED' LIMIT 1").first(): + raise MigrationError("unsupported_source", "Source data requires an unsupported conversion.") + +def apply(connection, *, configuration): + connection.exec_driver_sql("UPDATE items SET normalized = lower(value)") + +def verify(connection, *, configuration): + validate(connection, configuration=configuration) + +def validate(connection, *, configuration): + if connection.exec_driver_sql( + "SELECT 1 FROM items WHERE normalized IS NULL OR normalized != lower(value) LIMIT 1" + ).first(): + raise MigrationError("domain_not_ready", "Normalized source data is incomplete.") +""" + +_PROJECT = """ +from pathlib import Path +from powercontext.builtin.persistence.migrations.models import MigrationError + +def preflight(connection, *, configuration): + pass + +def apply(connection, *, configuration): + connection.exec_driver_sql("DELETE FROM projection") + connection.exec_driver_sql("INSERT INTO projection SELECT id, normalized FROM items") + +def verify(connection, *, configuration): + if configuration.get("fault_file") and Path(configuration["fault_file"]).exists(): + raise MigrationError("injected_failure", "Simulated interruption during domain verification.") + validate(connection, configuration=configuration) + +def validate(connection, *, configuration): + if connection.exec_driver_sql( + "SELECT 1 FROM items i LEFT JOIN projection p ON p.id = i.id " + "WHERE p.id IS NULL OR p.value != i.normalized LIMIT 1" + ).first() or connection.exec_driver_sql( + "SELECT 1 FROM projection p LEFT JOIN items i ON i.id = p.id WHERE i.id IS NULL LIMIT 1" + ).first(): + raise MigrationError("projection_not_ready", "Projection differs from authoritative data.") +""" + + +def _bundle(directory: Path, *, head: int = 3) -> MigrationBundle: + directory.mkdir() + (directory / "versions").mkdir() + (directory / "domains").mkdir() + fixture = Path(__file__).resolve().parents[2] / "fixtures/database_migrations" + shutil.copyfile(fixture / "env.py", directory / "env.py") + statements = ( + "CREATE TABLE items (id INTEGER PRIMARY KEY, value TEXT NOT NULL)", + "ALTER TABLE items ADD COLUMN normalized TEXT", + "CREATE TABLE projection (id INTEGER PRIMARY KEY, value TEXT NOT NULL)", + "CREATE INDEX ix_projection_value ON projection(value)", + ) + fingerprints = {} + engine = create_engine("sqlite://") + try: + with engine.begin() as connection: + for number, statement in enumerate(statements[:head], start=1): + revision = f"d{number}" + parent = f"d{number - 1}" if number > 1 else None + (directory / "versions" / f"{revision}.py").write_text( + f"from alembic import op\nrevision = {revision!r}\ndown_revision = {parent!r}\n" + f"def upgrade():\n op.get_bind().exec_driver_sql({statement!r})\n", + encoding="utf-8", + ) + connection.exec_driver_sql(statement) + fingerprints[revision] = [digest(sqlite_inventory(connection))] + finally: + engine.dispose() + (directory / "domains/normalize.py").write_text(_NORMALIZE, encoding="utf-8") + (directory / "domains/project.py").write_text(_PROJECT, encoding="utf-8") + steps = [ + {"id": "normalize", "revision": "d2", "kind": "data", "module": "domains/normalize.py", "backends": ["sqlite"]}, + { + "id": "project", + "revision": "d3", + "kind": "projection", + "module": "domains/project.py", + "depends_on": ["normalize"], + "backends": ["sqlite"], + }, + ] + if head == 4: + # A later release registers more work without changing the planner or executor. + steps.append({**steps[1], "id": "reproject", "revision": "d4", "depends_on": ["project"]}) + manifest = { + "revision_resources": {revision: [] for revision in fingerprints}, + "sqlite_fingerprints": fingerprints, + "sqlite_baselines": {fingerprints["d1"][0]: "d1"}, + "affected_objects": ["items", "projection"], + # Deliberately unordered: execution follows schema order and dependencies. + "domain_steps": list(reversed([step for step in steps if int(step["revision"][1:]) <= head])), + "domain_readiness": {f"d{i}": ["normalize"] + (["project"] if i >= 3 else []) for i in range(2, head + 1)}, + } + (directory / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8") + return MigrationBundle(directory) + + +def _legacy(database: Path) -> None: + with sqlite3.connect(database) as connection: + connection.execute("CREATE TABLE items (id INTEGER PRIMARY KEY, value TEXT NOT NULL)") + connection.executemany("INSERT INTO items VALUES (?, ?)", [(1, "FIRST"), (2, "Second")]) + + +def _apply(runner: SQLiteMigrationRunner): + return runner.apply(plan_id=runner.plan().plan_id, accepted=True, maintenance_confirmed=True) + + +def _rows(database: Path, sql: str): + with sqlite3.connect(database) as connection: + return connection.execute(sql).fetchall() + + +def test_registered_schema_data_and_projection_upgrade_preserves_authority(tmp_path: Path) -> None: + bundle = _bundle(tmp_path / "bundle") + database = tmp_path / "pc.db" + _legacy(database) + runner = SQLiteMigrationRunner(database, bundle, configuration={"format": "lower"}) + plan = runner.plan() + assert plan.revisions == ("d2", "d3") + assert plan.domain_steps == ("normalize", "project") + assert plan.domain_validators == ("normalize", "project") + result = _apply(runner) + assert result.changed and result.backup_ref + assert _rows(database, "SELECT * FROM items ORDER BY id") == [(1, "FIRST", "first"), (2, "Second", "second")] + assert _rows(database, "SELECT * FROM projection ORDER BY id") == [(1, "first"), (2, "second")] + assert runner.verify().revision == "d3" + assert not runner.apply().changed + assert _rows(Path(result.backup_ref), "SELECT * FROM items ORDER BY id") == [(1, "FIRST"), (2, "Second")] + + +def test_preflight_rejects_incompatible_configuration_before_writes(tmp_path: Path) -> None: + bundle = _bundle(tmp_path / "bundle") + database = tmp_path / "pc.db" + _legacy(database) + original = database.read_bytes() + runner = SQLiteMigrationRunner(database, bundle, configuration={"format": "unknown"}) + with pytest.raises(MigrationError, match="unsupported_configuration"): + runner.apply() + assert database.read_bytes() == original + assert not runner.evidence_path.exists() + + +def test_changed_source_data_is_rechecked_before_applying_an_accepted_plan(tmp_path: Path) -> None: + bundle = _bundle(tmp_path / "bundle") + database = tmp_path / "pc.db" + _legacy(database) + configuration = {"format": "lower"} + runner = SQLiteMigrationRunner(database, bundle, configuration=configuration) + accepted = runner.plan() + configuration["format"] = "unreviewed" + assert runner.plan() == accepted + with sqlite3.connect(database) as connection: + connection.execute("UPDATE items SET value = 'UNSUPPORTED' WHERE id = 1") + original = database.read_bytes() + with pytest.raises(MigrationError, match="unsupported_source"): + runner.apply(plan_id=accepted.plan_id, accepted=True, maintenance_confirmed=True) + assert database.read_bytes() == original + assert not runner.evidence_path.exists() + + +def test_failed_domain_verification_rolls_back_revision_and_reuses_original_backup(tmp_path: Path) -> None: + bundle = _bundle(tmp_path / "bundle") + database = tmp_path / "pc.db" + _legacy(database) + fault = tmp_path / "interrupt" + fault.touch() + runner = SQLiteMigrationRunner(database, bundle, configuration={"format": "lower", "fault_file": str(fault)}) + with pytest.raises(MigrationError, match="injected_failure"): + _apply(runner) + assert _rows(database, "SELECT version_num FROM pc_schema_revision") == [("d2",)] + assert not _rows(database, "SELECT name FROM sqlite_schema WHERE name = 'projection'") + original = json.loads(runner.evidence_path.read_text())["backup"] + fault.unlink() + assert runner.plan().state == "recovery_required" + assert runner.plan().domain_steps == ("project",) + result = _apply(runner) + assert result.backup_ref == original["location"] + assert runner.verify().revision == "d3" + fault.touch() + # Ordinary verification uses live invariants, not migration-copy verification. + assert runner.verify().revision == "d3" + + +@pytest.mark.parametrize( + "corruption, error", + [ + ("DELETE FROM projection WHERE id = 1", "projection_not_ready"), + ("UPDATE items SET normalized = NULL WHERE id = 1", "domain_not_ready"), + ], +) +def test_schema_head_alone_does_not_establish_readiness(tmp_path: Path, corruption: str, error: str) -> None: + bundle = _bundle(tmp_path / "bundle") + database = tmp_path / "pc.db" + _legacy(database) + runner = SQLiteMigrationRunner(database, bundle, configuration={"format": "lower"}) + _apply(runner) + with sqlite3.connect(database) as connection: + connection.execute(corruption) + assert _rows(database, "SELECT version_num FROM pc_schema_revision") == [("d3",)] + with pytest.raises(MigrationError, match=error): + runner.verify() + with pytest.raises(MigrationError, match=error): + runner.apply() + + +def test_subsequent_release_and_direct_upgrade_produce_equivalent_state(tmp_path: Path) -> None: + prior = _bundle(tmp_path / "prior") + subsequent = _bundle(tmp_path / "subsequent", head=4) + assert all(subsequent.checksums[revision] == checksum for revision, checksum in prior.checksums.items()) + sequential_db, direct_db = tmp_path / "sequential.db", tmp_path / "direct.db" + for database in (sequential_db, direct_db): + _legacy(database) + _apply(SQLiteMigrationRunner(sequential_db, prior, configuration={"format": "lower"})) + sequential = SQLiteMigrationRunner(sequential_db, subsequent, configuration={"format": "lower"}) + assert sequential.plan().domain_steps == ("reproject",) + _apply(sequential) + direct = SQLiteMigrationRunner(direct_db, subsequent, configuration={"format": "lower"}) + _apply(direct) + assert sequential.verify().revision == direct.verify().revision == "d4" + for query in ("SELECT * FROM items ORDER BY id", "SELECT * FROM projection ORDER BY id"): + assert _rows(sequential_db, query) == _rows(direct_db, query) + + +def test_released_domain_resources_cannot_be_rewritten(tmp_path: Path) -> None: + bundle = _bundle(tmp_path / "bundle") + database = tmp_path / "pc.db" + runner = SQLiteMigrationRunner(database, bundle, configuration={"format": "lower"}) + _apply(runner) + module = bundle.directory / "domains/normalize.py" + module.write_text(_NORMALIZE + "\n# changed historical resource\n", encoding="utf-8") + replacement = SQLiteMigrationRunner(database, MigrationBundle(bundle.directory), configuration={"format": "lower"}) + with pytest.raises(MigrationError, match="checksum_conflict"): + replacement.plan() + + +@pytest.mark.parametrize( + "defect", ["cycle", "future_dependency", "missing_resource", "missing_readiness", "unknown_field"] +) +def test_invalid_registration_rejected_before_database_access(tmp_path: Path, defect: str) -> None: + bundle = _bundle(tmp_path / "bundle") + path = bundle.directory / "manifest.json" + manifest = json.loads(path.read_text()) + project, normalize = manifest["domain_steps"] + if defect == "cycle": + project["revision"] = "d2" + normalize["depends_on"] = ["project"] + elif defect == "future_dependency": + normalize["depends_on"] = ["project"] + elif defect == "missing_resource": + normalize["resources"] = ["missing.json"] + elif defect == "missing_readiness": + del manifest["domain_readiness"]["d3"] + else: + normalize["depend_on"] = ["project"] + path.write_text(json.dumps(manifest), encoding="utf-8") + with pytest.raises(MigrationError, match="invalid_bundle"): + MigrationBundle(bundle.directory) + + +def test_unregistered_backend_fails_before_execution(tmp_path: Path) -> None: + bundle = _bundle(tmp_path / "bundle") + with pytest.raises(MigrationError, match="unsupported_upgrade_path"): + bundle.domains.pending("d1", "seekdb") diff --git a/tests/builtin/persistence/test_full_server_migrations.py b/tests/builtin/persistence/test_full_server_migrations.py new file mode 100644 index 0000000000..5c4b975d60 --- /dev/null +++ b/tests/builtin/persistence/test_full_server_migrations.py @@ -0,0 +1,319 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Complete frozen Server upgrades, rollback, recovery and admission behavior.""" + +import asyncio +import hashlib +import json +import sqlite3 +from contextlib import closing +from pathlib import Path + +import pytest +from alembic import command +from sqlalchemy import create_engine + +from powercontext.builtin.persistence.dream_schema import check_dream_schema +from powercontext.builtin.persistence.migrations.deployment import server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.sqlite import SQLiteMigrationRunner +from powercontext.builtin.persistence.sqlite import SQLiteConfig, SQLiteProfile +from powercontext.builtin.persistence.supervision import ArtifactProcessingLeaseRepository +from powercontext.builtin.runtime.config import BuiltinConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest + + +@pytest.fixture +def source(tmp_path): + database = tmp_path / "source.db" + configuration = {"processing_manifest": canonical_processing_manifest(BuiltinConfig()), "embedding_enabled": False} + bundle = server_bundle() + engine = create_engine(f"sqlite:///{database}") + with engine.connect() as connection: + connection.exec_driver_sql("PRAGMA foreign_keys=OFF") + connection.exec_driver_sql("BEGIN IMMEDIATE") + config = bundle.config(connection) + config.attributes["configuration"] = configuration + command.upgrade(config, "s1300") + connection.exec_driver_sql("DROP TABLE pc_schema_revision") + connection.exec_driver_sql( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version) VALUES ('scope','title','summary','scope','title','summary',1)" + ) + connection.exec_driver_sql( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content) VALUES ('scope','custom-family','historical',1,?)", + (b'{"history":"unchanged"}',), + ) + connection.commit() + engine.dispose() + return SQLiteMigrationRunner(database, bundle, configuration=configuration) + + +def apply(runner): + plan = runner.plan() + return runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + + +def dump(path): + with closing(sqlite3.connect(path)) as connection: + return tuple(connection.iterdump()) + + +def test_complete_upgrade_preserves_history_backup_and_repeated_apply(source): + before = dump(source.database) + plan = source.plan() + assert plan.source_revision == "s1300" and plan.target_revision == "s1402" + assert plan.revisions == ("s1400", "s1401", "s1402") + assert dump(source.database) == before + result = apply(source) + assert result.readiness_scope == "server" and result.backup_state == "completed" + assert result.backup_ref and Path(result.backup_ref).exists() + with closing(sqlite3.connect(source.database)) as connection: + assert connection.execute( + "SELECT content,created_at,created_by,restored_from_revision,rollback_reason FROM pc_artifacts" + ).fetchall() == [(b'{"history":"unchanged"}', None, None, None, None)] + after = dump(source.database) + assert not apply(source).changed + assert source.verify().readiness_scope == "server" + assert dump(source.database) == after + + +def test_archived_memory_history_survives_repeat_apply_and_runtime_admission(source): + apply(source) + metadata = json.dumps({ + "format": "powercontext.memory-artifact-archive.v1", + "lineage": {"sources": [], "artifacts": []}, + "incoming_references": [], + "collection": {"head": None, "owners": [], "tags": [], "bindings": [], "idempotency": []}, + }).encode() + archived = ("scope", "memory", "legacy", 2, b'{"history":"unchanged"}', b"[]", metadata) + with closing(sqlite3.connect(source.database)) as connection, connection: + connection.execute("INSERT INTO pc_memory_artifact_archive VALUES (?,?,?,?,?,?,?)", archived) + assert not apply(source).changed + assert source.verify().readiness_scope == "server" + + async def read_archive(): + config = SQLiteConfig(url=f"sqlite+aiosqlite:///{source.database}") + async with ( + SQLiteProfile.open(config, tables=()) as profile, + profile.database.transaction() as connection, + ): + rows = await connection.exec_driver_sql("SELECT * FROM pc_memory_artifact_archive") + assert [tuple(row) for row in rows] == [archived] + + asyncio.run(read_archive()) + + +def test_migration_revokes_old_fence_and_allows_expiring_worker_lease(source): + with closing(sqlite3.connect(source.database)) as connection, connection: + connection.execute( + "INSERT INTO pc_artifact_processing_leases (supervisor_group,holder_id,supervisor_generation,lease_expires_at) VALUES ('global','old-worker',7,NULL)" + ) + apply(source) + + async def scenario(): + repository = ArtifactProcessingLeaseRepository() + config = SQLiteConfig(url=f"sqlite+aiosqlite:///{source.database}") + async with SQLiteProfile.open(config, tables=()) as profile: + async with profile.database.transaction() as connection: + acquired = await repository.try_acquire(connection, "new-worker", 15) + assert acquired is not None, "The migration fence must allow an OceanBase-style lease takeover" + assert acquired.supervisor_generation > 7 + assert acquired.lease_expires_at is not None + + asyncio.run(scenario()) + + +def test_domain_failure_rolls_back_and_reuses_original_recovery_point(source, monkeypatch): + original = source.bundle.domains._invoke + + def interrupted(step, operation, connection, configuration): + original(step, operation, connection, configuration) + if step.id == "server_upgrade" and operation == "verify": + raise MigrationError("injected_failure", "Interrupted before revision advancement") + + with monkeypatch.context() as patch: + patch.setattr(source.bundle.domains, "_invoke", interrupted) + with pytest.raises(MigrationError, match="injected_failure"): + apply(source) + evidence = json.loads(source.evidence_path.read_text()) + with closing(sqlite3.connect(source.database)) as connection: + assert connection.execute("SELECT version_num FROM pc_schema_revision").fetchall() == [("s1300",)] + assert connection.execute("SELECT name FROM sqlite_schema WHERE name='pc_candidate_heads'").fetchall() == [] + assert source.plan().state == "recovery_required" + result = apply(source) + assert result.backup_ref == evidence["backup"]["location"] + assert source.verify().state == "ready" + + +def test_unknown_legacy_task_blocks_before_control_table_and_backup(source): + with closing(sqlite3.connect(source.database)) as connection, connection: + connection.execute( + "INSERT INTO pc_dream_runs (scope_id,run_id,principal_key,idempotency_key,request_digest,operation,status,accepted_at,generation,request_generation,payload) VALUES ('scope','run','principal','key','digest','derive_skill','queued',0,0,0,?)", + (b"{}",), + ) + before = hashlib.sha256(source.database.read_bytes()).hexdigest() + with pytest.raises(MigrationError, match="unsupported_task_format"): + source.plan() + assert hashlib.sha256(source.database.read_bytes()).hexdigest() == before + assert not source.evidence_path.exists() + + +def test_incompatible_processing_declaration_blocks_before_writes(source): + source.configuration["processing_manifest"]["capabilities"] = ["unknown-family"] + before = dump(source.database) + with pytest.raises(MigrationError, match="incompatible_target_processing_manifest"): + source.plan() + assert dump(source.database) == before + assert not source.evidence_path.exists() + + +def test_unknown_database_object_is_not_ignored(source): + with closing(sqlite3.connect(source.database)) as connection, connection: + connection.execute("CREATE TABLE user_extension (id INTEGER PRIMARY KEY)") + with pytest.raises(MigrationError, match="unknown_baseline"): + source.plan() + assert not source.evidence_path.exists() + + +def test_recorded_head_does_not_authorize_schema_repair_at_startup(source): + apply(source) + with closing(sqlite3.connect(source.database)) as connection, connection: + connection.execute("DROP INDEX ix_pc_dream_runs_proposal_fingerprint") + before = dump(source.database) + with pytest.raises(MigrationError, match="incompatible_schema"): + source.verify() + engine = create_engine(f"sqlite:///{source.database}") + try: + with engine.connect() as connection, pytest.raises(MigrationError, match="incompatible_schema"): + check_dream_schema(connection) + finally: + engine.dispose() + assert dump(source.database) == before + + +def test_processing_change_after_upgrade_is_not_compared_to_retained_snapshot(source): + apply(source) + with closing(sqlite3.connect(source.database)) as connection, connection: + connection.execute( + "INSERT INTO pc_artifact_processing_intents (binding_name,scope_id,pending_sequence,dirty_generation,clean_generation,requested_generation,handled_generation,last_auto_scan_generation,consecutive_dream_attempts) VALUES ('topic-memory-source-window','scope',1,3,1,2,1,1,4)" + ) + assert source.verify().state == "ready" + + +def test_missing_full_text_rows_fail_live_verification_without_repair(source): + apply(source) + with closing(sqlite3.connect(source.database)) as connection, connection: + connection.execute( + "INSERT INTO pc_artifact_fts (scope_id,family,artifact_id,revision,searchable_text) VALUES ('scope','skill','stale',1,'text')" + ) + before = dump(source.database) + with pytest.raises(MigrationError, match="projection_verification_failed"): + source.verify() + assert dump(source.database) == before + + +def test_optional_configuration_cannot_silently_extend_managed_database(source): + from powercontext.builtin.persistence.migrations.server_readiness import prepare_server_storage + from powercontext.builtin.persistence.sqlite import SQLiteConfig + from powercontext.server.settings import ServerSettings + + apply(source) + before = dump(source.database) + settings = ServerSettings(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{source.database}")) + with pytest.raises(MigrationError, match="unsupported_upgrade_configuration"): + prepare_server_storage(settings, injected_embedding=True) + assert dump(source.database) == before + + +def test_code_with_separate_cache_upgrades_and_reopens_server(source, tmp_path): + from fastapi.testclient import TestClient + + from powercontext.builtin.code import CodeConfig + from powercontext.builtin.persistence.migrations.deployment import deployment_runner + from powercontext.server.factory import create_server_app + from powercontext.server.settings import McpConfig, ServerSettings + + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{source.database}"), + code=CodeConfig(enabled=True, cache_dir=tmp_path / "code-cache"), + mcp=McpConfig(enabled=False), + ) + runner = deployment_runner(settings) + assert runner.plan().source_revision == "s1300" + apply(runner) + assert runner.verify().readiness_scope == "server" + app = create_server_app(settings=settings, scheduler_path=tmp_path / "scheduler.db") + with TestClient(app) as client: + assert client.get("/health/ready").status_code == 200 + response = client.get("/v1/scopes") + assert response.status_code == 200 + assert any(scope["scope_id"] == "scope" for scope in response.json()["items"]) + code = client.post("/v1/scopes/scope/code/query", json={"operation": {"kind": "status"}}) + assert code.status_code == 200 + assert code.json()["status"] == "disabled" + assert code.json()["reason"] == "code_repository_unconfigured" + assert not apply(deployment_runner(settings)).changed + with closing(sqlite3.connect(source.database)) as connection: + assert connection.execute("SELECT content FROM pc_artifacts WHERE artifact_id='historical'").fetchall() == [ + (b'{"history":"unchanged"}',) + ] + + +@pytest.mark.parametrize("bundle", ["processing", "atomic-projection"]) +def test_partial_vector_maintenance_is_rejected_before_opening_database(tmp_path, bundle): + from powercontext.builtin.persistence.migrations.deployment import deployment_runner + from powercontext.builtin.runtime.config import InferenceConfig + from powercontext.server.settings import ServerSettings + + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'server.db'}"), + inference=InferenceConfig( + embedding_model="openai:fixture", embedding_profile_id="fixture", embedding_dimension=3 + ), + ) + with pytest.raises(MigrationError, match="unsupported_upgrade_configuration"): + deployment_runner(settings, bundle=bundle) + assert list(tmp_path.iterdir()) == [] + + +def test_captured_oceanbase_baseline_preserves_ordered_audit_cursor(): + import copy + + from powercontext.builtin.persistence.migrations.mysql_schema import MySQLSchema + + root = Path(__file__).parents[3] + inventory = json.loads( + ( + root / "src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/oceanbase.json" + ).read_text() + )["inventory"] + schema = MySQLSchema(server_bundle()) + assert schema.identify(inventory) == "s1300" + changed = copy.deepcopy(inventory) + changed["tables"]["pc_access_audit"]["auto_increment_mode"] = "noorder" + with pytest.raises(MigrationError, match="incompatible_schema"): + schema.identify(changed) + + +def test_unregistered_processing_intent_blocks_before_any_upgrade(source): + with closing(sqlite3.connect(source.database)) as connection, connection: + connection.execute( + "INSERT INTO pc_artifact_processing_intents (binding_name,scope_id,pending_sequence,dirty_generation,clean_generation,requested_generation,handled_generation,last_auto_scan_generation) VALUES ('unknown-handler','scope',1,0,0,1,0,0)" + ) + before = dump(source.database) + with pytest.raises(MigrationError, match="unsupported_task_format"): + source.plan() + assert dump(source.database) == before + assert not source.evidence_path.exists() diff --git a/tests/builtin/persistence/test_mysql_initialization_preflight.py b/tests/builtin/persistence/test_mysql_initialization_preflight.py new file mode 100644 index 0000000000..2c97dc4f78 --- /dev/null +++ b/tests/builtin/persistence/test_mysql_initialization_preflight.py @@ -0,0 +1,151 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Recovery planning with modeled MySQL DDL and real domain seed checks. + +These tests do not establish native backend acceptance. +""" + +import copy +import json + +import pytest +from alembic import command +from pydantic import SecretStr +from sqlalchemy import create_engine + +from powercontext.builtin.persistence.migrations import mysql as migration +from powercontext.builtin.persistence.migrations import mysql_schema +from powercontext.builtin.persistence.migrations.connections import BackendIdentity, MaintenanceConnections +from powercontext.builtin.persistence.migrations.deployment import server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.mysql_schema import _parse_table +from powercontext.builtin.persistence.oceanbase import OceanBaseConfig +from powercontext.builtin.persistence.seekdb import SeekDBConfig +from powercontext.builtin.runtime.config import BuiltinConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest + + +@pytest.mark.parametrize("backend", ["seekdb", "oceanbase"]) +@pytest.mark.parametrize("damage", [None, "missing-evidence", "unregistered-table"]) +def test_initialization_recovery_defers_seed_checks_only_for_registered_prefix(tmp_path, monkeypatch, backend, damage): + directory = tmp_path / "evidence" + config = ( + SeekDBConfig(path=tmp_path / "native") + if backend == "seekdb" + else OceanBaseConfig(url=SecretStr("mysql+aoceanbase://root@localhost:2881/test?charset=utf8mb4")) + ) + connections = MaintenanceConnections( + config, evidence_directory=directory, lock_coordination="single-host" if backend == "oceanbase" else None + ) + runner = migration.MySQLMigrationRunner( + connections, + server_bundle(), + evidence_directory=directory, + configuration={ + "processing_manifest": canonical_processing_manifest(BuiltinConfig()), + "embedding_enabled": False, + }, + ) + identity = BackendIdentity(backend, "fixture-initialization", "test") + inventory = copy.deepcopy( + next(state for state in runner.schema._prefixes["s1300"] if "pc_artifact_candidate_versions" in state["tables"]) + ) + control = "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) NOT NULL, PRIMARY KEY (version_num))" + name, definition = _parse_table(control) + inventory["tables"][name] = definition + journal = migration._Journal(directory, identity) + journal.write( + migration._Evidence( + state="active", + database_id=identity.database_id, + source_revision=None, + source_fingerprint=runner.schema.fingerprint({"tables": {}, "views": [], "triggers": []}), + bundle_checksum=runner.checksum, + configuration_digest=runner.configuration_digest, + last_revision=None, + pending_revision="s1300", + maintenance_window_id="interrupted-initialization", + backup_policy="auto", + backup_state="not_required", + ) + ) + if damage == "missing-evidence": + journal.path.unlink() + elif damage == "unregistered-table": + name, definition = _parse_table("CREATE TABLE unregistered (id INTEGER PRIMARY KEY)") + inventory["tables"][name] = definition + + engine = create_engine("sqlite://") + try: + with engine.connect() as connection: + resource = json.loads((runner.bundle.snapshot_directory / "schemas/sqlite.json").read_text()) + for statement in resource["initialize"]: + connection.exec_driver_sql(statement) + connection.exec_driver_sql(control) + connection.commit() + # The baseline table DDL exists, but its final manifest seed has not + # run. Domain preflight cannot validate this intermediate state yet. + with pytest.raises(MigrationError, match="domain_validation_failed"): + runner.bundle.domains.preflight(connection, None, backend=backend, configuration=runner.configuration) + monkeypatch.setattr(migration, "mysql_inventory", lambda _connection: copy.deepcopy(inventory)) + monkeypatch.setattr(mysql_schema, "mysql_inventory", lambda _connection: copy.deepcopy(inventory)) + monkeypatch.setattr( + connections, "run", lambda operation, **_options: operation(connection, identity, lambda: None) + ) + monkeypatch.setattr(connections, "coordination", lambda _identity: {}) + if damage: + code = "recovery_required" if damage == "missing-evidence" else "incompatible_schema" + with pytest.raises(MigrationError, match=code): + runner.plan() + else: + plan = runner.plan() + assert plan.state == "recovery_required" + assert plan.source_revision is None + assert plan.revisions[0] == "s1300" + assert plan.domain_steps + native_execute = runner.schema.execute + + def execute_revision(connection, _config, _source, target, guard): + # Model native DDL with the matching frozen SQLite schema + # revisions. The public runner still uses its real domain + # checks and durable evidence throughout plan/apply/verify. + config = runner.bundle.config(connection) + config.attributes["configuration"] = runner.configuration + if target == "s1300": + native_execute(connection, "@mysql_operations.py:initialize_processing", runner.configuration) + command.stamp(config, target) + else: + command.upgrade(config, target) + runner.bundle.domains.apply( + connection, target, backend=backend, configuration=runner.configuration, guard=guard + ) + inventory.clear() + inventory.update(copy.deepcopy(runner.schema._schemas[target])) + inventory["tables"]["pc_schema_revision"] = _parse_table(control)[1] + connection.commit() + + monkeypatch.setattr(runner, "_upgrade", execute_revision) + result = runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + assert result.state == "ready" and result.revision == runner.bundle.head + assert result.backup_state == "not_required" + assert runner.verify().state == "ready" + assert not runner.apply( + plan_id=runner.plan().plan_id, accepted=True, maintenance_confirmed=True + ).changed + completed = journal.read() + assert completed is not None and completed.state == "complete" + assert completed.maintenance_window_id == "interrupted-initialization" + finally: + engine.dispose() diff --git a/tests/builtin/persistence/test_mysql_migration_checks.py b/tests/builtin/persistence/test_mysql_migration_checks.py index fc779a9d93..c0e4a7d6ce 100644 --- a/tests/builtin/persistence/test_mysql_migration_checks.py +++ b/tests/builtin/persistence/test_mysql_migration_checks.py @@ -192,6 +192,10 @@ def test_real_seekdb_percent_checks_validate_data_and_plan( layout = json.loads(path.read_text()) layout["mysql"].append(ddl) path.write_text(json.dumps(layout, ensure_ascii=False)) + path = directory / "mysql_layouts.json" + layouts = json.loads(path.read_text()) + layouts["revisions"]["p0001"].append(ddl) + path.write_text(json.dumps(layouts), encoding="utf-8") bundle = MigrationBundle(directory) schema = MySQLSchema(bundle) connections = MaintenanceConnections(SeekDBConfig(path=short_tmp_path / "seekdb")) @@ -219,6 +223,20 @@ def invalidate(connection, _identity, guard): schema.validate_data(connection, "p0001") with _seekdb_scenario_lifetime(monkeypatch): + fresh = MySQLMigrationRunner( + MaintenanceConnections(SeekDBConfig(path=short_tmp_path / "fresh_seekdb")), + bundle, + evidence_directory=short_tmp_path / "fresh_evidence", + ) + fresh_plan = fresh.plan(backup_policy="skip") + fresh.apply( + plan_id=fresh_plan.plan_id, + accepted=True, + maintenance_confirmed=True, + backup_policy="skip", + accept_no_backup=True, + ) + assert fresh.verify().state == "ready" connections.run(prepare, writable=True) assert runner.plan().state == "migration_required" connections.run(invalidate, writable=True) diff --git a/tests/builtin/persistence/test_mysql_migration_runner.py b/tests/builtin/persistence/test_mysql_migration_runner.py index 945c6b78ce..d361734fcf 100644 --- a/tests/builtin/persistence/test_mysql_migration_runner.py +++ b/tests/builtin/persistence/test_mysql_migration_runner.py @@ -20,11 +20,15 @@ import json import logging import os +import subprocess +import sys +import time from collections.abc import Callable, Iterator from contextlib import contextmanager from dataclasses import dataclass +from datetime import UTC, datetime from pathlib import Path -from typing import Literal +from typing import Literal, cast from unittest.mock import Mock from uuid import uuid4 @@ -34,16 +38,25 @@ from sqlalchemy.engine import make_url from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.pool import NullPool +from typer.testing import CliRunner +from powercontext.builtin.artifacts.atomic_memory.models import AtomicMemoryContent from powercontext.builtin.persistence.migrations import mysql as mysql_migrations from powercontext.builtin.persistence.migrations.backup import BackupCapabilities, BackupProvider from powercontext.builtin.persistence.migrations.connections import BackendIdentity, MaintenanceConnections -from powercontext.builtin.persistence.migrations.deployment import production_bundle +from powercontext.builtin.persistence.migrations.deployment import deployment_runner, production_bundle, server_bundle from powercontext.builtin.persistence.migrations.models import MigrationError, MigrationPlan from powercontext.builtin.persistence.migrations.mysql import MySQLMigrationRunner +from powercontext.builtin.persistence.migrations.mysql_schema import mysql_inventory from powercontext.builtin.persistence.oceanbase import OceanBaseConfig from powercontext.builtin.persistence.oceanbase import profile as oceanbase_profile from powercontext.builtin.persistence.seekdb import SeekDBConfig +from powercontext.builtin.persistence.topic_memory_recovery_schema import recovery_statements +from powercontext.builtin.runtime.config import BuiltinConfig, RuntimeConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest +from powercontext.cli.app import create_cli +from powercontext.server.cli import app +from powercontext.server.settings import ServerSettings @dataclass(frozen=True) @@ -255,6 +268,68 @@ def inspect(connection: Connection | None, _identity: BackendIdentity, _guard: C return backend.connections().run(inspect) +def test_mysql_topic_bundle_refuses_managed_server_storage(backend: _BackendTarget) -> None: + from powercontext.builtin.persistence.migrations.topic_memory_recovery import TopicMemoryRecoveryMigrationRunner + + def seed(connection, _identity, _guard): + connection.exec_driver_sql("CREATE TABLE pc_schema_revision (version_num VARCHAR(32) PRIMARY KEY NOT NULL)") + connection.exec_driver_sql("INSERT INTO pc_schema_revision VALUES ('unknown-future')") + for statement in recovery_statements("budget_v0", "mysql"): + connection.exec_driver_sql(statement) + connection.commit() + + backend.connections().run(seed, writable=True) + + def snapshot(connection, _identity, _guard): + assert connection is not None + return mysql_inventory(connection) + + before = backend.connections().run(snapshot) + runner = TopicMemoryRecoveryMigrationRunner( + backend.config, + evidence_directory=backend.evidence_directory, + lock_coordination="single-host" if isinstance(backend.config, OceanBaseConfig) else None, + ) + with pytest.raises(MigrationError, match="unsupported_target"): + runner.plan() + after = backend.connections().run(snapshot) + assert after == before + assert not list(backend.evidence_directory.rglob("topic-memory-recovery.json")) + + +def test_mysql_managed_identity_initialization_requires_no_ddl(backend: _BackendTarget) -> None: + from powercontext.builtin.persistence.oceanbase import OceanBaseProfile + from powercontext.builtin.persistence.seekdb import SeekDBProfile + from powercontext.server.identity import ServerIdentityRepository + + _load_full_server_baseline(backend) + _apply(_full_server_runner(backend), policy="manual") + + async def scenario(): + opened = ( + OceanBaseProfile.open(backend.config, tables=()) + if isinstance(backend.config, OceanBaseConfig) + else SeekDBProfile.open(backend.config, tables=()) + ) + async with opened as profile: + + @event.listens_for(profile.database.engine.sync_engine, "before_cursor_execute") + def reject_ddl(_connection, _cursor, statement, _parameters, _context, _executemany): + assert not statement.lstrip().upper().startswith(("CREATE ", "ALTER ", "DROP ")), ( + "Managed identity initialization must not issue DDL" + ) + + repository = ServerIdentityRepository(profile.database) + await repository.initialize() + original = await repository.load_or_create() + assert await repository.load_or_create() == original + rotated = await repository.rotate() + assert rotated != original and await repository.load_or_create() == rotated + + asyncio.run(scenario()) + assert _full_server_runner(backend).verify().state == "ready" + + def test_mysql_maintenance_commits_rolls_back_and_reopens_for_ddl(backend: _BackendTarget) -> None: def create(connection: Connection | None, _identity: BackendIdentity, _guard: Callable[[], None]) -> None: assert connection is not None @@ -372,6 +447,641 @@ def interrupt(_connection, _cursor, statement: str, _parameters, _context, _exec event.remove(Engine, "after_cursor_execute", interrupt) +def _full_server_runner( + backend: _BackendTarget, *, acknowledge_previous_run: str | None = None +) -> MySQLMigrationRunner: + return MySQLMigrationRunner( + backend.connections(acknowledge_previous_run=acknowledge_previous_run), + server_bundle(), + evidence_directory=backend.evidence_directory, + configuration={ + "processing_manifest": canonical_processing_manifest(BuiltinConfig()), + "embedding_enabled": False, + }, + ) + + +@pytest.mark.parametrize( + ("origin", "dimension", "code"), + [("released", 3, False), ("empty", 4, True)], +) +def test_mysql_vector_upgrade_preserves_space_and_registered_indexes( + backend: _BackendTarget, tmp_path: Path, origin: str, dimension: int, code: bool +) -> None: + from powercontext.builtin.artifacts.memory import EmbeddingProfile + from powercontext.builtin.code import CodeConfig + from powercontext.builtin.persistence.migrations.server_readiness import check_server_schema, prepare_server_storage + from powercontext.builtin.persistence.topic_memory_index import topic_memory_embedding_profile_fingerprint + from powercontext.builtin.runtime.config import InferenceConfig + + profile = EmbeddingProfile(profile_id="native-vectors", model="fixture", dimension=dimension) + bundle = server_bundle(embedding_profile=profile) + settings = ServerSettings( + database=backend.config, + code=CodeConfig(enabled=code, cache_dir=tmp_path / "code-cache"), + inference=InferenceConfig( + embedding_model=profile.model, + embedding_profile_id=profile.profile_id, + embedding_dimension=dimension, + ), + ) + runner = cast( + MySQLMigrationRunner, + deployment_runner( + settings, + evidence_dir=backend.evidence_directory, + lock_coordination="single-host" if isinstance(backend.config, OceanBaseConfig) else None, + ), + ) + if origin == "released": + + def initialize_source(connection, _identity, _guard): + baseline = runner.schema.recovery(mysql_inventory(connection), None) + for statement in baseline.pending_statements: + runner.schema.execute(connection, statement, runner.configuration) + connection.execute( + text("INSERT INTO pc_topic_memory_retrieval_shape VALUES (1,'hybrid',:profile)"), + {"profile": topic_memory_embedding_profile_fingerprint(profile)}, + ) + connection.commit() + + backend.connections().run(initialize_source, writable=True) + assert runner.plan(backup_policy="manual").source_revision == ("s1300" if origin == "released" else None) + assert _apply(runner, policy="manual").revision == bundle.head + assert not _apply(runner, policy="manual").changed + assert runner.verify().state == "ready" + prepare_server_storage(settings) + + def verify(connection, _identity, _guard): + check_server_schema(connection) + inventory = mysql_inventory(connection) + for name in ("pc_atomic_memory_current", "pc_topic_memory_vector_topics", "pc_topic_memory_vector_chunks"): + table = inventory["tables"][name] + assert table["columns"]["embedding"]["type"] == ["vector", [str(dimension)]] + assert any(index.get("kind") == "vector" for index in table["indexes"].values()) + assert json.loads( + connection.exec_driver_sql("SELECT embedding_profile FROM pc_server_storage_profile").scalar_one() + ) == profile.model_dump(mode="json") + + backend.connections().run(verify) + + +@pytest.mark.parametrize("backend", ("oceanbase",), indirect=True) +def test_oceanbase_server_upgrade_with_separate_code_cache_preserves_history( + backend: _BackendTarget, tmp_path: Path +) -> None: + from powercontext.builtin.code import CodeConfig + from powercontext.builtin.persistence.migrations.server_readiness import prepare_server_storage + + _load_full_server_baseline(backend) + settings = ServerSettings( + database=backend.config, + code=CodeConfig(enabled=True, cache_dir=tmp_path / "code-cache"), + ) + runner = cast( + MySQLMigrationRunner, + deployment_runner(settings, evidence_dir=backend.evidence_directory, lock_coordination="single-host"), + ) + assert runner.plan(backup_policy="manual").source_revision == "s1300" + result = _apply(runner, policy="manual") + assert result.readiness_scope == "server" and result.revision == "s1402" + assert runner.verify().state == "ready" + prepare_server_storage(settings) + assert not _apply(runner, policy="manual").changed + + def history(connection, identity, guard): + guard() + return connection.exec_driver_sql( + "SELECT content FROM pc_artifacts WHERE scope_id='historical' AND artifact_id='preserved'" + ).scalar_one() + + assert backend.connections().run(history) == b'{"history":"unchanged"}' + + +@pytest.mark.parametrize("backend", ("seekdb",), indirect=True) +@pytest.mark.parametrize("origin", ("empty", "released")) +def test_embedded_code_migration_preserves_registered_tables_and_reopens_server( + backend: _BackendTarget, tmp_path: Path, origin: str +) -> None: + from powercontext.builtin.code import CodeConfig + from powercontext.builtin.persistence.dream_schema import check_dream_schema + from powercontext.builtin.persistence.migrations.server_readiness import prepare_server_storage + + bundle = server_bundle() + profile = json.loads((bundle.snapshot_directory / "profiles/seekdb-code.json").read_text()) + names = tuple(profile["tables"]) + if origin == "released": + _load_full_server_baseline(backend) + + def populate(connection, _identity, _guard): + for statement in profile["statements"]: + connection.exec_driver_sql(statement, execution_options={"no_parameters": True}) + connection.exec_driver_sql( + "INSERT INTO pc_code_generations VALUES (%s,%s,%s,0,0,0)", + ("a" * 32, "b" * 64, "c" * 64), + ) + connection.commit() + + backend.connections().run(populate) + settings = ServerSettings( + database=backend.config, + code=CodeConfig(enabled=True, cache_dir=tmp_path / "code-cache"), + ) + runner = cast(MySQLMigrationRunner, deployment_runner(settings, evidence_dir=backend.evidence_directory)) + plan = runner.plan(backup_policy="manual") + assert plan.source_revision == ("s1300" if origin == "released" else None) + result = _apply(runner, policy="manual") + assert result.state == "ready" and result.revision == bundle.head and result.readiness_scope == "server" + assert runner.verify().state == "ready" + prepare_server_storage(settings) + + def read(connection, _identity, _guard): + check_dream_schema(connection) + inventory = mysql_inventory(connection) + assert {name: inventory["tables"][name] for name in names} == profile["tables"] + return connection.exec_driver_sql("SELECT * FROM pc_code_generations").all() + + assert backend.connections().run(read) == ( + [("a" * 32, "b" * 64, "c" * 64, 0, 0, 0)] if origin == "released" else [] + ) + assert not _apply(runner, policy="manual").changed + + def damage(connection, _identity, _guard): + connection.exec_driver_sql("DROP INDEX ix_code_name ON pc_code_nodes") + + backend.connections().run(damage) + with pytest.raises(MigrationError): + prepare_server_storage(settings) + with pytest.raises(MigrationError): + backend.connections().run(read) + + +@pytest.mark.parametrize("backend", ("seekdb",), indirect=True) +def test_code_service_cannot_add_unregistered_tables_to_managed_storage( + backend: _BackendTarget, tmp_path: Path +) -> None: + from powercontext.builtin.code import CodeConfig, CodeError + from powercontext.builtin.code.configuration import open_code_service + + _apply(_full_server_runner(backend), policy="manual") + + async def open_service(): + async with open_code_service(CodeConfig(enabled=True, cache_dir=tmp_path / "code-cache"), backend.config): + pytest.fail("Code cannot implicitly expand the managed Server schema") + + with pytest.raises(CodeError, match="code_storage_unavailable"): + asyncio.run(open_service()) + assert _full_server_runner(backend).verify().state == "ready" + + +def test_mysql_registered_processing_mode_switch_preserves_revision_and_history(backend: _BackendTarget) -> None: + _load_full_server_baseline(backend) + _apply(_full_server_runner(backend), policy="manual") + settings = ServerSettings( + database=backend.config, + runtime=RuntimeConfig(artifact_processing_supervisor_mode="dedicated"), + ) + runner = deployment_runner( + settings, + bundle="processing", + evidence_dir=backend.evidence_directory, + lock_coordination="single-host" if isinstance(backend.config, OceanBaseConfig) else None, + migration_id="native-mode-switch", + ) + plan = runner.plan(backup_policy="manual") + assert plan.state == "migration_required" + result = runner.apply( + plan_id=plan.plan_id, + accepted=True, + maintenance_confirmed=True, + backup_policy="manual", + backup_confirmed=True, + backup_ref="operator://mode-backup", + ) + assert result.backup_state == "user_confirmed" and result.backup_ref == "operator://mode-backup" + assert runner.verify().state == "ready" + assert not runner.apply().changed + + def inspect_history(connection, identity, guard): + return ( + connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalar_one(), + json.loads( + connection.exec_driver_sql( + "SELECT config_manifest FROM pc_artifact_processing_schema WHERE singleton=1" + ).scalar_one() + )["mode"], + connection.exec_driver_sql( + "SELECT content FROM pc_artifacts WHERE scope_id='historical' AND artifact_id='preserved'" + ).scalar_one(), + ) + + assert backend.connections().run(inspect_history) == ("s1402", "dedicated", b'{"history":"unchanged"}') + + +def test_mysql_atomic_compatibility_command_uses_shared_server_upgrade( + backend: _BackendTarget, monkeypatch, capsys +) -> None: + _load_full_server_baseline(backend) + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_KIND", backend.config.kind) + if isinstance(backend.config, SeekDBConfig): + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_PATH", str(backend.config.path)) + else: + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_URL", backend.config.url.get_secret_value()) + cli = create_cli([app]) + arguments = [ + "server", + "atomic-memory-migrate", + "--evidence-dir", + str(backend.evidence_directory), + "--lock-coordination", + "single-host", + ] + + def invoke(options): + # Pytest live SQL logging suspends capture during emit. Keep its + # stream switching outside Typer's independent stdout isolation. + with capsys.disabled(): + return CliRunner().invoke(cli, [*arguments, *options]) + + inspected = invoke(["--action", "plan", "--backup", "manual"]) + assert inspected.exit_code == 0, inspected.output + plan = json.loads(inspected.output) + assert plan["source_revision"] == "s1300" and plan["readiness_scope"] == "server" + applied = invoke( + [ + "--action", + "apply", + "--plan-id", + plan["plan_id"], + "--backup", + "manual", + "--backup-confirmed", + "--backup-ref", + "operator://native-atomic-backup", + "--maintenance-confirmed", + "--yes", + ], + ) + assert applied.exit_code == 0, applied.output + assert json.loads(applied.output)["backup_ref"] == "operator://native-atomic-backup" + verified = invoke(["--action", "verify"]) + assert verified.exit_code == 0, verified.output + assert json.loads(verified.output)["readiness_scope"] == "server" + repeated = invoke(["--action", "apply", "--maintenance-confirmed"]) + assert repeated.exit_code == 0, repeated.output + assert json.loads(repeated.output)["changed"] is False + + def inspect_history(connection, identity, guard): + return ( + connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalar_one(), + connection.exec_driver_sql( + "SELECT content FROM pc_artifacts WHERE scope_id='historical' AND artifact_id='preserved'" + ).scalar_one(), + ) + + assert backend.connections().run(inspect_history) == ("s1402", b'{"history":"unchanged"}') + + +def test_mysql_registered_projection_repair_preserves_authority_and_revision(backend: _BackendTarget) -> None: + from powercontext.builtin.persistence.migrations.atomic_projection import AtomicProjectionMigrationRunner + + full = _full_server_runner(backend) + plan = full.plan() + full.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + content = ( + AtomicMemoryContent(kind="fact", text="Preserve immutable nebula evidence.") + .model_dump_json(by_alias=True) + .encode() + ) + + def seed(connection, identity, guard): + connection.exec_driver_sql( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version) " + "VALUES ('scope','title','summary','scope','title','summary',1)" + ) + connection.execute( + text( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content) " + "VALUES ('scope','atomic-memory','fact',1,:content)" + ), + {"content": content}, + ) + connection.exec_driver_sql( + "INSERT INTO pc_artifact_heads (scope_id,family,artifact_id,revision) " + "VALUES ('scope','atomic-memory','fact',1)" + ) + connection.commit() + + backend.connections().run(seed, writable=True) + for inspect_state in (full.plan, full.verify): + with pytest.raises(MigrationError, match="projection_verification_failed"): + inspect_state() + runner = AtomicProjectionMigrationRunner( + backend.connections(), + full.bundle, + configuration=full.configuration, + evidence_directory=backend.evidence_directory, + batch_size=1, + ) + plan = runner.plan(backup_policy="manual") + assert plan.state == "migration_required" + result = runner.apply( + plan_id=plan.plan_id, + accepted=True, + maintenance_confirmed=True, + backup_policy="manual", + backup_confirmed=True, + backup_ref="operator://projection-recovery", + ) + assert result.backup_state == "user_confirmed" and result.backup_ref == "operator://projection-recovery" + assert runner.verify().state == "ready" + assert full.verify().state == "ready" + assert not runner.apply().changed + + def inspect_content(connection, identity, guard): + return ( + connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalar_one(), + connection.exec_driver_sql("SELECT content FROM pc_artifacts WHERE artifact_id='fact'").scalar_one(), + connection.exec_driver_sql( + "SELECT text FROM pc_atomic_memory_current WHERE artifact_id='fact'" + ).scalar_one(), + ) + + assert backend.connections().run(inspect_content) == ("s1402", content, "Preserve immutable nebula evidence.") + + +@pytest.mark.parametrize("origin", ("empty", "released")) +def test_mysql_full_server_registers_portable_storage_and_retains_timestamp_precision( + backend: _BackendTarget, origin: str +) -> None: + if origin == "released": + _load_full_server_baseline(backend) + runner = _full_server_runner(backend) + try: + assert _apply(runner, policy="manual").state == "ready" + finally: + _capture_portable_schema(backend) + assert runner.verify().state == "ready" + # Native DATETIME stores UTC values without timezone metadata. + timestamp = datetime(2026, 10, 24, 12, 34, 56, 123456, tzinfo=UTC).replace(tzinfo=None) + + def verify(connection, _identity, _guard): + inventory = mysql_inventory(connection) + receipt = inventory["tables"]["pc_portable_restore_receipts"] + assert receipt["columns"]["projections_ready"]["type"] == ["tinyint", ["1"]] + for table, column in ( + ("pc_artifacts", "created_at"), + ("pc_profile_policies", "updated_at"), + ("pc_topic_memory_revision_publications", "published_at"), + ("pc_artifact_tags", "assigned_at"), + ): + assert inventory["tables"][table]["columns"][column]["type"] == ["datetime", ["6"]] + connection.execute( + text( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content,created_at) " + "VALUES ('precision','custom-family','retained',1,:content,:created_at)" + ), + {"content": b'{"history":"retained"}', "created_at": timestamp}, + ) + connection.execute( + text( + "INSERT INTO pc_portable_restore_receipts " + "(bundle_id,format_version,total_digest,record_count,status,inserted,already_present,projections_ready,updated_at) " + "VALUES ('receipt',1,'sha256:fixture',0,'ready',0,0,1,:updated_at)" + ), + {"updated_at": timestamp}, + ) + connection.commit() + assert ( + connection.exec_driver_sql( + "SELECT projections_ready FROM pc_portable_restore_receipts WHERE bundle_id='receipt'" + ).scalar_one() + == 1 + ) + assert ( + connection.exec_driver_sql( + "SELECT created_at FROM pc_artifacts WHERE scope_id='precision' AND artifact_id='retained'" + ).scalar_one() + == timestamp + ) + + backend.connections().run(verify, writable=True) + assert not _apply(runner, policy="manual").changed + + +def _capture_portable_schema(backend: _BackendTarget) -> None: + """Keep content-free native reflection even when a new DDL prefix is rejected.""" + + def capture(connection, _identity, _guard): + names = {row[0] for row in connection.exec_driver_sql("SHOW FULL TABLES")} + tables = {} + for name in ( + "pc_portable_restore_receipts", + "pc_artifacts", + "pc_profile_policies", + "pc_topic_memory_revision_publications", + "pc_artifact_tags", + ): + if name in names: + tables[name] = connection.exec_driver_sql(f"SHOW CREATE TABLE `{name}`").one()[1] + return {"backend_version": connection.exec_driver_sql("SELECT VERSION()").scalar_one(), "tables": tables} + + try: + diagnostic = backend.connections().run(capture) + except Exception as error: + diagnostic = {"capture_error_type": type(error).__name__} + backend.evidence_directory.mkdir(parents=True, exist_ok=True) + (backend.evidence_directory / "portable-schema.json").write_text( + json.dumps(diagnostic, ensure_ascii=False, indent=2), encoding="utf-8" + ) + + +def test_mysql_full_server_resumes_interrupted_initialization_before_manifest_seed(backend: _BackendTarget) -> None: + runner = _full_server_runner(backend) + _interrupt_after_ddl(runner, "CREATE TABLE `pc_artifact_candidate_versions`", policy="auto") + (journal,) = backend.evidence_directory.rglob("maintenance.json") + interrupted = json.loads(journal.read_text()) + assert interrupted["last_revision"] is None + assert interrupted["pending_revision"] == "s1300" + recovery = _full_server_runner(backend).plan() + assert recovery.state == "recovery_required" and recovery.source_revision is None + token = (recovery.coordination or {}).get("pending_run_id") + # The injected failure occurs after the native DDL returned and the owned + # connection closed, so this test can acknowledge that exact operation. + resumed = _full_server_runner(backend, acknowledge_previous_run=token) + result = _apply(resumed) + assert result.state == "ready" and result.revision == resumed.bundle.head + assert result.backup_state == "not_required" + assert resumed.verify().state == "ready" + completed = json.loads(journal.read_text()) + assert completed["maintenance_window_id"] == interrupted["maintenance_window_id"] + assert completed["source_fingerprint"] == interrupted["source_fingerprint"] + assert completed["state"] == "complete" + + +def _load_full_server_baseline(backend: _BackendTarget) -> None: + runner = _full_server_runner(backend) + + def load(connection: Connection | None, _identity: BackendIdentity, _guard: Callable[[], None]) -> None: + assert connection is not None + source = runner.schema.recovery(mysql_inventory(connection), None) + assert source.revision == "s1300" + for statement in source.pending_statements: + runner.schema.execute(connection, statement, runner.configuration) + connection.exec_driver_sql( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version) " + "VALUES ('historical','retained title','retained summary','historical','retained title','retained summary',1)" + ) + connection.execute( + text( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content) " + "VALUES ('historical','custom-family','preserved',1,:content)" + ), + {"content": b'{"history":"unchanged"}'}, + ) + connection.commit() + + backend.connections().run(load, writable=True) + + +@pytest.mark.parametrize( + ("interruption", "recorded_revision"), + [ + ("CREATE TABLE pc_retained_dream_candidate_versions", "s1300"), + ("ALTER TABLE pc_artifact_candidate_versions RENAME TO pc_candidate_versions", "s1300"), + ("UPDATE pc_artifact_processing_schema SET config_manifest", "s1300"), + ("CREATE TABLE pc_memory_artifact_archive", "s1400"), + ], + ids=("retained-copy", "candidate-rename", "domain-processing", "next-revision"), +) +def test_mysql_full_server_interruption_preserves_history_and_original_maintenance( + backend: _BackendTarget, interruption: str, recorded_revision: str +) -> None: + """Native backend recovery covers the full Server chain, not just the Artifact prototype.""" + + _load_full_server_baseline(backend) + runner = _full_server_runner(backend) + original = runner.plan(backup_policy="manual") + assert original.source_revision == "s1300" and original.target_revision == "s1402" + _interrupt_after_ddl(runner, interruption) + journals = list(backend.evidence_directory.rglob("maintenance.json")) + assert len(journals) == 1 + interrupted = json.loads(journals[0].read_text()) + assert interrupted["state"] == "active" + assert interrupted["manual_ref"] == "operator://manual-backup" + recovery = _full_server_runner(backend).plan(backup_policy="manual") + assert recovery.state == "recovery_required" + assert recovery.source_revision == recorded_revision + token = (recovery.coordination or {}).get("pending_run_id") + if isinstance(backend.config, OceanBaseConfig): + assert token + with pytest.raises(MigrationError, match="recovery_required"): + _apply(_full_server_runner(backend), policy="manual") + # The injected exception happens after the backend returned from the + # statement and the owned connection closed. This known test interruption + # permits the required declaration; arbitrary remote interruptions do not. + resumed = _full_server_runner(backend, acknowledge_previous_run=token) + + result = _apply(resumed, policy="manual") + + assert result.readiness_scope == "server" and result.revision == "s1402" + assert result.backup_state == "user_confirmed" + assert result.backup_ref == interrupted["manual_ref"] + assert resumed.verify().state == "ready" + completed = json.loads(journals[0].read_text()) + assert completed["maintenance_window_id"] == interrupted["maintenance_window_id"] + assert completed["source_fingerprint"] == interrupted["source_fingerprint"] + assert completed["manual_ref"] == interrupted["manual_ref"] + assert completed["state"] == "complete" + + def inspect_history(connection: Connection | None, _identity: BackendIdentity, _guard: Callable[[], None]): + assert connection is not None + return ( + connection.exec_driver_sql( + "SELECT content FROM pc_artifacts WHERE scope_id='historical' AND artifact_id='preserved'" + ).all(), + connection.exec_driver_sql("SELECT title,summary FROM pc_scopes WHERE scope_id='historical'").all(), + ) + + assert backend.connections().run(inspect_history) == ( + [(b'{"history":"unchanged"}',)], + [("retained title", "retained summary")], + ) + assert not _apply(_full_server_runner(backend), policy="manual").changed + + +@pytest.mark.parametrize("backend", ("oceanbase",), indirect=True) +def test_oceanbase_full_server_client_exit_requires_acknowledgement_and_preserves_recovery( + backend: _BackendTarget, +) -> None: + """A dead remote client requires acknowledgement; the database engine stays running.""" + assert isinstance(backend.config, OceanBaseConfig) + _load_full_server_baseline(backend) + root = Path(__file__).resolve().parents[3] + environment = { + **os.environ, + "PYTHONPATH": os.pathsep.join(filter(None, (str(root), os.environ.get("PYTHONPATH")))), + "POWERCONTEXT_MIGRATION_CRASH_TARGET": backend.config.url.get_secret_value(), + } + with (backend.evidence_directory / "crashed-client.log").open("w", encoding="utf-8") as log: + crashed = subprocess.run( + [ + sys.executable, + str(root / "tests/fixtures/database_migrations/crash_after_registered_ddl.py"), + str(backend.evidence_directory), + ], + env=environment, + stdout=log, + stderr=subprocess.STDOUT, + timeout=180, + check=False, + ) + assert crashed.returncode == 73, "The migration client did not exit at the registered DDL boundary" + marker = json.loads((backend.evidence_directory / "client-exit.json").read_text(encoding="utf-8")) + journals = list(backend.evidence_directory.rglob("maintenance.json")) + assert len(journals) == 1 + interrupted = json.loads(journals[0].read_text(encoding="utf-8")) + assert interrupted["state"] == "active" and interrupted["manual_ref"] == "operator://manual-backup" + + def client_active(connection: Connection | None, _identity: BackendIdentity, _guard: Callable[[], None]): + assert connection is not None + return connection.execute( + text("SELECT ID FROM information_schema.PROCESSLIST WHERE ID=:connection_id"), + {"connection_id": marker["connection_id"]}, + ).first() + + deadline = time.monotonic() + 15 + while backend.connections().run(client_active) is not None: + assert time.monotonic() < deadline, "The terminated client's remote session did not end" + time.sleep(0.2) + recovery = _full_server_runner(backend).plan(backup_policy="manual") + assert recovery.state == "recovery_required" and recovery.source_revision == "s1300" + token = (recovery.coordination or {}).get("pending_run_id") + assert token + with pytest.raises(MigrationError, match="recovery_required"): + _apply(_full_server_runner(backend), policy="manual") + # The completed DDL and vanished remote session establish this test's known + # interruption. An arbitrary remote failure cannot be acknowledged blindly. + resumed = _full_server_runner(backend, acknowledge_previous_run=token) + result = _apply(resumed, policy="manual") + assert result.state == "ready" and result.readiness_scope == "server" and result.revision == "s1402" + assert result.backup_state == "user_confirmed" and result.backup_ref == interrupted["manual_ref"] + assert resumed.verify().state == "ready" + completed = json.loads(journals[0].read_text(encoding="utf-8")) + assert completed["state"] == "complete" + for key in ("maintenance_window_id", "source_fingerprint", "manual_ref"): + assert completed[key] == interrupted[key] + + def history(connection: Connection | None, _identity: BackendIdentity, _guard: Callable[[], None]): + assert connection is not None + return connection.exec_driver_sql( + "SELECT content FROM pc_artifacts WHERE scope_id='historical' AND artifact_id='preserved'" + ).scalar_one() + + assert backend.connections().run(history) == b'{"history":"unchanged"}' + assert not _apply(_full_server_runner(backend), policy="manual").changed + + def _interrupt_after_committed_citation_ddl(backend: _BackendTarget) -> None: _load_baseline(backend) _interrupt_after_ddl(backend.runner(), "ALTER TABLE pc_artifacts ADD COLUMN memory_citations") diff --git a/tests/builtin/persistence/test_mysql_migration_schema.py b/tests/builtin/persistence/test_mysql_migration_schema.py index 268a9a1cb8..1fd98788e1 100644 --- a/tests/builtin/persistence/test_mysql_migration_schema.py +++ b/tests/builtin/persistence/test_mysql_migration_schema.py @@ -16,9 +16,11 @@ from __future__ import annotations +import asyncio import json import os import re +import shutil from pathlib import Path from types import SimpleNamespace from typing import Any, cast @@ -26,9 +28,11 @@ import pytest from sqlalchemy import Connection, create_engine -from powercontext.builtin.persistence.migrations.bundle import MigrationBundle +from powercontext.builtin.persistence.migrations import SQLiteMigrationRunner +from powercontext.builtin.persistence.migrations.bundle import MigrationBundle, sqlite_inventory from powercontext.builtin.persistence.migrations.connections import MaintenanceConnections -from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.models import MigrationError, digest +from powercontext.builtin.persistence.migrations.mysql import MySQLMigrationRunner from powercontext.builtin.persistence.migrations.mysql_schema import MySQLSchema, mysql_inventory, read_revision from powercontext.builtin.persistence.seekdb import SeekDBConfig @@ -87,6 +91,185 @@ def schema() -> MySQLSchema: return MySQLSchema(MigrationBundle(_RESOURCES)) +_NEXT_TABLE = "CREATE TABLE pc_extension_marker (id INTEGER NOT NULL, PRIMARY KEY (id))" + + +@pytest.fixture +def extended_bundle(tmp_path: Path) -> MigrationBundle: + directory = tmp_path / "bundle" + shutil.copytree(_RESOURCES, directory) + database = tmp_path / "fingerprint.db" + runner = SQLiteMigrationRunner(database, MigrationBundle(_RESOURCES)) + runner.apply(plan_id=runner.plan().plan_id, accepted=True, maintenance_confirmed=True) + engine = create_engine("sqlite:///" + str(database)) + try: + with engine.begin() as connection: + connection.exec_driver_sql(_NEXT_TABLE) + fingerprint = digest( + sqlite_inventory(connection, exclude=frozenset({"pc_schema_revision", *runner.bundle.retained_objects})) + ) + finally: + engine.dispose() + manifest_path = directory / "manifest.json" + manifest = json.loads(manifest_path.read_text()) + manifest["revision_resources"]["fixture_next"] = ["mysql_fixture_next.json", "mysql_wrappers/fixture_next.py"] + manifest["sqlite_fingerprints"]["fixture_next"] = [fingerprint] + manifest["mysql_layouts"].append("mysql_fixture_next.json") + manifest["mysql_wrappers"] = {"fixture_next": "mysql_wrappers/fixture_next.py"} + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + (directory / "mysql_fixture_next.json").write_text( + json.dumps({ + "format": "powercontext.mysql-layouts.v1", + "revisions": {"fixture_next": [_NEXT_TABLE]}, + "checkpoints": {}, + }), + encoding="utf-8", + ) + (directory / "versions/fixture_next.py").write_text( + "from alembic import op\n" + "revision = 'fixture_next'\n" + "down_revision = 'p0004'\n" + f"def upgrade():\n op.execute({_NEXT_TABLE!r})\n", + encoding="utf-8", + ) + (directory / "mysql_wrappers").mkdir() + (directory / "mysql_wrappers/fixture_next.py").write_text( + "from alembic import context\n" + "revision = 'fixture_next'\n" + "down_revision = 'p0004'\n" + "def upgrade():\n context.config.attributes['execute_revision'](revision)\n", + encoding="utf-8", + ) + return MigrationBundle(directory) + + +def test_registered_next_revision_uses_the_same_recognition_and_sqlite_executor( + tmp_path: Path, extended_bundle: MigrationBundle +) -> None: + schema = MySQLSchema(extended_bundle) + layouts = json.loads((extended_bundle.snapshot_directory / "mysql_layouts.json").read_text()) + tables = _show_tables(citations=2, drop_family_check=True) + for revision in ("p0004",): + for statement in layouts["revisions"][revision]: + tables[statement.split()[2]] = statement + source = _ReflectedDatabase(tables) + inventory = _inventory(source) + assert schema.identify(inventory) == "p0004" + recovery = schema.recovery(inventory, "p0004") + assert recovery.revision == "fixture_next" + assert recovery.pending_statements == (_NEXT_TABLE,) + source.tables["pc_extension_marker"] = _NEXT_TABLE + inventory = _inventory(source) + assert schema.identify(inventory) == "fixture_next" + schema.verify(inventory, "fixture_next") + assert schema.recovery(inventory, "p0004").completed_revision == "fixture_next" + assert extended_bundle.pending("p0003") == ("p0004", "fixture_next") + source.tables["pc_schema_revision"] = ( + "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) NOT NULL, PRIMARY KEY (version_num))" + ) + source.heads = ["fixture_next"] + assert read_revision(cast(Connection, source), _inventory(source), revisions=extended_bundle.revisions) == ( + True, + "fixture_next", + ) + + runner = SQLiteMigrationRunner(tmp_path / "adjacent.db", MigrationBundle(_RESOURCES)) + runner.apply(plan_id=runner.plan().plan_id, accepted=True, maintenance_confirmed=True) + adjacent = SQLiteMigrationRunner(runner.database, extended_bundle) + plan = adjacent.plan() + assert plan.revisions == ("fixture_next",) + adjacent.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + direct = SQLiteMigrationRunner(tmp_path / "direct.db", extended_bundle) + direct.apply(plan_id=direct.plan().plan_id, accepted=True, maintenance_confirmed=True) + assert adjacent.verify().revision == direct.verify().revision == "fixture_next" + assert adjacent.plan().schema_fingerprint == direct.plan().schema_fingerprint + + +@pytest.mark.parametrize("change", ["missing", "unregistered", "duplicate"]) +def test_mysql_layout_registration_must_cover_exactly_the_revision_chain( + extended_bundle: MigrationBundle, change: str +) -> None: + path = extended_bundle.directory / "mysql_fixture_next.json" + layouts = json.loads(path.read_text()) + if change == "missing": + del layouts["revisions"]["fixture_next"] + elif change == "unregistered": + layouts["revisions"]["unregistered"] = [] + else: + layouts["revisions"]["p0001"] = [] + path.write_text(json.dumps(layouts), encoding="utf-8") + with pytest.raises(MigrationError, match="invalid_bundle"): + MySQLSchema(MigrationBundle(extended_bundle.directory)) + + +@pytest.mark.parametrize("change", ["missing", "predecessor", "unbound", "override"]) +def test_mysql_execution_registration_rejects_incomplete_history_before_opening_a_target( + extended_bundle: MigrationBundle, tmp_path: Path, change: str +) -> None: + path = extended_bundle.directory / "manifest.json" + manifest = json.loads(path.read_text()) + if change == "missing": + del manifest["mysql_wrappers"] + elif change == "predecessor": + wrapper = extended_bundle.directory / "mysql_wrappers/fixture_next.py" + wrapper.write_text(wrapper.read_text().replace("'p0004'", "'p0003'"), encoding="utf-8") + elif change == "unbound": + manifest["revision_resources"]["fixture_next"].remove("mysql_wrappers/fixture_next.py") + else: + manifest["mysql_wrappers"]["p0004"] = "mysql_wrappers/fixture_next.py" + path.write_text(json.dumps(manifest), encoding="utf-8") + target = tmp_path / "absent-seekdb" + evidence = tmp_path / "absent-evidence" + with pytest.raises(MigrationError, match="invalid_bundle"): + MySQLMigrationRunner( + MaintenanceConnections(SeekDBConfig(path=target)), + MigrationBundle(extended_bundle.directory), + evidence_directory=evidence, + ) + assert not target.exists() + assert not evidence.exists() + + +@pytest.mark.skipif(os.environ.get("POWERCONTEXT_TEST_MIGRATION_SEEKDB") != "1", reason="real seekdb probe not enabled") +def test_registered_next_revision_executes_directly_and_sequentially_on_real_seekdb( + extended_bundle: MigrationBundle, short_tmp_path: Path +) -> None: + binding = pytest.importorskip("pylibseekdb") + leases = [] + try: + fingerprints = [] + for adjacent in (False, True): + target = short_tmp_path / ("adjacent" if adjacent else "direct") + # Retain a native engine lease while public maintenance calls open + # independent connections; restart behavior has separate acceptance. + leases.append(asyncio.run(binding.aopen(str(target)))) + connections = MaintenanceConnections(SeekDBConfig(path=target)) + evidence = short_tmp_path / (target.name + "-evidence") + previous = None + if adjacent: + previous = MySQLMigrationRunner(connections, MigrationBundle(_RESOURCES), evidence_directory=evidence) + previous.apply(plan_id=previous.plan().plan_id, accepted=True, maintenance_confirmed=True) + runner = MySQLMigrationRunner(connections, extended_bundle, evidence_directory=evidence) + if previous is not None: + assert all(runner.checksums[revision] == checksum for revision, checksum in previous.checksums.items()) + plan = runner.plan(backup_policy="skip") + assert plan.revisions == (("fixture_next",) if adjacent else extended_bundle.revisions) + result = runner.apply( + plan_id=plan.plan_id, + backup_policy="skip", + accepted=True, + maintenance_confirmed=True, + accept_no_backup=True, + ) + assert result.changed and runner.verify().revision == "fixture_next" + assert not runner.apply().changed + fingerprints.append(runner.plan().schema_fingerprint) + assert fingerprints[0] == fingerprints[1] + finally: + for lease in leases: + lease.close() + + def _append_clause(statement: str, clause: str) -> str: return statement.replace("\n)", ",\n\t" + clause + "\n)") @@ -122,6 +305,69 @@ def _inventory(database: _ReflectedDatabase) -> dict[str, Any]: return mysql_inventory(cast(Connection, database)) +def _released_fulltext_tables() -> dict[str, str]: + path = Path(__file__).parents[2] / "fixtures/database_migrations/schemas/seekdb_v1_3_0_fulltext.json" + tables = json.loads(path.read_text(encoding="utf-8"))["tables"] + return {name: ddl.replace("`test`.", "`TestDB`.") for name, ddl in tables.items()} + + +def test_released_fulltext_keys_preserve_search_semantics_in_the_inventory() -> None: + inventory = _inventory(_ReflectedDatabase(_released_fulltext_tables())) + for name, table in inventory["tables"].items(): + assert "fulltext" not in table["columns"] + assert table["indexes"][f"ix_{name}_fts"] == { + "columns": [["searchable_text", None, "asc"]], + "unique": False, + "kind": "fulltext", + "parser": "space", + "parser_properties": {"min_token_size": 3, "max_token_size": 84}, + } + + +@pytest.mark.parametrize( + ("old", "new"), + [ + ("WITH PARSER space", "WITH PARSER ngram"), + ("min_token_size=3", "min_token_size=4"), + ("max_token_size=84", "max_token_size=80"), + ("ix_pc_artifact_heads_fts", "ix_other_fts"), + ], +) +def test_fulltext_semantic_changes_have_distinct_fingerprints(old: str, new: str) -> None: + tables = _released_fulltext_tables() + original = digest(_inventory(_ReflectedDatabase(tables))) + tables["pc_artifact_heads"] = tables["pc_artifact_heads"].replace(old, new) + assert digest(_inventory(_ReflectedDatabase(tables))) != original + + +@pytest.mark.parametrize( + "properties", + [ + "unknown_option=3,max_token_size=84", + "min_token_size='3',max_token_size=84", + "min_token_size=3,min_token_size=4", + "min_token_size=3+1,max_token_size=84", + ], +) +def test_fulltext_unknown_parser_properties_are_rejected(properties: str) -> None: + tables = _released_fulltext_tables() + tables["pc_artifact_heads"] = tables["pc_artifact_heads"].replace("min_token_size=3,max_token_size=84", properties) + with pytest.raises(MigrationError, match="incompatible_schema"): + _inventory(_ReflectedDatabase(tables)) + + +def test_fulltext_key_cannot_be_hidden_as_an_automatic_foreign_key_index(schema: MySQLSchema) -> None: + tables = _show_tables() + tables["pc_artifact_heads"] = _append_clause( + tables["pc_artifact_heads"], + "FULLTEXT KEY extra_search (scope_id, family, artifact_id, revision)", + ) + inventory = _inventory(_ReflectedDatabase(tables)) + assert inventory["tables"]["pc_artifact_heads"]["indexes"]["extra_search"]["kind"] == "fulltext" + with pytest.raises(MigrationError, match="complete layout"): + schema.identify(inventory) + + @pytest.mark.parametrize( ("citations", "drop_family", "revision"), [(0, False, "p0001"), (2, False, "p0002"), (2, True, "p0003")] ) @@ -173,6 +419,28 @@ def test_named_unique_constraints_and_native_keys_have_the_same_fingerprint(sche schema.identify(native) +def test_native_oceanbase_auto_increment_mode_is_preserved_while_the_counter_can_advance() -> None: + path = Path(__file__).parents[2] / "fixtures/database_migrations/schemas/oceanbase_v1_3_0_audit.json" + statement = json.loads(path.read_text())["tables"]["pc_access_audit"] + original = _inventory(_ReflectedDatabase({"pc_access_audit": statement})) + assert original["tables"]["pc_access_audit"]["auto_increment_mode"] == "order" + advanced = _inventory( + _ReflectedDatabase({"pc_access_audit": statement.replace("AUTO_INCREMENT = 37", "AUTO_INCREMENT = 900")}) + ) + assert advanced == original + unordered = _inventory(_ReflectedDatabase({"pc_access_audit": statement.replace("'ORDER'", "'NOORDER'")})) + assert unordered["tables"]["pc_access_audit"]["auto_increment_mode"] == "noorder" + assert digest(unordered) != digest(original) + + +@pytest.mark.parametrize("mode", ["'FUTURE'", "'ORDER' AUTO_INCREMENT_MODE='NOORDER'"]) +def test_unknown_or_duplicate_auto_increment_mode_is_not_silently_discarded(mode: str) -> None: + path = Path(__file__).parents[2] / "fixtures/database_migrations/schemas/oceanbase_v1_3_0_audit.json" + statement = json.loads(path.read_text())["tables"]["pc_access_audit"].replace("'ORDER'", mode) + with pytest.raises(MigrationError, match="incompatible_schema"): + _inventory(_ReflectedDatabase({"pc_access_audit": statement})) + + @pytest.mark.parametrize("applied", range(7)) def test_baseline_recovery_accepts_only_the_completed_statement_prefix(schema: MySQLSchema, applied: int) -> None: inventory = _inventory(_ReflectedDatabase(_show_tables(baseline_prefix=applied))) @@ -268,28 +536,96 @@ def test_unknown_objects_or_changed_integrity_constraints_cannot_be_adopted(sche def test_version_table_is_validated_before_reading_and_is_not_a_business_table(schema: MySQLSchema) -> None: database = _ReflectedDatabase(_show_tables()) - assert read_revision(cast(Connection, database), _inventory(database)) == (False, None) + assert read_revision(cast(Connection, database), _inventory(database), revisions=schema.bundle.revisions) == ( + False, + None, + ) database.tables["pc_schema_revision"] = ( "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) NOT NULL, " "CONSTRAINT pc_schema_revision_pkc PRIMARY KEY (version_num)\n)" ) - assert read_revision(cast(Connection, database), _inventory(database)) == (True, None) + assert read_revision(cast(Connection, database), _inventory(database), revisions=schema.bundle.revisions) == ( + True, + None, + ) database.heads = ["p0001"] inventory = _inventory(database) - assert read_revision(cast(Connection, database), inventory) == (True, "p0001") + assert read_revision(cast(Connection, database), inventory, revisions=schema.bundle.revisions) == (True, "p0001") assert schema.identify(inventory) == "p0001" schema.verify(inventory, "p0001") database.heads = ["p0001", "p0002"] with pytest.raises(MigrationError, match="multiple or unknown"): - read_revision(cast(Connection, database), inventory) + read_revision(cast(Connection, database), inventory, revisions=schema.bundle.revisions) database.heads = ["unknown"] with pytest.raises(MigrationError, match="multiple or unknown"): - read_revision(cast(Connection, database), inventory) + read_revision(cast(Connection, database), inventory, revisions=schema.bundle.revisions) database.tables["pc_schema_revision"] = _append_clause( database.tables["pc_schema_revision"], "KEY unexpected (version_num)" ) with pytest.raises(MigrationError, match="version table"): - read_revision(cast(Connection, database), _inventory(database)) + read_revision(cast(Connection, database), _inventory(database), revisions=schema.bundle.revisions) + + +_VECTOR_TABLE = ( + "CREATE TABLE pc_vector_projection (" + "id BIGINT NOT NULL, embedding VECTOR(3) NOT NULL, PRIMARY KEY (id), " + "VECTOR KEY ix_embedding (embedding) WITH " + "(DISTANCE=L2, TYPE=HNSW, LIB=VSAG, M=16, EF_CONSTRUCTION=200, EF_SEARCH=64) BLOCK_SIZE 16384" + ") DEFAULT CHARSET=utf8mb4" +) + + +def test_vector_index_reflection_preserves_parameters_without_inventing_a_column() -> None: + # VECTOR KEY comes from the actual installed 1.3.0 optional-profile captures. + table = _inventory(_ReflectedDatabase({"pc_vector_projection": _VECTOR_TABLE}))["tables"]["pc_vector_projection"] + assert set(table["columns"]) == {"id", "embedding"} + assert table["columns"]["embedding"]["type"] == ["vector", ["3"]] + assert table["indexes"]["ix_embedding"] == { + "columns": [["embedding", None, "asc"]], + "unique": False, + "kind": "vector", + "parameters": { + "distance": "l2", + "type": "hnsw", + "lib": "vsag", + "m": 16, + "ef_construction": 200, + "ef_search": 64, + }, + } + changed = _VECTOR_TABLE.replace("EF_SEARCH=64", "EF_SEARCH=128") + actual = _inventory(_ReflectedDatabase({"pc_vector_projection": changed}))["tables"]["pc_vector_projection"] + assert actual["columns"] == table["columns"] + assert actual["indexes"] != table["indexes"] + + +@pytest.mark.parametrize( + "replacement", + ["M=16, M=32", "M=-1", "M=0", "M=anything", "UNKNOWN_OPTION=16"], +) +def test_unprovable_vector_index_options_fail_schema_reflection(replacement: str) -> None: + definition = _VECTOR_TABLE.replace("M=16", replacement) + with pytest.raises(MigrationError, match="VECTOR"): + _inventory(_ReflectedDatabase({"pc_vector_projection": definition})) + + +@pytest.mark.skipif(os.environ.get("POWERCONTEXT_TEST_MIGRATION_SEEKDB") != "1", reason="real seekdb probe not enabled") +def test_real_seekdb_vector_index_is_reflected_as_an_index(short_tmp_path: Path) -> None: + def inspect_vector(connection, _identity, _guard): + assert connection is not None + connection.exec_driver_sql(_VECTOR_TABLE) + connection.commit() + table = mysql_inventory(connection)["tables"]["pc_vector_projection"] + assert set(table["columns"]) == {"id", "embedding"} + index = table["indexes"]["ix_embedding"] + assert index["kind"] == "vector" + assert index["columns"] == [["embedding", None, "asc"]] + assert index["parameters"]["distance"] == "l2" + assert index["parameters"]["m"] == 16 + assert index["parameters"]["ef_construction"] == 200 + assert index["parameters"]["ef_search"] == 64 + + MaintenanceConnections(SeekDBConfig(path=short_tmp_path / "seekdb")).run(inspect_vector, writable=True) @pytest.mark.parametrize("violation", ["foreign_key", "check"]) @@ -317,13 +653,16 @@ def test_real_seekdb_frozen_schema_reflection_and_revision_recovery(short_tmp_pa def migrate(connection, _identity, guard) -> None: assert connection is not None - assert read_revision(connection, mysql_inventory(connection)) == (False, None) + assert read_revision(connection, mysql_inventory(connection), revisions=schema.bundle.revisions) == ( + False, + None, + ) connection.exec_driver_sql( "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) NOT NULL, " "CONSTRAINT pc_schema_revision_pkc PRIMARY KEY (version_num))" ) connection.commit() - assert read_revision(connection, mysql_inventory(connection)) == (True, None) + assert read_revision(connection, mysql_inventory(connection), revisions=schema.bundle.revisions) == (True, None) source = None for target in ("p0001", "p0002", "p0003"): while True: diff --git a/tests/builtin/persistence/test_mysql_portable_layout.py b/tests/builtin/persistence/test_mysql_portable_layout.py new file mode 100644 index 0000000000..debe53a35f --- /dev/null +++ b/tests/builtin/persistence/test_mysql_portable_layout.py @@ -0,0 +1,118 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Strict portable layout recognition using native Boolean reflection evidence. + +The receipt SHOW CREATE below is modeled, not a claimed native capture. Its +Boolean spelling is anchored to the separately captured seekDB 1.4 table. +""" + +import json +from pathlib import Path + +import pytest + +from powercontext.builtin.persistence.migrations.deployment import server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.mysql_schema import MySQLSchema +from tests.builtin.persistence.test_mysql_migration_schema import _inventory, _ReflectedDatabase + +_RECEIPT = "pc_portable_restore_receipts" +_RECEIPT_SHOW_CREATE = """CREATE TABLE `pc_portable_restore_receipts` ( + `bundle_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `format_version` int(11) NOT NULL, + `total_digest` varchar(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `record_count` bigint(20) NOT NULL, + `status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `inserted` bigint(20) NOT NULL, + `already_present` bigint(20) NOT NULL, + `projections_ready` tinyint(1) NOT NULL, + `updated_at` datetime NOT NULL, + PRIMARY KEY (`bundle_id`), + CONSTRAINT `ck_pc_portable_restore_receipts_status` CHECK ((`status` in ('authoritative_restored','ready'))), + CONSTRAINT `ck_pc_portable_restore_receipts_record_count` CHECK ((`record_count` >= 0)), + CONSTRAINT `ck_pc_portable_restore_receipts_inserted` CHECK ((`inserted` >= 0)), + CONSTRAINT `ck_pc_portable_restore_receipts_existing` CHECK ((`already_present` >= 0)) +) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC +COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0""" +_PRECISION_COLUMNS = ( + ("pc_artifacts", "created_at"), + ("pc_profile_policies", "updated_at"), + ("pc_topic_memory_revision_publications", "published_at"), + ("pc_artifact_tags", "assigned_at"), +) + + +def historical_inventory(bundle): + inventory = {"tables": {}, "views": [], "triggers": []} + for revision in ("s1300", "s1400", "s1401"): + fragment = json.loads((bundle.directory / f"schemas/mysql-{revision}.json").read_text()) + for change in fragment["revisions"][revision]["changes"]: + for name, table in change.items(): + if table is None: + del inventory["tables"][name] + else: + inventory["tables"][name] = table + return inventory + + +def receipt_inventory(): + return _inventory(_ReflectedDatabase({_RECEIPT: _RECEIPT_SHOW_CREATE}))["tables"][_RECEIPT] + + +def test_native_boolean_reflection_allows_only_registered_portable_recovery_prefixes(): + fixture = Path(__file__).parents[2] / "fixtures/database_migrations/schemas/seekdb_v1_3_0_checks.json" + captured = json.loads(fixture.read_text())["tables"]["pc_model_usage_daily"] + reflected = _inventory(_ReflectedDatabase({"pc_model_usage_daily": captured["show_create_table"]})) + native_boolean = reflected["tables"]["pc_model_usage_daily"]["columns"]["input_complete"] + receipt = receipt_inventory() + assert receipt["columns"]["projections_ready"] == native_boolean + + bundle = server_bundle() + schema = MySQLSchema(bundle) + inventory = historical_inventory(bundle) + assert schema.identify(inventory) == "s1401" + inventory["tables"][_RECEIPT] = receipt + recovery = schema.recovery(inventory, "s1401") + assert recovery.revision == "s1402" + assert recovery.pending_statements == tuple( + f"ALTER TABLE `{table}` MODIFY COLUMN `{column}` DATETIME(6) " + + ("NULL" if table == "pc_artifacts" else "NOT NULL") + for table, column in _PRECISION_COLUMNS + ) + for position, (table, column) in enumerate(_PRECISION_COLUMNS, 1): + inventory["tables"][table]["columns"][column]["type"] = ["datetime", ["6"]] + assert schema.recovery(inventory, "s1401").pending_statements == recovery.pending_statements[position:] + assert schema.identify(inventory) == "s1402" + schema.verify(inventory, "s1402") + + +@pytest.mark.parametrize("damage", ["boolean_width", "timestamp_precision", "missing_constraint"]) +def test_portable_target_rejects_unregistered_types_and_constraints(damage): + bundle = server_bundle() + schema = MySQLSchema(bundle) + inventory = historical_inventory(bundle) + inventory["tables"][_RECEIPT] = receipt_inventory() + for table, column in _PRECISION_COLUMNS: + inventory["tables"][table]["columns"][column]["type"] = ["datetime", ["6"]] + if damage == "boolean_width": + inventory["tables"][_RECEIPT]["columns"]["projections_ready"]["type"] = ["tinyint", ["2"]] + elif damage == "timestamp_precision": + inventory["tables"]["pc_artifacts"]["columns"]["created_at"]["type"] = ["datetime", ["3"]] + else: + del inventory["tables"][_RECEIPT]["checks"]["ck_pc_portable_restore_receipts_record_count"] + with pytest.raises(MigrationError, match="incompatible_schema"): + schema.verify(inventory, "s1402") + with pytest.raises(MigrationError, match="incompatible_schema"): + schema.recovery(inventory, "s1401") diff --git a/tests/builtin/persistence/test_oceanbase_profile.py b/tests/builtin/persistence/test_oceanbase_profile.py index cba0b75f7d..1740e70148 100644 --- a/tests/builtin/persistence/test_oceanbase_profile.py +++ b/tests/builtin/persistence/test_oceanbase_profile.py @@ -47,7 +47,7 @@ VALID_URL = "mysql+aoceanbase://root%40tenant:secret@127.0.0.1:2881/powercontext?charset=utf8mb4" -async def _skip_dream_preflight(_engine: AsyncEngine) -> None: +async def _skip_dream_preflight(_engine: AsyncEngine, *, _archive_recovery: bool = False) -> None: """These profile unit tests use engine doubles; read-only preflight has integration coverage.""" diff --git a/tests/builtin/persistence/test_portable_storage_migration.py b/tests/builtin/persistence/test_portable_storage_migration.py new file mode 100644 index 0000000000..39b299ab3d --- /dev/null +++ b/tests/builtin/persistence/test_portable_storage_migration.py @@ -0,0 +1,129 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Managed upgrades retain existing evidence while admitting current portable storage.""" + +import json +import shutil +import sqlite3 +from contextlib import closing + +import pytest + +from powercontext.builtin.artifacts.memory import EmbeddingProfile +from powercontext.builtin.persistence.migrations.bundle import MigrationBundle +from powercontext.builtin.persistence.migrations.deployment import server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.sqlite import SQLiteMigrationRunner +from powercontext.builtin.runtime.config import BuiltinConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest + + +def previous_bundle(directory, embedding): + current = server_bundle(embedding_profile=embedding) + shutil.copytree(current.directory, directory, ignore=shutil.ignore_patterns("__pycache__")) + manifest_path = directory / "manifest.json" + manifest = json.loads(manifest_path.read_text()) + removed = manifest["revision_resources"].pop("s1402") + for resource in (*removed, "versions/s1402_portable_storage.py", "mysql/versions/s1402.py", "portable_storage.py"): + (directory / resource).unlink() + del manifest["sqlite_fingerprints"]["s1402"] + del manifest["domain_readiness"]["s1402"] + manifest["domain_steps"] = [step for step in manifest["domain_steps"] if step["revision"] != "s1402"] + manifest["mysql_state_layouts"].remove("schemas/mysql-s1402.json") + manifest["affected_objects"].remove("pc_portable_restore_receipts") + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + return MigrationBundle(directory, embedding_profile=embedding) + + +def configuration(embedding): + result = { + "processing_manifest": canonical_processing_manifest(BuiltinConfig()), + "embedding_enabled": embedding is not None, + } + if embedding is not None: + result["embedding_profile"] = embedding.model_dump(mode="json") + return result + + +def apply(runner): + return runner.apply(plan_id=runner.plan().plan_id, accepted=True, maintenance_confirmed=True) + + +@pytest.mark.parametrize("vectors", [False, True]) +def test_registered_s1401_upgrade_preserves_completed_evidence_and_matches_fresh_storage(tmp_path, vectors): + embedding = EmbeddingProfile(profile_id="portable-vectors", model="fixture", dimension=3) if vectors else None + config = configuration(embedding) + previous = previous_bundle(tmp_path / "previous-bundle", embedding) + database = tmp_path / "existing.db" + old = SQLiteMigrationRunner(database, previous, configuration=config) + assert apply(old).revision == "s1401" + historical_time = "2026-10-23 12:34:56.123456" + with closing(sqlite3.connect(database)) as connection, connection: + connection.execute( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version) " + "VALUES ('scope','title','summary','scope','title','summary',1)" + ) + connection.execute( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content,created_at) " + "VALUES ('scope','custom','preserved',1,?,?)", + (b'{"history":"unchanged"}', historical_time), + ) + assert old.verify().state == "ready" + + bundle = server_bundle(embedding_profile=embedding) + upgraded = SQLiteMigrationRunner(database, bundle, configuration=config) + plan = upgraded.plan() + assert plan.source_revision == "s1401" + assert plan.revisions == ("s1402",) + assert apply(upgraded).revision == "s1402" + assert upgraded.verify().state == "ready" + assert not apply(upgraded).changed + fresh = SQLiteMigrationRunner(tmp_path / "fresh.db", bundle, configuration=config) + apply(fresh) + assert upgraded.plan().schema_fingerprint == fresh.plan().schema_fingerprint + with closing(sqlite3.connect(database)) as connection, connection: + assert connection.execute( + "SELECT content,created_at FROM pc_artifacts WHERE artifact_id='preserved'" + ).fetchone() == (b'{"history":"unchanged"}', historical_time) + connection.execute( + "INSERT INTO pc_portable_restore_receipts " + "(bundle_id,format_version,total_digest,record_count,status,inserted,already_present,projections_ready,updated_at) " + "VALUES ('receipt',1,'sha256:fixture',0,'ready',0,0,1,?)", + (historical_time,), + ) + assert upgraded.verify().state == "ready" + + +@pytest.mark.parametrize("vectors", [False, True]) +def test_pending_portable_restore_blocks_migration_readiness_without_repair(tmp_path, vectors): + embedding = EmbeddingProfile(profile_id="portable-vectors", model="fixture", dimension=3) if vectors else None + runner = SQLiteMigrationRunner( + tmp_path / "managed.db", server_bundle(embedding_profile=embedding), configuration=configuration(embedding) + ) + apply(runner) + with closing(sqlite3.connect(runner.database)) as connection, connection: + connection.execute( + "INSERT INTO pc_portable_restore_receipts " + "(bundle_id,format_version,total_digest,record_count,status,inserted,already_present,projections_ready,updated_at) " + "VALUES ('receipt',1,'sha256:fixture',0,'authoritative_restored',0,0,0,'2026-10-23 12:34:56.123456')" + ) + before = runner.database.read_bytes() + for check in (runner.plan, runner.verify): + with pytest.raises(MigrationError, match="archive_recovery_required"): + check() + assert runner.database.read_bytes() == before + with closing(sqlite3.connect(runner.database)) as connection, connection: + connection.execute("UPDATE pc_portable_restore_receipts SET status='ready',projections_ready=1") + assert runner.verify().state == "ready" diff --git a/tests/builtin/persistence/test_seekdb_profile.py b/tests/builtin/persistence/test_seekdb_profile.py index 488e7d1b6e..d751e45616 100644 --- a/tests/builtin/persistence/test_seekdb_profile.py +++ b/tests/builtin/persistence/test_seekdb_profile.py @@ -83,7 +83,7 @@ async def aopen(self, path: str) -> _SeekDBInstance: return self.instance -async def _skip_dream_preflight(_engine: AsyncEngine) -> None: +async def _skip_dream_preflight(_engine: AsyncEngine, *, _archive_recovery: bool = False) -> None: """These profile unit tests use engine doubles; read-only preflight has integration coverage.""" diff --git a/tests/builtin/persistence/test_server_migration_baselines.py b/tests/builtin/persistence/test_server_migration_baselines.py new file mode 100644 index 0000000000..2351140870 --- /dev/null +++ b/tests/builtin/persistence/test_server_migration_baselines.py @@ -0,0 +1,196 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Recognize complete released schemas without stamping or promising an upgrade.""" + +from __future__ import annotations + +import copy +import json +import sqlite3 +from pathlib import Path + +import pytest +from sqlalchemy import create_engine + +from powercontext.builtin.persistence.migrations import MigrationError, SQLiteMigrationRunner +from powercontext.builtin.persistence.migrations.deployment import production_bundle +from powercontext.builtin.persistence.migrations.server_baselines import ( + recognize_mysql_server_baseline, + recognize_sqlite_server_baseline, +) + +RESOURCE = ( + Path(__file__).resolve().parents[3] + / "src/powercontext/builtin/persistence/migration_resources/server_baselines/1.3.0/sqlite.json" +) + + +@pytest.fixture +def released_database(tmp_path: Path) -> Path: + database = tmp_path / "released.db" + inventory = json.loads(RESOURCE.read_text(encoding="utf-8"))["inventory"] + with sqlite3.connect(database) as connection: + # Creating an FTS virtual table also creates its frozen shadow tables. + for kind in ("table", "index", "trigger", "view"): + for object_kind, name, _, sql in inventory: + if ( + object_kind == kind + and connection.execute("SELECT 1 FROM sqlite_schema WHERE name=?", (name,)).fetchone() is None + ): + connection.execute(sql) + connection.execute( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content) " + "VALUES ('scope','atomic-memory','historical',1,?)", + (b'{"historical":"immutable"}',), + ) + return database + + +def recognize(database: Path): + engine = create_engine("sqlite://", creator=lambda: sqlite3.connect(database.as_uri() + "?mode=ro", uri=True)) + try: + with engine.connect() as connection: + connection.exec_driver_sql("PRAGMA query_only=ON") + return recognize_sqlite_server_baseline(connection) + finally: + engine.dispose() + + +def test_complete_release_schema_is_recognized_without_writing_or_claiming_readiness(released_database: Path) -> None: + before = released_database.read_bytes() + files = set(released_database.parent.iterdir()) + baseline = recognize(released_database) + assert baseline is not None + assert baseline.release == "1.3.0" + assert baseline.schema_fingerprint == "11e8770068a5fe32dfb25681564dbbe586ba0fd0238ed01adf1b09dde8a49f22" + runner = SQLiteMigrationRunner(released_database, production_bundle()) + for operation in (runner.plan, runner.verify, runner.apply): + with pytest.raises(MigrationError, match="server_migration_unavailable"): + operation() + assert released_database.read_bytes() == before + assert set(released_database.parent.iterdir()) == files + + +@pytest.mark.parametrize( + "ddl", + [ + "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) PRIMARY KEY)", + "CREATE TABLE pc_unregistered_persistent_state (id INTEGER PRIMARY KEY)", + "CREATE VIEW unregistered_view AS SELECT artifact_id FROM pc_artifacts", + "DROP INDEX ix_pc_artifact_tags_key", + "CREATE INDEX unregistered_index ON pc_artifacts (family)", + "ALTER TABLE pc_artifacts ADD COLUMN unexpected TEXT", + "ALTER TABLE pc_artifact_heads RENAME TO altered_heads", + "CREATE TRIGGER unregistered_trigger AFTER DELETE ON pc_artifacts BEGIN SELECT 1; END", + ], +) +def test_unregistered_schema_changes_are_not_recognized(released_database: Path, ddl: str) -> None: + with sqlite3.connect(released_database) as connection: + connection.execute(ddl) + before = released_database.read_bytes() + assert recognize(released_database) is None + assert released_database.read_bytes() == before + + +def test_four_table_subset_is_not_a_complete_server_baseline(tmp_path: Path) -> None: + database = tmp_path / "subset.db" + selected = {"pc_artifacts", "pc_artifact_heads", "pc_artifact_tags", "pc_artifact_candidate_versions"} + inventory = json.loads(RESOURCE.read_text(encoding="utf-8"))["inventory"] + with sqlite3.connect(database) as connection: + for kind, name, _, sql in inventory: + if kind == "table" and name in selected: + connection.execute(sql) + before = database.read_bytes() + assert recognize(database) is None + with pytest.raises(MigrationError, match="unknown_baseline"): + SQLiteMigrationRunner(database, production_bundle()).plan() + assert database.read_bytes() == before + + +def test_weakened_constraint_with_identical_columns_is_not_recognized(released_database: Path) -> None: + with sqlite3.connect(released_database) as connection: + original = connection.execute("SELECT sql FROM sqlite_schema WHERE name='pc_artifact_heads'").fetchone()[0] + assert "CHECK (revision > 0)" in original + connection.execute("DROP TABLE pc_artifact_heads") + connection.execute(original.replace("CHECK (revision > 0)", "CHECK (revision >= 0)")) + before = released_database.read_bytes() + assert recognize(released_database) is None + assert released_database.read_bytes() == before + + +@pytest.mark.parametrize("backend", ["seekdb", "oceanbase"]) +def test_complete_mysql_release_inventory_is_recognized_only_for_its_captured_backend(backend) -> None: + resource = json.loads(RESOURCE.with_name(f"{backend}.json").read_text(encoding="utf-8")) + inventory = resource["inventory"] + before = copy.deepcopy(inventory) + baseline = recognize_mysql_server_baseline(inventory, backend=backend) + assert baseline is not None + assert baseline.release == "1.3.0" + assert baseline.schema_fingerprint == resource["schema_fingerprint"] + other_backend = "oceanbase" if backend == "seekdb" else "seekdb" + assert recognize_mysql_server_baseline(inventory, backend=other_backend) is None + assert inventory == before + + +@pytest.mark.parametrize("backend", ["seekdb", "oceanbase"]) +@pytest.mark.parametrize( + "change", + [ + "table", + "control", + "column", + "constraint", + "index", + "parser", + "properties", + "view", + "trigger", + "subset", + ], +) +def test_incomplete_or_altered_mysql_inventories_are_not_released_baselines(change: str, backend) -> None: + inventory = json.loads(RESOURCE.with_name(f"{backend}.json").read_text(encoding="utf-8"))["inventory"] + table = inventory["tables"]["pc_artifact_heads"] + if change == "table": + del inventory["tables"]["pc_sources"] + elif change == "control": + inventory["tables"]["pc_schema_revision"] = {} + elif change == "column": + table["columns"]["revision"]["nullable"] = True + elif change == "constraint": + table["checks"]["ck_pc_artifact_heads_revision_positive"]["enforced"] = False + elif change == "index": + del table["indexes"]["ix_pc_artifact_heads_fts"] + elif change == "parser": + table["indexes"]["ix_pc_artifact_heads_fts"]["parser"] = "ngram" + elif change == "properties": + table["indexes"]["ix_pc_artifact_heads_fts"]["parser_properties"]["min_token_size"] = 4 + elif change == "view": + inventory["views"] = ["unknown_view"] + elif change == "trigger": + inventory["triggers"] = [{"Trigger": "unknown_trigger", "Table": "pc_artifacts"}] + else: + inventory["tables"] = { + name: shape for name, shape in inventory["tables"].items() if name.startswith("pc_artifact") + } + before = copy.deepcopy(inventory) + assert recognize_mysql_server_baseline(inventory, backend=backend) is None + assert inventory == before + + +def test_changed_audit_auto_increment_mode_is_not_a_released_oceanbase_baseline() -> None: + inventory = json.loads(RESOURCE.with_name("oceanbase.json").read_text(encoding="utf-8"))["inventory"] + inventory["tables"]["pc_access_audit"]["auto_increment_mode"] = "noorder" + assert recognize_mysql_server_baseline(inventory, backend="oceanbase") is None diff --git a/tests/builtin/persistence/test_server_vector_migrations.py b/tests/builtin/persistence/test_server_vector_migrations.py new file mode 100644 index 0000000000..68b4ca5df3 --- /dev/null +++ b/tests/builtin/persistence/test_server_vector_migrations.py @@ -0,0 +1,264 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Explicit vector maintenance preserves the configured embedding space.""" + +import asyncio +import json +from contextlib import closing + +import pytest +import sqlite_vec +from alembic import command +from fastapi.testclient import TestClient +from sqlalchemy import create_engine + +from powercontext.builtin.artifacts.memory import EmbeddingProfile +from powercontext.builtin.inference.models import EmbeddingResult +from powercontext.builtin.persistence.migrations.bundle import sqlite_inventory +from powercontext.builtin.persistence.migrations.deployment import server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.server_readiness import check_server_schema +from powercontext.builtin.persistence.migrations.sqlite import SQLiteMigrationRunner +from powercontext.builtin.persistence.sqlite import SQLiteConfig, SQLiteProfile +from powercontext.builtin.persistence.topic_memory_index import topic_memory_embedding_profile_fingerprint +from powercontext.builtin.runtime.config import BuiltinConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest +from powercontext.server.factory import create_server_app +from powercontext.server.settings import McpConfig, ServerSettings + + +class ControlledEmbedding: + def __init__(self, embedding): + self.profile = embedding + + async def embed(self, texts, /): + vector = (1.0,) + (0.0,) * (self.profile.dimension - 1) + return EmbeddingResult(vectors=tuple(vector for _ in texts)) + + +def connect(database): + import sqlite3 + + connection = sqlite3.connect(database) + connection.enable_load_extension(True) + try: + sqlite_vec.load(connection) + finally: + connection.enable_load_extension(False) + return connection + + +def profile(dimension): + return EmbeddingProfile(profile_id="migration-vectors", model="fixture", dimension=dimension) + + +def configuration(embedding): + return { + "processing_manifest": canonical_processing_manifest(BuiltinConfig()), + "embedding_enabled": True, + "embedding_profile": embedding.model_dump(mode="json"), + } + + +def apply(runner): + plan = runner.plan() + return runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + + +def source(database, embedding): + bundle = server_bundle() + frozen = json.loads((bundle.directory / "profiles/vector.json").read_text()) + engine = create_engine("sqlite://", creator=lambda: connect(database)) + with engine.connect() as connection: + connection.exec_driver_sql("BEGIN IMMEDIATE") + config = bundle.config(connection) + config.attributes["configuration"] = configuration(embedding) + command.upgrade(config, "s1300") + connection.exec_driver_sql("DROP TABLE pc_schema_revision") + for statement in frozen["sqlite"]["initialize"]: + connection.exec_driver_sql(statement.replace("__DIMENSION__", str(embedding.dimension))) + connection.exec_driver_sql( + "INSERT INTO pc_topic_memory_retrieval_shape VALUES (1,'hybrid',?)", + (topic_memory_embedding_profile_fingerprint(embedding),), + ) + connection.exec_driver_sql( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version)" + " VALUES ('scope','title','summary','scope','title','summary',1)" + ) + connection.exec_driver_sql( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content)" + " VALUES ('scope','custom-family','historical',1,?)", + (b'{"history":"unchanged"}',), + ) + connection.commit() + engine.dispose() + + +@pytest.mark.parametrize("dimension", [3, 4]) +def test_vector_upgrade_preserves_history_shape_and_matches_fresh_storage(tmp_path, dimension): + embedding = profile(dimension) + database = tmp_path / "old.db" + source(database, embedding) + bundle = server_bundle(embedding_profile=embedding) + runner = SQLiteMigrationRunner(database, bundle, configuration=configuration(embedding)) + assert runner.plan().source_revision == "s1300" + assert apply(runner).backup_state == "completed" + assert not apply(runner).changed + assert runner.verify().state == "ready" + with closing(connect(database)) as connection: + assert connection.execute("SELECT content FROM pc_artifacts WHERE artifact_id='historical'").fetchone() == ( + b'{"history":"unchanged"}', + ) + assert connection.execute( + "SELECT shape,profile_fingerprint FROM pc_topic_memory_retrieval_shape" + ).fetchone() == ( + "hybrid", + topic_memory_embedding_profile_fingerprint(embedding), + ) + assert json.loads( + connection.execute("SELECT embedding_profile FROM pc_server_storage_profile").fetchone()[0] + ) == embedding.model_dump(mode="json") + + fresh = tmp_path / "fresh.db" + apply(SQLiteMigrationRunner(fresh, bundle, configuration=configuration(embedding))) + inventories = [] + for path in (database, fresh): + engine = create_engine("sqlite://", creator=lambda path=path: connect(path)) + with engine.connect() as connection: + check_server_schema(connection) + inventories.append(sqlite_inventory(connection, exclude=frozenset(bundle.retained_objects))) + engine.dispose() + assert inventories[0] == inventories[1] + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{database}"), mcp=McpConfig(enabled=False) + ) + app = create_server_app( + settings=settings, embedding_model=ControlledEmbedding(embedding), scheduler_path=tmp_path / "scheduler" + ) + with TestClient(app) as client: + remembered = client.post( + "/v1/memory/remember", + json={"scope_id": "scope", "kind": "fact", "text": "New vectors remain searchable after maintenance."}, + ) + assert remembered.status_code == 200 + searched = client.post( + "/v1/memory/search", json={"scope_id": "scope", "query": "New vectors", "mode": "vector"} + ) + assert searched.status_code == 200 + assert searched.json()["hits"][0]["memory"]["text"] == "New vectors remain searchable after maintenance." + + +def test_vector_configuration_change_is_rejected_without_rebuilding_helpers(tmp_path): + database = tmp_path / "managed.db" + embedding = profile(3) + bundle = server_bundle(embedding_profile=embedding) + apply(SQLiteMigrationRunner(database, bundle, configuration=configuration(embedding))) + before = database.read_bytes() + other = embedding.model_copy(update={"model": "different-model"}) + runner = SQLiteMigrationRunner(database, server_bundle(embedding_profile=other), configuration=configuration(other)) + with pytest.raises(MigrationError, match="embedding_profile_mismatch"): + runner.plan() + assert database.read_bytes() == before + + +def test_missing_managed_vector_helper_is_rejected_without_startup_repair(tmp_path): + database = tmp_path / "managed.db" + embedding = profile(3) + bundle = server_bundle(embedding_profile=embedding) + apply(SQLiteMigrationRunner(database, bundle, configuration=configuration(embedding))) + with closing(connect(database)) as connection, connection: + connection.execute("DROP TRIGGER pc_atomic_memory_vec_insert") + before = database.read_bytes() + engine = create_engine("sqlite://", creator=lambda: connect(database)) + try: + with engine.connect() as connection, pytest.raises(MigrationError, match="incompatible_schema"): + check_server_schema(connection) + finally: + engine.dispose() + assert database.read_bytes() == before + + +def test_generic_storage_reader_admits_managed_vectors_without_explicit_extension_flag(tmp_path): + database = tmp_path / "managed.db" + embedding = profile(3) + runner = SQLiteMigrationRunner( + database, server_bundle(embedding_profile=embedding), configuration=configuration(embedding) + ) + apply(runner) + + async def read(): + config = SQLiteConfig(url=f"sqlite+aiosqlite:///{database}") + async with SQLiteProfile.open(config, tables=()) as storage, storage.database.transaction() as connection: + assert ( + await connection.exec_driver_sql("SELECT COUNT(*) FROM pc_atomic_memory_current_vec") + ).scalar_one() == 0 + + asyncio.run(read()) + assert not apply(runner).changed + + +def test_configured_vector_plan_is_read_only_for_a_missing_database(tmp_path): + from powercontext.builtin.persistence.migrations.deployment import deployment_runner + from powercontext.builtin.runtime.config import InferenceConfig + from powercontext.server.settings import ServerSettings + + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'server.db'}"), + inference=InferenceConfig( + embedding_model="openai:fixture", embedding_profile_id="fixture", embedding_dimension=3 + ), + ) + plan = deployment_runner(settings).plan() + assert plan.state == "uninitialized" and plan.readiness_scope == "server" + assert list(tmp_path.iterdir()) == [] + + +@pytest.mark.parametrize("damage", ["missing_current", "helper_identity"]) +def test_vector_readiness_rejects_incomplete_atomic_projections(tmp_path, damage): + database = tmp_path / "managed.db" + embedding = profile(3) + source(database, embedding) + runner = SQLiteMigrationRunner( + database, server_bundle(embedding_profile=embedding), configuration=configuration(embedding) + ) + apply(runner) + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{database}"), mcp=McpConfig(enabled=False) + ) + app = create_server_app( + settings=settings, embedding_model=ControlledEmbedding(embedding), scheduler_path=tmp_path / "scheduler" + ) + with TestClient(app) as client: + remembered = client.post( + "/v1/memory/remember", + json={"scope_id": "scope", "kind": "fact", "text": "Keep this vector-backed memory searchable."}, + ) + assert remembered.status_code == 200 + assert runner.verify().state == "ready" + with closing(connect(database)) as connection, connection: + if damage == "missing_current": + connection.execute("DELETE FROM pc_atomic_memory_current") + else: + connection.execute("UPDATE pc_atomic_memory_current_fts SET identity_token='broken'") + original = database.read_bytes() + for inspect_state in (runner.plan, runner.verify): + with pytest.raises(MigrationError, match="projection_verification_failed"): + inspect_state() + app = create_server_app( + settings=settings, embedding_model=ControlledEmbedding(embedding), scheduler_path=tmp_path / "scheduler" + ) + with pytest.raises(MigrationError, match="projection_verification_failed"), TestClient(app): + pytest.fail("Incomplete projections must block vector Server startup") + assert database.read_bytes() == original diff --git a/tests/builtin/runtime/test_processing_composition.py b/tests/builtin/runtime/test_processing_composition.py index bfc558c6e5..514ebf5587 100644 --- a/tests/builtin/runtime/test_processing_composition.py +++ b/tests/builtin/runtime/test_processing_composition.py @@ -188,8 +188,8 @@ async def scenario() -> None: # Exercise the OceanBase role's real startup gate against a real SQLite # transaction. A failed gate must run before dialect-specific indexes. @asynccontextmanager - async def local_profile(_config, *, tables): - async with SQLiteProfile.open(database, tables=tables) as profile: + async def local_profile(_config, *, tables, _archive_recovery=False): + async with SQLiteProfile.open(database, tables=tables, _archive_recovery=_archive_recovery) as profile: yield profile monkeypatch.setattr(composition.OceanBaseProfile, "open", local_profile) diff --git a/tests/builtin/test_native_code.py b/tests/builtin/test_native_code.py index 299ec69270..0872d6a49a 100644 --- a/tests/builtin/test_native_code.py +++ b/tests/builtin/test_native_code.py @@ -759,7 +759,8 @@ def test_http_client_mcp_and_prepared_code_share_freshness(repository, tmp_path) settings=ServerSettings( database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'http.db'}"), code=code, - ) + ), + scheduler_path=tmp_path / "bootstrap-scheduler.db", ) def http_client(headers=None, timeout=None, auth=None, **_): @@ -774,6 +775,9 @@ def http_client(headers=None, timeout=None, auth=None, **_): async def exercise(): nonlocal app + # Initialize Server-owned storage before seeding it through the SDK. + async with app.router.lifespan_context(app): + pass database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'http.db'}") async with open_builtin_runtime( BuiltinConfig(database=database), scheduler_path=tmp_path / "scheduler.db" diff --git a/tests/conftest.py b/tests/conftest.py index 91bda3b638..d8fd628be4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -161,6 +161,10 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item def pytest_ignore_collect(collection_path: Path, config: pytest.Config) -> bool | None: + # Installed-release suites run only through the mandatory artifact gate. + # The gate uses --confcutdir to exclude this repository conftest entirely. + if collection_path.resolve() == (Path(__file__).parent / "release_upgrades").resolve(): + return True # Alembic executes env.py as migration configuration; doctest collection # must not import it as a test module. if collection_path.resolve() == _DATABASE_MIGRATION_ENV.resolve(): diff --git a/tests/e2e/atomic_memory_migration_backend.py b/tests/e2e/atomic_memory_migration_backend.py index e22f52c115..78b3ea875d 100644 --- a/tests/e2e/atomic_memory_migration_backend.py +++ b/tests/e2e/atomic_memory_migration_backend.py @@ -16,16 +16,23 @@ from __future__ import annotations -from contextlib import AbstractAsyncContextManager +from collections.abc import AsyncIterator +from contextlib import AsyncExitStack, asynccontextmanager from dataclasses import dataclass from pathlib import Path +from sqlalchemy.ext.asyncio import create_async_engine + from powercontext.builtin.artifacts.memory import EmbeddingProfile from powercontext.builtin.persistence.atomic_memory_index import AtomicMemoryIndex -from powercontext.builtin.persistence.oceanbase import OceanBaseProfile +from powercontext.builtin.persistence.database import AsyncDatabase +from powercontext.builtin.persistence.oceanbase import OceanBaseConfig, OceanBaseProfile +from powercontext.builtin.persistence.oceanbase import profile as oceanbase_profile from powercontext.builtin.persistence.oceanbase.atomic_memory_index import OceanBaseAtomicMemoryIndex from powercontext.builtin.persistence.seekdb import SeekDBConfig, SeekDBProfile +from powercontext.builtin.persistence.seekdb import profile as seekdb_profile from powercontext.builtin.persistence.sqlite import SQLiteConfig, SQLiteProfile +from powercontext.builtin.persistence.sqlite import profile as sqlite_profile from powercontext.builtin.persistence.sqlite.atomic_memory_index import SQLiteAtomicMemoryIndex from powercontext.builtin.runtime.config import DatabaseConfig @@ -36,16 +43,46 @@ class MigrationBackend: directory: Path -def migration_profile( +@asynccontextmanager +async def migration_profile( config: DatabaseConfig, *, load_vector_extension: bool = False, -) -> AbstractAsyncContextManager[SQLiteProfile | SeekDBProfile | OceanBaseProfile]: - if isinstance(config, SQLiteConfig): - return SQLiteProfile.open(config, tables=(), load_vector_extension=load_vector_extension) - if isinstance(config, SeekDBConfig): - return SeekDBProfile.open(config, tables=()) - return OceanBaseProfile.open(config, tables=()) +) -> AsyncIterator[SQLiteProfile | SeekDBProfile | OceanBaseProfile]: + # Maintenance must inspect and repair the deliberately unready fixture. + # Business profile admission would reject it before the migration can run. + async with AsyncExitStack() as resources: + if isinstance(config, SQLiteConfig): + sqlite_profile._create_database_directory(config.url) + engine = create_async_engine(config.url, echo=config.echo, hide_parameters=True) + sqlite_profile._configure_sqlite(engine, config, load_vector_extension=load_vector_extension) + profile_type = SQLiteProfile + elif isinstance(config, SeekDBConfig): + path = config.path.expanduser().resolve() + path.parent.mkdir(parents=True, exist_ok=True) + instance = await seekdb_profile._open_instance(seekdb_profile._load_binding(), path) + resources.callback(instance.close) + engine = seekdb_profile._create_engine(config, instance.connection_options()) + profile_type = SeekDBProfile + else: + assert isinstance(config, OceanBaseConfig) + oceanbase_profile._register_official_dialect() + engine = create_async_engine( + config.url.get_secret_value(), + connect_args={"init_command": "SET autocommit = 0"}, + echo=config.echo, + hide_parameters=True, + pool_pre_ping=config.pool_pre_ping, + ) + profile_type = OceanBaseProfile + database = AsyncDatabase.own(engine) + resources.push_async_callback(database.close) + if isinstance(config, SQLiteConfig): + await sqlite_profile._warm_sqlite(engine, config) + elif isinstance(config, OceanBaseConfig): + async with database.transaction() as connection: + await oceanbase_profile._require_mysql_tenant(connection) + yield profile_type(database=database, tables=()) def migration_index(config: DatabaseConfig, profile: EmbeddingProfile | None = None) -> AtomicMemoryIndex: diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 5fe8846acf..4825a1e2e2 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -17,6 +17,7 @@ from __future__ import annotations import asyncio +import json import os from collections.abc import Iterator from pathlib import Path @@ -26,6 +27,7 @@ from pydantic import SecretStr from sqlalchemy.engine import make_url +from powercontext.builtin.persistence.migrations.backup import identify_fork_server from powercontext.builtin.persistence.oceanbase import OceanBaseConfig from powercontext.builtin.persistence.seekdb import SeekDBConfig from powercontext.builtin.persistence.sqlite import SQLiteConfig @@ -73,6 +75,17 @@ async def manage(*, create: bool) -> None: nonlocal owned async with migration_profile(config) as profile, profile.database.transaction() as connection: assert (await connection.exec_driver_sql("SELECT DATABASE()")).scalar_one() == url.database + version = str((await connection.exec_driver_sql("SELECT VERSION()")).scalar_one()) + comment = str((await connection.exec_driver_sql("SELECT @@version_comment")).scalar_one()) + product, engine_version = identify_fork_server(version, comment) + if product != "oceanbase_ai" or engine_version is None: + pytest.fail( + "Acceptance requires an identified real OceanBase version, not MySQL or SeekDB", pytrace=False + ) + if create: + evidence = {"backend": "oceanbase", "version": version, "engine_version": engine_version} + request.node.user_properties.append(("backend_identity", evidence)) + print(json.dumps(evidence, ensure_ascii=False)) await connection.exec_driver_sql( f"CREATE DATABASE `{database}`" if create else f"DROP DATABASE `{database}`" ) diff --git a/tests/e2e/server_storage.py b/tests/e2e/server_storage.py new file mode 100644 index 0000000000..8245d11fde --- /dev/null +++ b/tests/e2e/server_storage.py @@ -0,0 +1,46 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Initialize current Server fixtures before SDK or injected access components write.""" + +from pathlib import Path + +from powercontext.builtin.artifacts.memory import EmbeddingProfile +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.server.settings import ServerSettings + + +def prepare_server_database( + settings: ServerSettings, + *, + evidence_dir: Path | None = None, + embedding_profile: EmbeddingProfile | None = None, +) -> None: + runner = deployment_runner( + settings, + evidence_dir=evidence_dir, + lock_coordination="single-host" if settings.database.kind == "oceanbase" else None, + embedding_profile=embedding_profile, + ) + plan = runner.plan(backup_policy="skip") + assert plan.state in {"uninitialized", "ready"} + result = runner.apply( + plan_id=plan.plan_id, + backup_policy="skip", + accepted=True, + maintenance_confirmed=True, + accept_no_backup=True, + ) + assert result.state == "ready" + assert result.readiness_scope == "server" diff --git a/tests/e2e/test_access_control_http.py b/tests/e2e/test_access_control_http.py index 9b9a9aa044..14b7164eab 100644 --- a/tests/e2e/test_access_control_http.py +++ b/tests/e2e/test_access_control_http.py @@ -67,6 +67,7 @@ ServerSettings, ) from tests.e2e.dream_support import atomic_memory_pipeline, memory_source_text +from tests.e2e.server_storage import prepare_server_database ADMIN = PrincipalRef(type="service", id="admin") RECEIVER = PrincipalRef(type="user", id="bob") @@ -101,6 +102,7 @@ async def generate(self, request: AtomicMemoryExtractionInput, /) -> GenerationR def test_logical_handoff_grant_and_revoke_cross_the_public_server_boundary(tmp_path: Path) -> None: async def scenario() -> None: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'runtime.db'}") + await asyncio.to_thread(prepare_server_database, ServerSettings(database=database)) async with open_builtin_access_control( database, bootstrap_administrators=(ADMIN,), @@ -267,6 +269,7 @@ async def scenario() -> None: def test_base_source_and_artifact_routes_preserve_access_boundaries(tmp_path: Path) -> None: async def scenario() -> None: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'base-access-runtime.db'}") + await asyncio.to_thread(prepare_server_database, ServerSettings(database=database)) async with open_builtin_access_control( database, bootstrap_administrators=(ADMIN,), @@ -416,6 +419,7 @@ async def scenario() -> None: def test_query_parameters_cannot_override_path_authorization(tmp_path: Path) -> None: async def scenario() -> None: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'query-path-authz.db'}") + await asyncio.to_thread(prepare_server_database, ServerSettings(database=database)) async with open_builtin_access_control( database, bootstrap_administrators=(ADMIN,), @@ -581,6 +585,7 @@ def test_resource_discovery_includes_base_artifacts_under_scope_grants( ) -> None: async def scenario() -> None: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'discovery.db'}") + await asyncio.to_thread(prepare_server_database, ServerSettings(database=database)) open_access = open_builtin_access_control if provider == "builtin" else open_casbin_access_control async with open_access( database, bootstrap_administrators=(ADMIN,), deployment_id=DEPLOYMENT_ID @@ -681,6 +686,7 @@ async def scenario() -> None: def test_server_administrators_discover_managed_resources_without_content_access(tmp_path: Path, provider: str) -> None: async def scenario() -> None: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'admin-discovery.db'}") + await asyncio.to_thread(prepare_server_database, ServerSettings(database=database)) open_access = open_builtin_access_control if provider == "builtin" else open_casbin_access_control async with open_access( database, bootstrap_administrators=(ADMIN,), deployment_id=DEPLOYMENT_ID @@ -777,6 +783,7 @@ async def scenario() -> None: def test_tag_owners_and_exact_viewers_preserve_scope_boundaries(tmp_path: Path, family: str) -> None: async def scenario() -> None: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'tags.db'}") + await asyncio.to_thread(prepare_server_database, ServerSettings(database=database)) async with open_builtin_access_control( database, bootstrap_administrators=(ADMIN,), deployment_id=DEPLOYMENT_ID ) as access: diff --git a/tests/e2e/test_access_control_regressions.py b/tests/e2e/test_access_control_regressions.py index 27142c657f..b04f97669f 100644 --- a/tests/e2e/test_access_control_regressions.py +++ b/tests/e2e/test_access_control_regressions.py @@ -42,6 +42,7 @@ from powercontext.server.authz.composition import open_builtin_access_control, open_casbin_access_control from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, McpConfig, MetricsConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database ADMIN = PrincipalRef(type="service", id="admin") AUDIT = AccessAuditContext(transport="http", operation="test") @@ -59,17 +60,19 @@ async def readiness(self): @asynccontextmanager async def _server(tmp_path: Path, backend="builtin", *, inference: InferenceConfig | None = None): database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'regressions.db'}") + settings = ServerSettings( + database=database, + inference=inference or InferenceConfig(), + runtime=RuntimeConfig(artifact_processing_families=()), + access=AccessControlConfig(mode="enforced", deployment_id="regressions"), + mcp=McpConfig(enabled=False), + metrics=MetricsConfig(enabled=False), + ) + await asyncio.to_thread(prepare_server_database, settings) opener = open_builtin_access_control if backend == "builtin" else open_casbin_access_control async with opener(database, bootstrap_administrators=(ADMIN,), deployment_id="regressions") as access: app = create_server_app( - settings=ServerSettings( - database=database, - inference=inference or InferenceConfig(), - runtime=RuntimeConfig(artifact_processing_families=()), - access=AccessControlConfig(mode="enforced", deployment_id="regressions"), - mcp=McpConfig(enabled=False), - metrics=MetricsConfig(enabled=False), - ), + settings=settings, authentication_provider=_Authentication(), access_control=access, scheduler_path=tmp_path / "scheduler.db", diff --git a/tests/e2e/test_applicability_selection.py b/tests/e2e/test_applicability_selection.py index e48fc845c1..18de62e9ce 100644 --- a/tests/e2e/test_applicability_selection.py +++ b/tests/e2e/test_applicability_selection.py @@ -71,6 +71,7 @@ from powercontext.server.authz.composition import open_builtin_access_control from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, McpConfig, MetricsConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database class Authentication: @@ -85,20 +86,22 @@ async def readiness(self): @asynccontextmanager async def server(directory: Path) -> AsyncIterator[tuple[PowerContextClient, httpx.AsyncClient]]: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{directory / 'selection.db'}") + settings = ServerSettings( + database=database, + runtime=RuntimeConfig(artifact_processing_families=()), + external_skills=ExternalSkillsConfig(), + access=AccessControlConfig(mode="enforced", deployment_id="selection"), + mcp=McpConfig(enabled=False), + metrics=MetricsConfig(enabled=False), + ) + await asyncio.to_thread(prepare_server_database, settings) async with open_builtin_access_control( database, bootstrap_administrators=(PrincipalRef(type="service", id="admin"),), deployment_id="selection", ) as access: app = create_server_app( - settings=ServerSettings( - database=database, - runtime=RuntimeConfig(artifact_processing_families=()), - external_skills=ExternalSkillsConfig(), - access=AccessControlConfig(mode="enforced", deployment_id="selection"), - mcp=McpConfig(enabled=False), - metrics=MetricsConfig(enabled=False), - ), + settings=settings, authentication_provider=Authentication(), access_control=access, scheduler_path=directory / "scheduler.db", diff --git a/tests/e2e/test_atomic_artifact_search_http.py b/tests/e2e/test_atomic_artifact_search_http.py index ea3c7a8bbe..5b90442ec4 100644 --- a/tests/e2e/test_atomic_artifact_search_http.py +++ b/tests/e2e/test_atomic_artifact_search_http.py @@ -38,6 +38,7 @@ from powercontext.server.authz.composition import open_builtin_access_control from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, McpConfig, MetricsConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database ADMIN = PrincipalRef(type="service", id="atomic-search-admin") VIEWER = PrincipalRef(type="user", id="atomic-search-viewer") @@ -91,16 +92,24 @@ async def with_limit(connection): def test_supported_long_query_preserves_all_terms_in_both_http_searches( migration_backend, query, mode, sqlite_expression_limit ): + settings = ServerSettings( + database=migration_backend.config, + runtime=RuntimeConfig(artifact_processing_families=()), + metrics=MetricsConfig(enabled=False), + mcp=McpConfig(enabled=False), + ) + embedding = _Embedding() + prepare_server_database( + settings, + evidence_dir=migration_backend.directory / "migration-evidence", + embedding_profile=embedding.profile, + ) + async def scenario(): app = create_server_app( - settings=ServerSettings( - database=migration_backend.config, - runtime=RuntimeConfig(artifact_processing_families=()), - metrics=MetricsConfig(enabled=False), - mcp=McpConfig(enabled=False), - ), + settings=settings, scheduler_path=migration_backend.directory / "scheduler.db", - embedding_model=_Embedding(), + embedding_model=embedding, ) async with ( app.router.lifespan_context(app), @@ -247,6 +256,7 @@ async def capture(self, connection, scope_id, context, **kwargs): async def scenario() -> None: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'authenticated.db'}") + await asyncio.to_thread(prepare_server_database, ServerSettings(database=database)) async with open_builtin_access_control( database, bootstrap_administrators=(ADMIN,), deployment_id=DEPLOYMENT ) as access: diff --git a/tests/e2e/test_atomic_memory_grant_migration.py b/tests/e2e/test_atomic_memory_grant_migration.py index 5d20b82bd0..057adbe63a 100644 --- a/tests/e2e/test_atomic_memory_grant_migration.py +++ b/tests/e2e/test_atomic_memory_grant_migration.py @@ -64,6 +64,7 @@ from powercontext.server.middleware import AuthenticationMiddleware from powercontext.server.settings import AccessControlConfig, BearerAuthConfig, McpConfig, MetricsConfig, ServerSettings from tests.e2e.atomic_memory_migration_backend import MigrationBackend, migration_index, migration_profile +from tests.e2e.server_storage import prepare_server_database ACTOR = PrincipalRef(type="service", id="server-token", description="PowerContext static bearer") CREATED_AT = datetime(2026, 1, 1, tzinfo=UTC) @@ -289,6 +290,17 @@ async def _apply(migration_backend: MigrationBackend): def _prepare(migration_backend: MigrationBackend, **options): + prepare_server_database( + ServerSettings( + database=migration_backend.config, + runtime=RuntimeConfig(artifact_processing_families=()), + access=AccessControlConfig(mode="enforced"), + auth=BearerAuthConfig(enabled=True, token=SecretStr("migration-test-token")), + mcp=McpConfig(enabled=False), + metrics=MetricsConfig(enabled=False), + ), + evidence_dir=migration_backend.directory / "server-migration-evidence", + ) with _client(migration_backend) as client: scope_id = client.get("/v1/scopes/default").json()["scope_id"] return asyncio.run(_seed_legacy(migration_backend, scope_id, **options)) diff --git a/tests/e2e/test_atomic_memory_normalization.py b/tests/e2e/test_atomic_memory_normalization.py index c62b4f0877..f08c4007fc 100644 --- a/tests/e2e/test_atomic_memory_normalization.py +++ b/tests/e2e/test_atomic_memory_normalization.py @@ -42,6 +42,7 @@ ) from powercontext.server.factory import create_server_app from powercontext.server.settings import McpConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database @pytest.mark.parametrize( @@ -140,9 +141,12 @@ def test_atomic_memory_history_read_rebuild_and_restore_preserve_original_conten tmp_path: Path, historical_text: str ) -> None: async def scenario() -> None: - async with open_builtin_contexts( - BuiltinConfig(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'history.db'}")) - ) as contexts: + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'history.db'}"), + mcp=McpConfig(enabled=False), + ) + await asyncio.to_thread(prepare_server_database, settings) + async with open_builtin_contexts(BuiltinConfig(database=settings.database)) as contexts: scope_id = ( await contexts.scopes.create( ScopeDraft(title="History", summary="Historical text compatibility", idempotency_key="history") @@ -205,12 +209,7 @@ async def scenario() -> None: historical = await contexts.records.get_artifact_revision(scope_id, "atomic-memory", created.artifact_id, 1) assert historical == record - app = create_server_app( - settings=ServerSettings( - database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'history.db'}"), - mcp=McpConfig(enabled=False), - ) - ) + app = create_server_app(settings=settings) async with ( app.router.lifespan_context(app), httpx.AsyncClient( diff --git a/tests/e2e/test_atomic_memory_search_authorization_snapshot.py b/tests/e2e/test_atomic_memory_search_authorization_snapshot.py index 8439ce2174..1e8bf46d0f 100644 --- a/tests/e2e/test_atomic_memory_search_authorization_snapshot.py +++ b/tests/e2e/test_atomic_memory_search_authorization_snapshot.py @@ -65,6 +65,7 @@ from powercontext.server.authz.service import AccessControlService from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, McpConfig, MetricsConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database ADMIN = PrincipalRef(type="service", id="admin") VIEWER = PrincipalRef(type="user", id="viewer") @@ -131,16 +132,23 @@ async def _isolated_database(backend: str, tmp_path: Path) -> AsyncIterator[Data @asynccontextmanager async def _server(database: DatabaseConfig, provider: str, tmp_path: Path, embedding: _Embedding): + settings = ServerSettings( + database=database, + runtime=RuntimeConfig(artifact_processing_families=()), + access=AccessControlConfig(mode="enforced", deployment_id=DEPLOYMENT_ID), + metrics=MetricsConfig(enabled=False), + mcp=McpConfig(enabled=False), + ) + await asyncio.to_thread( + prepare_server_database, + settings, + evidence_dir=tmp_path / "migration-evidence", + embedding_profile=embedding.profile, + ) open_access = open_builtin_access_control if provider == "builtin" else open_casbin_access_control async with open_access(database, bootstrap_administrators=(ADMIN,), deployment_id=DEPLOYMENT_ID) as access: app = create_server_app( - settings=ServerSettings( - database=database, - runtime=RuntimeConfig(artifact_processing_families=()), - access=AccessControlConfig(mode="enforced", deployment_id=DEPLOYMENT_ID), - metrics=MetricsConfig(enabled=False), - mcp=McpConfig(enabled=False), - ), + settings=settings, scheduler_path=tmp_path / "scheduler.db", access_control=access, authentication_provider=_Authentication(), diff --git a/tests/e2e/test_background_metrics.py b/tests/e2e/test_background_metrics.py index 4399592917..d4f8201d22 100644 --- a/tests/e2e/test_background_metrics.py +++ b/tests/e2e/test_background_metrics.py @@ -19,20 +19,17 @@ import pytest +from powercontext.builtin.persistence.migrations.deployment import deployment_runner from powercontext.builtin.persistence.oceanbase import OceanBaseConfig -from powercontext.builtin.runtime.composition import open_builtin_runtime -from powercontext.builtin.runtime.config import BuiltinConfig, RuntimeConfig -from powercontext.server.authz import PrincipalRef -from powercontext.server.authz.composition import open_builtin_access_control +from powercontext.builtin.runtime.config import RuntimeConfig +from powercontext.server.settings import ServerSettings +from tests.e2e.atomic_memory_migration_backend import migration_profile -async def _initialize_background_database(config: BuiltinConfig) -> None: - principal = PrincipalRef(type="service", id="server-token", description="PowerContext static bearer") - async with ( - open_builtin_access_control(config.database, bootstrap_administrators=(principal,)), - open_builtin_runtime(config), - ): - pass +async def _database_objects(config: OceanBaseConfig) -> tuple[tuple[str, str], ...]: + async with migration_profile(config) as profile, profile.database.transaction() as connection: + rows = await connection.exec_driver_sql("SHOW FULL TABLES") + return tuple(sorted((row[0], row[1]) for row in rows)) @pytest.mark.parametrize("migration_backend", ["oceanbase"], indirect=True) @@ -47,15 +44,6 @@ def test_background_cli_serves_only_authorized_metrics_and_stops_cleanly(tmp_pat import httpx assert isinstance(migration_backend.config, OceanBaseConfig) - # Complete cold OceanBase schema/index creation before timing the listener lifecycle. - asyncio.run( - _initialize_background_database( - BuiltinConfig( - database=migration_backend.config, - runtime=RuntimeConfig(artifact_processing_role="background"), - ) - ) - ) with socket.socket() as candidate: candidate.bind(("127.0.0.1", 0)) port = candidate.getsockname()[1] @@ -68,19 +56,47 @@ def test_background_cli_serves_only_authorized_metrics_and_stops_cleanly(tmp_pat "POWERCONTEXT_SERVER_METRICS_BACKGROUND_PORT": str(port), "POWERCONTEXT_SERVER_METRICS_ENABLED": "true", }) + command = [ + sys.executable, + "-c", + "from powercontext.server.cli import app; app()", + "run", + "--role", + "background", + "--no-env-file", + ] + before = asyncio.run(_database_objects(migration_backend.config)) + assert before == () + rejected = subprocess.run(command, env=environment, capture_output=True, text=True, timeout=60, check=False) + assert rejected.returncode != 0, rejected.stdout + rejected.stderr + assert "migration_required" in rejected.stdout + rejected.stderr + assert asyncio.run(_database_objects(migration_backend.config)) == before + + # Explicit initialization must finish before either split Server role can serve. + runner = deployment_runner( + ServerSettings( + database=migration_backend.config, + runtime=RuntimeConfig(artifact_processing_role="background"), + ), + evidence_dir=tmp_path / "migration-evidence", + lock_coordination="single-host", + ) + plan = runner.plan(backup_policy="skip") + initialized = runner.apply( + plan_id=plan.plan_id, + accepted=True, + maintenance_confirmed=True, + backup_policy="skip", + accept_no_backup=True, + ) + assert initialized.state == "ready" + assert initialized.readiness_scope == "server" reports = os.environ.get("MIGRATION_REPORTS") log_path = Path(reports) / "background-metrics" / "background.log" if reports else tmp_path / "background.log" log_path.parent.mkdir(parents=True, exist_ok=True) with log_path.open("w") as output: process = subprocess.Popen( - [ - os.path.join(os.path.dirname(sys.executable), "powercontext"), - "server", - "run", - "--role", - "background", - "--no-env-file", - ], + command, env=environment, stdout=output, stderr=subprocess.STDOUT, diff --git a/tests/e2e/test_background_startup_admission.py b/tests/e2e/test_background_startup_admission.py new file mode 100644 index 0000000000..fdbd6ff1d0 --- /dev/null +++ b/tests/e2e/test_background_startup_admission.py @@ -0,0 +1,111 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Local admission regressions; native split-role acceptance lives in test_background_metrics.""" + +import asyncio + +import httpx +import pytest +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from typer.testing import CliRunner + +from powercontext.builtin.persistence.migrations.connections import MaintenanceConnections +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.server import cli +from powercontext.server.factory import create_server_app +from powercontext.server.settings import McpConfig, ServerSettings +from powercontext.server.tracing import ServerTracing + + +@pytest.mark.parametrize("access_mode", ["disabled", "enforced"]) +def test_background_cli_rejects_uninitialized_remote_storage_before_business_writes(tmp_path, monkeypatch, access_mode): + path = tmp_path / "empty.db" + path.touch() + before = path.read_bytes() + + def inspect_local_database(_self, operation, *, writable=False): + # Exercise the real remote admission decision with an actual empty SQL + # inventory. This substitutes only the network connection, not native OB. + assert writable is False + engine = create_engine(f"sqlite:///{path}") + try: + with engine.connect() as connection: + return operation(connection, None, lambda: None) + finally: + engine.dispose() + + def unexpected_business_profile(*args, **kwargs): + pytest.fail("uninitialized remote storage reached a business profile") + + monkeypatch.setattr(MaintenanceConnections, "run", inspect_local_database) + monkeypatch.setattr(cli, "open_builtin_access_control", unexpected_business_profile) + monkeypatch.setattr(cli, "open_builtin_runtime", unexpected_business_profile) + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_KIND", "oceanbase") + monkeypatch.setenv( + "POWERCONTEXT_SERVER_DATABASE_URL", "mysql+aoceanbase://test@localhost:2881/test?charset=utf8mb4" + ) + monkeypatch.setenv("POWERCONTEXT_SERVER_ACCESS_MODE", access_mode) + if access_mode == "enforced": + monkeypatch.setenv("POWERCONTEXT_SERVER_AUTH_TOKEN", "isolated-background-test-token") + result = CliRunner().invoke(cli.app, ["run", "--role", "background", "--no-env-file"]) + + assert isinstance(result.exception, MigrationError), result.output + assert result.exception.code == "migration_required" + assert path.read_bytes() == before + + +def test_managed_storage_remains_usable_by_background_runner_and_api(tmp_path, monkeypatch): + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'managed.db'}"), + mcp=McpConfig(enabled=False), + ) + runner = deployment_runner(settings) + plan = runner.plan() + assert runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True).state == "ready" + + with TestClient(create_server_app(settings=settings, scheduler_path=tmp_path / "scheduler.db")) as client: + created = client.post( + "/v1/scopes", + json={ + "title": "Preserved scope", + "summary": "Created before background startup", + "idempotency_key": "scope", + }, + ) + assert created.status_code == 201, created.text + scope_id = created.json()["scope_id"] + + async def serve(app, configured, stopped): + async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://testserver") as client: + assert (await client.get("/health/live")).status_code == 200 + metrics = await client.get("/metrics") + assert metrics.status_code == 200 + assert 'powercontext_server_readiness_state{state="ready"} 1.0' in metrics.text + stopped.set() + + monkeypatch.setattr(cli, "serve_background_metrics", serve) + # SQLite supports the all role. Invoke the runner with a real local runtime + # to check its admitted lifecycle; the native OB test covers split-role CLI. + asyncio.run(cli._run_background_async(settings, ServerTracing.context_only(role="background"))) + + with TestClient(create_server_app(settings=settings, scheduler_path=tmp_path / "scheduler.db")) as client: + response = client.get(f"/v1/scopes/{scope_id}") + assert response.status_code == 200, response.text + assert response.json()["title"] == "Preserved scope" + assert client.get("/health/ready").status_code == 200 + assert runner.verify().state == "ready" diff --git a/tests/e2e/test_candidate_review_authority.py b/tests/e2e/test_candidate_review_authority.py index 16183ede82..5e4a03d225 100644 --- a/tests/e2e/test_candidate_review_authority.py +++ b/tests/e2e/test_candidate_review_authority.py @@ -40,6 +40,7 @@ from powercontext.server.authz.composition import open_builtin_access_control from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, McpConfig, MetricsConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database ADMIN = PrincipalRef(type="service", id="admin") AGENT = PrincipalRef(type="service", id="coding-agent") @@ -92,15 +93,17 @@ def create_http_client( @asynccontextmanager async def _review_server(tmp_path: Path) -> AsyncIterator[tuple[FastAPI, str]]: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'review.db'}") + settings = ServerSettings( + database=database, + runtime=RuntimeConfig(artifact_processing_families=()), + access=AccessControlConfig(mode="enforced"), + mcp=McpConfig(enabled=True), + metrics=MetricsConfig(enabled=False), + ) + await asyncio.to_thread(prepare_server_database, settings) async with open_builtin_access_control(database, bootstrap_administrators=(ADMIN,)) as access: app = create_server_app( - settings=ServerSettings( - database=database, - runtime=RuntimeConfig(artifact_processing_families=()), - access=AccessControlConfig(mode="enforced"), - mcp=McpConfig(enabled=True), - metrics=MetricsConfig(enabled=False), - ), + settings=settings, authentication_provider=_Authentication(), access_control=access, scheduler_path=tmp_path / "scheduler.db", diff --git a/tests/e2e/test_candidate_schema_upgrade.py b/tests/e2e/test_candidate_schema_upgrade.py index a735d9cd6f..a1d2fe822f 100644 --- a/tests/e2e/test_candidate_schema_upgrade.py +++ b/tests/e2e/test_candidate_schema_upgrade.py @@ -29,6 +29,7 @@ upgrade_dream_storage, validate_dream_processing_manifest, ) +from powercontext.builtin.persistence.migrations import MigrationError from powercontext.builtin.persistence.sqlite import SQLiteConfig from powercontext.builtin.persistence.tables import PROFILE_POLICIES_TABLE from powercontext.builtin.runtime import BuiltinConfig, open_builtin_contexts @@ -415,7 +416,7 @@ async def scenario(): connection.execute("CREATE TABLE pc_schema_revision (version_num VARCHAR(32) NOT NULL PRIMARY KEY)") connection.execute("INSERT INTO pc_schema_revision VALUES ('p0003')") before = snapshot(path) - with pytest.raises(DreamSchemaNotReadyError, match="migration_framework_not_ready"): + with pytest.raises(MigrationError, match="migration_required"): async with open_builtin_contexts(BuiltinConfig(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{path}"))): pytest.fail("A version-table marker cannot establish Server readiness") assert snapshot(path) == before diff --git a/tests/e2e/test_kimi_code_host.py b/tests/e2e/test_kimi_code_host.py index 55818d6728..db574e9ed9 100644 --- a/tests/e2e/test_kimi_code_host.py +++ b/tests/e2e/test_kimi_code_host.py @@ -21,6 +21,7 @@ import shutil import socket import subprocess +import sys import threading import time from collections.abc import Callable, Iterator @@ -233,22 +234,45 @@ def log_message(self, format: str, *args: Any) -> None: # noqa: A002 - stdlib s @contextmanager -def serving(app): +def serving(app, *, startup_diagnostic: Path | None = None): with socket.socket() as probe: probe.bind(("127.0.0.1", 0)) port = probe.getsockname()[1] server = uvicorn.Server(uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error")) thread = threading.Thread(target=server.run, daemon=True) thread.start() + failure = None try: - deadline = time.monotonic() + 15 + started = time.monotonic() + deadline = started + 15 while not server.started and thread.is_alive() and time.monotonic() < deadline: time.sleep(0.05) - assert server.started + if not server.started: + frames = [] + frame = sys._current_frames().get(thread.ident) + while frame is not None: + frames.append({ + "file": Path(frame.f_code.co_filename).name, + "function": frame.f_code.co_name, + "line": frame.f_lineno, + }) + frame = frame.f_back + failure = { + "startup_seconds": time.monotonic() - started, + "thread_alive_before_shutdown": thread.is_alive(), + "server_started": server.started, + "frames": frames, + } + if startup_diagnostic is not None: + startup_diagnostic.write_text(json.dumps(failure, indent=2) + "\n", encoding="utf-8") + assert server.started, json.dumps(failure) yield server, f"http://127.0.0.1:{port}", thread finally: server.should_exit = True thread.join(timeout=10) + if failure is not None and startup_diagnostic is not None: + failure["thread_alive_after_shutdown"] = thread.is_alive() + startup_diagnostic.write_text(json.dumps(failure, indent=2) + "\n", encoding="utf-8") @pytest.fixture @@ -293,7 +317,7 @@ def test_real_kimi_memory_capture_recall_and_fail_open(tmp_path: Path, monkeypat scheduler_path=tmp_path / "scheduler", ) with ( - serving(app) as (server, endpoint, server_thread), + serving(app, startup_diagnostic=tmp_path / "server-startup.json") as (server, endpoint, server_thread), httpx.Client( base_url=endpoint, headers={"Authorization": "Bearer kimi-synthetic-server-token"}, diff --git a/tests/e2e/test_kimi_code_workflows.py b/tests/e2e/test_kimi_code_workflows.py index 0a80064ec9..908e26f9cc 100644 --- a/tests/e2e/test_kimi_code_workflows.py +++ b/tests/e2e/test_kimi_code_workflows.py @@ -112,7 +112,10 @@ def choose(body, scope_id, messages): gateway.choose_tool = choose try: - with serving(app) as (_, endpoint, _), httpx.Client(base_url=endpoint, timeout=10) as client: + with ( + serving(app, startup_diagnostic=tmp_path / "server-startup.json") as (_, endpoint, _), + httpx.Client(base_url=endpoint, timeout=10) as client, + ): scope = client.post( "/v1/scopes", json={"title": "Kimi workflows", "summary": "Native acceptance", "idempotency_key": "kimi-workflows"}, diff --git a/tests/e2e/test_managed_archive_recovery.py b/tests/e2e/test_managed_archive_recovery.py new file mode 100644 index 0000000000..5e8344734d --- /dev/null +++ b/tests/e2e/test_managed_archive_recovery.py @@ -0,0 +1,120 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Registered storage keeps interrupted archive recovery separate from normal startup.""" + +import asyncio + +import pytest +from pydantic import JsonValue + +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.portability import BundleRecoveryRequiredError, PortableBundleService +from powercontext.builtin.records import ArtifactWrite +from powercontext.builtin.runtime import BuiltinConfig, open_builtin_contexts +from powercontext.builtin.scope import ScopeDraft +from powercontext.server.settings import ServerSettings +from tests.builtin.persistence.test_server_vector_migrations import ControlledEmbedding, profile +from tests.e2e.server_storage import prepare_server_database + + +@pytest.mark.parametrize("family", ["atomic-memory", "experience", "topic-memory"]) +@pytest.mark.parametrize("vector", [False, True]) +def test_managed_archive_restore_resumes_after_projection_failure(tmp_path, family, vector): + embedding = ControlledEmbedding(profile(3)) if vector else None + settings = ServerSettings(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'target.db'}")) + prepare_server_database(settings, embedding_profile=None if embedding is None else embedding.profile) + config = BuiltinConfig(database=settings.database) + archive = tmp_path / "recovery.pcb" + contents: dict[str, dict[str, JsonValue]] = { + "atomic-memory": {"kind": "fact", "text": "Keep exact recovery evidence."}, + "experience": {"situation": "Recovery", "action": "Retry", "outcome": "Ready", "lesson": "Verify recovery."}, + "topic-memory": {"title": "Recovery", "summary": "Retry", "detail": "Keep exact recovery evidence."}, + } + content = contents[family] + + async def authorize(_scopes): + pass + + async def interrupted(_scopes): + raise RuntimeError("interrupted projection rebuild") # noqa: TRY003 + + async def scenario(): + async with open_builtin_contexts(BuiltinConfig(), embedding_model=embedding) as source: + scope = await source.scopes.create( + ScopeDraft(title="Recovery", summary="Registered restore", idempotency_key="recovery") + ) + artifact = await source.records.create_artifact(scope.scope_id, family, ArtifactWrite(content=content)) + authoritative = await source.records.get_artifact(scope.scope_id, family, artifact.artifact_id) + await source.portability.export([scope.scope_id], archive, authorize=authorize) + async with open_builtin_contexts(config, embedding_model=embedding) as target: + with pytest.raises(RuntimeError, match="interrupted projection rebuild"): + await PortableBundleService( + target.database, + projection_rebuilder=interrupted, + source_decoder=target.repositories.sources.decode_source, + ).restore(archive) + with pytest.raises(BundleRecoveryRequiredError): + async with open_builtin_contexts(config, embedding_model=embedding): + pass + with pytest.raises(BundleRecoveryRequiredError): + async with open_builtin_contexts( + config, embedding_model=embedding, _topic_memory_worker=True, _archive_recovery=True + ): + pass + other_embedding = None if vector else ControlledEmbedding(profile(3)) + with pytest.raises(MigrationError, match=r"embedding_profile_mismatch|unsupported_configuration"): + async with open_builtin_contexts(config, embedding_model=other_embedding, _archive_recovery=True): + pass + async with open_builtin_contexts(config, embedding_model=embedding, _archive_recovery=True) as target: + restored = await target.portability.restore(archive) + assert restored.projections_ready + assert (await target.portability.restore(archive)).inserted == 0 + async with open_builtin_contexts(config, embedding_model=embedding) as target: + restored = await target.records.get_artifact(scope.scope_id, family, artifact.artifact_id) + assert restored.content == authoritative.content + + asyncio.run(scenario()) + assert ( + deployment_runner(settings, embedding_profile=None if embedding is None else embedding.profile).verify().state + == "ready" + ) + + +@pytest.mark.parametrize("corruption", ["schema", "projection"]) +def test_archive_recovery_does_not_bypass_unrelated_managed_validation(tmp_path, corruption): + settings = ServerSettings(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'target.db'}")) + prepare_server_database(settings) + config = BuiltinConfig(database=settings.database) + + async def scenario(): + async with open_builtin_contexts(config) as target: + scope = await target.scopes.create( + ScopeDraft(title="Validation", summary="No recovery pending", idempotency_key="validation") + ) + await target.records.create_artifact( + scope.scope_id, "atomic-memory", ArtifactWrite(content={"kind": "fact", "text": "Retain this record."}) + ) + async with target.database.transaction() as connection: + if corruption == "schema": + await connection.exec_driver_sql("DROP TABLE pc_portable_restore_receipts") + else: + await connection.exec_driver_sql("DELETE FROM pc_atomic_memory_current") + with pytest.raises(MigrationError): + async with open_builtin_contexts(config, _archive_recovery=True): + pass + + asyncio.run(scenario()) diff --git a/tests/e2e/test_managed_startup_snapshot.py b/tests/e2e/test_managed_startup_snapshot.py new file mode 100644 index 0000000000..f3e4da2b19 --- /dev/null +++ b/tests/e2e/test_managed_startup_snapshot.py @@ -0,0 +1,104 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Healthy concurrent revisions must not cause managed startup to reject storage.""" + +import asyncio + +import aiosqlite +import pytest +from fastapi.testclient import TestClient + +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.runtime import BuiltinConfig, atomic_memory_rebuild, open_builtin_contexts +from powercontext.server.factory import create_server_app +from powercontext.server.settings import AccessControlConfig, BearerAuthConfig, McpConfig, ServerSettings +from tests.builtin.persistence.test_server_vector_migrations import ControlledEmbedding, profile + + +@pytest.mark.parametrize("entry", ["sdk", "server"]) +@pytest.mark.parametrize("vector", [False, True]) +def test_healthy_concurrent_update_does_not_block_managed_startup(tmp_path, monkeypatch, entry, vector): + embedding = ControlledEmbedding(profile(3)) if vector else None + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'managed.db'}", journal_mode="WAL"), + mcp=McpConfig(enabled=False), + access=AccessControlConfig(mode="disabled"), + auth=BearerAuthConfig(enabled=False), + ) + app = create_server_app(settings=settings, scheduler_path=tmp_path / "scheduler.db", embedding_model=embedding) + with TestClient(app) as client: + scope_response = client.post( + "/v1/scopes", + json={ + "title": "Concurrent startup", + "summary": "Concurrent update", + "idempotency_key": "concurrent-startup", + }, + ) + assert scope_response.status_code == 201, scope_response.text + scope = scope_response.json()["scope_id"] + response = client.post( + "/v1/memory/remember", json={"scope_id": scope, "kind": "fact", "text": "Before concurrent revision."} + ) + assert response.status_code == 200, response.text + artifact = response.json()["records"][0]["artifact"] + path = f"/v1/scopes/{scope}/artifacts/atomic-memory/{artifact['artifact_id']}" + etag = client.get(path).headers["ETag"] + original = atomic_memory_rebuild._record + updated = False + + async def interleaved_record(*args, **kwargs): + nonlocal updated + record = await original(*args, **kwargs) + # Schedule a real HTTP update between authoritative and projection + # reads in async startup. The synchronous migration runner already + # owns a snapshot and is not the boundary under test. + driver = args[0].connection.connection.driver_connection + if not updated and isinstance(driver, aiosqlite.Connection): + updated = True + replacement = client.put( + path, + headers={"If-Match": etag}, + json={"content": {"kind": "fact", "text": "After concurrent revision."}}, + ) + assert replacement.status_code == 200, replacement.text + return record + + monkeypatch.setattr(atomic_memory_rebuild, "_record", interleaved_record) + + async def start(): + async with open_builtin_contexts(BuiltinConfig(database=settings.database), embedding_model=embedding): + pass + + try: + if entry == "sdk": + asyncio.run(start()) + else: + with TestClient( + create_server_app( + settings=settings, embedding_model=embedding, scheduler_path=tmp_path / "second-scheduler.db" + ) + ): + pass + finally: + monkeypatch.setattr(atomic_memory_rebuild, "_record", original) + assert ( + deployment_runner(settings, embedding_profile=None if embedding is None else embedding.profile) + .verify() + .state + == "ready" + ) + assert client.get(path).json()["content"]["text"] == "After concurrent revision." diff --git a/tests/e2e/test_mcp_request_authentication.py b/tests/e2e/test_mcp_request_authentication.py index c601e444d1..43efb020a5 100644 --- a/tests/e2e/test_mcp_request_authentication.py +++ b/tests/e2e/test_mcp_request_authentication.py @@ -61,6 +61,7 @@ from powercontext.server.mcp import mount_mcp from powercontext.server.middleware import AuthenticationMiddleware from powercontext.server.settings import AccessControlConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database ALICE = PrincipalRef(type="user", id="alice") BOB = PrincipalRef(type="user", id="bob") @@ -356,6 +357,8 @@ async def generate(): def test_mcp_client_writes_reconnects_and_preserves_memory_after_server_restart(tmp_path: Path) -> None: async def scenario() -> None: database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'runtime.db'}") + settings = ServerSettings(database=database, access=AccessControlConfig(mode="enforced")) + await asyncio.to_thread(prepare_server_database, settings) authentication = _AuthenticationProvider() authentication.identities["Bearer alice-token"] = AuthenticationResult(subject=ALICE) auth = _SwitchableAuth() @@ -364,7 +367,7 @@ async def scenario() -> None: def server_app() -> FastAPI: return create_server_app( - settings=ServerSettings(database=database, access=AccessControlConfig(mode="enforced")), + settings=settings, scheduler_path=tmp_path / "scheduler.db", access_control=access, authentication_provider=authentication, diff --git a/tests/e2e/test_memory_capacity.py b/tests/e2e/test_memory_capacity.py index e7509872c9..483ebc02db 100644 --- a/tests/e2e/test_memory_capacity.py +++ b/tests/e2e/test_memory_capacity.py @@ -29,6 +29,7 @@ from powercontext.server.authz.composition import open_builtin_access_control from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, BearerAuthConfig, McpConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database def test_capacity_and_refusal_through_server_and_client(tmp_path): @@ -95,13 +96,15 @@ def test_legacy_capacity_authentication_and_explicit_refusal(tmp_path): async def scenario(): database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'access.db'}") + settings = ServerSettings( + database=database, + access=AccessControlConfig(mode="enforced"), + mcp=McpConfig(enabled=False), + ) + await asyncio.to_thread(prepare_server_database, settings) async with open_builtin_access_control(database) as access: app = create_server_app( - settings=ServerSettings( - database=database, - access=AccessControlConfig(mode="enforced"), - mcp=McpConfig(enabled=False), - ), + settings=settings, access_control=access, authentication_provider=StaticBearerAuthenticationProvider( "test-token", PrincipalRef(type="user", id="outsider") diff --git a/tests/e2e/test_native_code_history.py b/tests/e2e/test_native_code_history.py index b39231baf8..65f9a4ea2e 100644 --- a/tests/e2e/test_native_code_history.py +++ b/tests/e2e/test_native_code_history.py @@ -58,6 +58,15 @@ def test_http_code_prepare_preserves_approved_experience_when_memory_is_empty(tm lesson = "Budget preparation must preserve approved historical evidence." async def exercise(): + bootstrap = create_server_app( + settings=ServerSettings( + database=database, mcp=McpConfig(enabled=False), metrics=MetricsConfig(enabled=False) + ), + scheduler_path=tmp_path / "bootstrap-scheduler.db", + ) + # Initialize Server-owned storage before seeding it through the SDK. + async with bootstrap.router.lifespan_context(bootstrap): + pass async with open_builtin_runtime( BuiltinConfig(database=database), scheduler_path=tmp_path / "runtime-scheduler.db" ) as runtime: diff --git a/tests/e2e/test_native_code_multilanguage.py b/tests/e2e/test_native_code_multilanguage.py index 45fecd8bb6..a29dac15ba 100644 --- a/tests/e2e/test_native_code_multilanguage.py +++ b/tests/e2e/test_native_code_multilanguage.py @@ -51,6 +51,12 @@ def test_prepare_uses_each_language_and_drops_stale_code(tmp_path): database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'database.db'}") async def exercise(): + bootstrap = create_server_app( + settings=ServerSettings(database=database), scheduler_path=tmp_path / "bootstrap-scheduler.db" + ) + # Initialize Server-owned storage before seeding it through the SDK. + async with bootstrap.router.lifespan_context(bootstrap): + pass async with open_builtin_runtime( BuiltinConfig(database=database), scheduler_path=tmp_path / "scope.db" ) as runtime: diff --git a/tests/e2e/test_native_code_seekdb.py b/tests/e2e/test_native_code_seekdb.py index 4aa874f491..ab432a5fb0 100644 --- a/tests/e2e/test_native_code_seekdb.py +++ b/tests/e2e/test_native_code_seekdb.py @@ -511,11 +511,16 @@ def test_process_exit_before_publication_recovers_without_partial_results(reposi assert cursor.fetchone()[0] == 2 -def test_http_prepare_online_cli_sync_and_restart(tmp_path, backend): - database, _ = backend +def test_http_prepare_online_cli_sync_and_restart(tmp_path): + database = SeekDBConfig(path=tmp_path / "server-database") root = make_repository(tmp_path) async def exercise(): + bootstrap = create_server_app( + settings=ServerSettings(database=database, code=CodeConfig(enabled=True, cache_dir=tmp_path / "cache")) + ) + async with bootstrap.router.lifespan_context(bootstrap): + pass async with open_builtin_runtime( BuiltinConfig(database=database), scheduler_path=tmp_path / "scheduler.db" ) as runtime: diff --git a/tests/e2e/test_runtime_server.py b/tests/e2e/test_runtime_server.py index e30c80fefe..cf49415ab1 100644 --- a/tests/e2e/test_runtime_server.py +++ b/tests/e2e/test_runtime_server.py @@ -47,6 +47,7 @@ ) from powercontext.builtin.artifacts.memory.errors import InvalidMemoryCandidateError from powercontext.builtin.inference import EmbeddingResult, GenerationResult, InferenceConfigurationError +from powercontext.builtin.persistence.migrations.deployment import deployment_runner from powercontext.builtin.persistence.oceanbase import OceanBaseConfig, OceanBaseProfile from powercontext.builtin.persistence.sqlite import SQLiteConfig from powercontext.builtin.runtime import ( @@ -201,15 +202,38 @@ def _server_settings( ) +def _initialize_remote_server_storage( + settings: ServerSettings, tmp_path: Path, *, embedding_profile: EmbeddingProfile | None = None +) -> None: + if not isinstance(settings.database, OceanBaseConfig): + return + runner = deployment_runner( + settings, + evidence_dir=tmp_path / "migration-evidence", + lock_coordination="single-host", + embedding_profile=embedding_profile, + ) + plan = runner.plan(backup_policy="skip") + result = runner.apply( + plan_id=plan.plan_id, + backup_policy="skip", + accepted=True, + maintenance_confirmed=True, + accept_no_backup=True, + ) + assert result.state == "ready" + assert result.readiness_scope == "server" + + @pytest.mark.parametrize("database_kind", ["sqlite", "oceanbase"]) def test_server_databases_share_source_to_memory_search_behavior( database: SQLiteConfig | OceanBaseConfig, + tmp_path: Path, ) -> None: + settings = ServerSettings(database=database, mcp=McpConfig(enabled=False)) + _initialize_remote_server_storage(settings, tmp_path) app = create_server_app( - settings=ServerSettings( - database=database, - mcp=McpConfig(enabled=False), - ), + settings=settings, candidate_pipeline=atomic_memory_pipeline(ContentCandidatePipeline()), ) @@ -384,12 +408,13 @@ async def scenario() -> None: @pytest.mark.parametrize("database_kind", ["sqlite", "oceanbase"]) def test_server_databases_keep_case_and_accent_variant_identities_distinct( database: SQLiteConfig | OceanBaseConfig, + tmp_path: Path, ) -> None: marker = uuid4().hex[:12] memory_text = "Rotate the production signing key every ninety days." - app = create_server_app( - settings=ServerSettings(database=database, mcp=McpConfig(enabled=False)), - ) + settings = ServerSettings(database=database, mcp=McpConfig(enabled=False)) + _initialize_remote_server_storage(settings, tmp_path) + app = create_server_app(settings=settings) async def scenario() -> None: async with ( @@ -820,12 +845,12 @@ async def scenario() -> None: @pytest.mark.parametrize("database_kind", ["sqlite", "oceanbase"]) def test_server_databases_share_vector_and_hybrid_search_behavior( database: SQLiteConfig | OceanBaseConfig, + tmp_path: Path, ) -> None: + settings = ServerSettings(database=database, mcp=McpConfig(enabled=False)) + _initialize_remote_server_storage(settings, tmp_path, embedding_profile=EMBEDDING_PROFILE) app = create_server_app( - settings=ServerSettings( - database=database, - mcp=McpConfig(enabled=False), - ), + settings=settings, candidate_pipeline=atomic_memory_pipeline(ContentCandidatePipeline()), embedding_model=KeywordEmbeddingModel(), ) diff --git a/tests/e2e/test_server_metrics_oceanbase.py b/tests/e2e/test_server_metrics_oceanbase.py index 25a1bcdd55..37f22b6f61 100644 --- a/tests/e2e/test_server_metrics_oceanbase.py +++ b/tests/e2e/test_server_metrics_oceanbase.py @@ -22,6 +22,7 @@ from __future__ import annotations import asyncio +import json import os import signal import socket @@ -98,6 +99,45 @@ def test_real_oceanbase_cli_supervisor_metrics_auth_and_shutdown(oceanbase_url, "POWERCONTEXT_SERVER_RUNTIME_MEMORY_MAX_WORKERS": "1", "POWERCONTEXT_SERVER_METRICS_BACKGROUND_PORT": str(port), }) + migration_command = [sys.executable, "-c", "from powercontext.server.cli import app; app()", "db-migrate"] + migration_options = [ + "--evidence-dir", + str(tmp_path / "migration-evidence"), + "--lock-coordination", + "single-host", + "--backup", + "skip", + ] + planned = subprocess.run( + [*migration_command, "plan", *migration_options], + cwd=tmp_path, + env=environment, + capture_output=True, + text=True, + timeout=180, + check=False, + ) + assert planned.returncode == 0, planned.stdout + planned.stderr + applied = subprocess.run( + [ + *migration_command, + "apply", + *migration_options, + "--plan-id", + json.loads(planned.stdout)["plan_id"], + "--yes", + "--maintenance-confirmed", + "--accept-no-backup", + ], + cwd=tmp_path, + env=environment, + capture_output=True, + text=True, + timeout=180, + check=False, + ) + assert applied.returncode == 0, applied.stdout + applied.stderr + assert json.loads(applied.stdout)["server_ready"] is True log_path = tmp_path / "server.log" with log_path.open("w+") as output: process = subprocess.Popen( diff --git a/tests/e2e/test_tag_schema_upgrade.py b/tests/e2e/test_tag_schema_upgrade.py index 9922c9eba7..1ba735dcad 100644 --- a/tests/e2e/test_tag_schema_upgrade.py +++ b/tests/e2e/test_tag_schema_upgrade.py @@ -30,11 +30,15 @@ from pydantic import SecretStr from sqlalchemy.engine import make_url +from powercontext.builtin.persistence.migrations.connections import MaintenanceConnections +from powercontext.builtin.persistence.migrations.pinned import finish, pinned from powercontext.builtin.persistence.oceanbase import OceanBaseConfig, OceanBaseProfile from powercontext.builtin.persistence.seekdb import SeekDBConfig, SeekDBProfile +from powercontext.builtin.persistence.tag_schema import ensure_topic_memory_tag_schema from powercontext.builtin.runtime import RuntimeConfig from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, BearerAuthConfig, McpConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database @asynccontextmanager @@ -71,6 +75,9 @@ async def scenario(): access=AccessControlConfig(mode="disabled"), mcp=McpConfig(enabled=False), ) + await asyncio.to_thread( + prepare_server_database, settings, evidence_dir=tmp_path / "server-migration-evidence" + ) @asynccontextmanager async def client(): @@ -116,6 +123,21 @@ async def client(): f"ck_pc_artifact_tags_family CHECK (family IN ({families}))" ) + def upgrade_tags(): + def apply(connection, identity, guard): + assert connection is not None + guard() + finish(ensure_topic_memory_tag_schema(pinned(connection))) + connection.commit() + + MaintenanceConnections( + database, + evidence_directory=tmp_path / "tag-maintenance-evidence", + lock_coordination="single-host" if isinstance(database, OceanBaseConfig) else None, + ).run(apply, writable=True) + + await asyncio.to_thread(upgrade_tags) + # First startup still never uses Topic Memory; the second tests retry. for restart in range(2): async with client() as http: diff --git a/tests/e2e/test_topic_memory_generic_api.py b/tests/e2e/test_topic_memory_generic_api.py index 6052102b99..8bec75f88b 100644 --- a/tests/e2e/test_topic_memory_generic_api.py +++ b/tests/e2e/test_topic_memory_generic_api.py @@ -752,23 +752,26 @@ def test_legacy_tags_survive_transactional_upgrade_and_repeated_startup(tmp_path class InterruptedUpgrade(Exception): pass - async def interrupted_upgrade(): + async def upgrade(*, interrupt=False): engine = create_async_engine(f"sqlite+aiosqlite:///{database}") try: - with pytest.raises(InterruptedUpgrade): - async with engine.begin() as connection: - await ensure_topic_memory_tag_schema(connection) + async with engine.begin() as connection: + await ensure_topic_memory_tag_schema(connection) + if interrupt: raise InterruptedUpgrade finally: await engine.dispose() - asyncio.run(interrupted_upgrade()) + with pytest.raises(InterruptedUpgrade): + asyncio.run(upgrade(interrupt=True)) with sqlite3.connect(database) as connection: assert ( "ck_pc_artifact_tags_family" in connection.execute("SELECT sql FROM sqlite_master WHERE name = 'pc_artifact_tags'").fetchone()[0] ) assert connection.execute("SELECT * FROM pc_artifact_tags").fetchall() == rows + # Repair the isolated legacy constraint before normal Server admission. + asyncio.run(upgrade()) configuration_tags = {} for restart in range(2): with TestClient(_app(tmp_path)) as client: diff --git a/tests/e2e/test_topic_memory_recovery_access.py b/tests/e2e/test_topic_memory_recovery_access.py index 3b25d4e8da..2ae619c8f3 100644 --- a/tests/e2e/test_topic_memory_recovery_access.py +++ b/tests/e2e/test_topic_memory_recovery_access.py @@ -26,6 +26,7 @@ from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, McpConfig, MetricsConfig, ServerSettings from tests.builtin.runtime.test_topic_memory_terminal_frontier import _budget, _seed +from tests.e2e.server_storage import prepare_server_database from tests.e2e.test_access_control_regressions import ADMIN, _Authentication, _grant, _scope, _server from tests.e2e.test_topic_memory_terminal_frontier import _Handler, _Provider @@ -38,17 +39,19 @@ async def _recovery_server(tmp_path, backend, inference): yield app, client return database = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'recovery.db'}") + settings = ServerSettings( + database=database, + inference=inference, + runtime=RuntimeConfig(artifact_processing_families=("topic-memory",)), + access=AccessControlConfig(mode="enforced", deployment_id="recovery", background_principal_id=ADMIN.id), + mcp=McpConfig(enabled=False), + metrics=MetricsConfig(enabled=False), + ) + await asyncio.to_thread(prepare_server_database, settings) async with open_builtin_access_control(database, bootstrap_administrators=(ADMIN,), deployment_id="recovery"): pass app = create_server_app( - settings=ServerSettings( - database=database, - inference=inference, - runtime=RuntimeConfig(artifact_processing_families=("topic-memory",)), - access=AccessControlConfig(mode="enforced", deployment_id="recovery", background_principal_id=ADMIN.id), - mcp=McpConfig(enabled=False), - metrics=MetricsConfig(enabled=False), - ), + settings=settings, authentication_provider=_Authentication(), scheduler_path=tmp_path / "scheduler.db", ) diff --git a/tests/e2e/test_topic_memory_recovery_migration.py b/tests/e2e/test_topic_memory_recovery_migration.py index b9bfe6a834..4cfb6c6c2c 100644 --- a/tests/e2e/test_topic_memory_recovery_migration.py +++ b/tests/e2e/test_topic_memory_recovery_migration.py @@ -127,6 +127,21 @@ def _apply(runner): return runner.apply(plan_id=runner.plan().plan_id, accepted=True, maintenance_confirmed=True) +@pytest.mark.parametrize("revision", ["s1401", "unknown-future"]) +def test_topic_bundle_refuses_managed_server_storage_before_backup_or_writes(tmp_path, revision): + path = tmp_path / "managed-topic.db" + config, _ = _legacy(path) + with closing(sqlite3.connect(path)) as connection, connection: + connection.execute("CREATE TABLE pc_schema_revision (version_num VARCHAR(32) PRIMARY KEY NOT NULL)") + connection.execute("INSERT INTO pc_schema_revision VALUES (?)", (revision,)) + original = path.read_bytes() + runner = TopicMemoryRecoveryMigrationRunner(config) + with pytest.raises(MigrationError, match="unsupported_target"): + _apply(runner) + assert path.read_bytes() == original + assert not path.with_name(path.name + ".pc-migration-state").exists() + + @pytest.fixture def atomic_maintenance_database(tmp_path, monkeypatch): path = tmp_path / "atomic-maintenance.db" diff --git a/tests/e2e/test_topic_search_authorization_snapshot.py b/tests/e2e/test_topic_search_authorization_snapshot.py index 63bd9d35fa..87f0445b9e 100644 --- a/tests/e2e/test_topic_search_authorization_snapshot.py +++ b/tests/e2e/test_topic_search_authorization_snapshot.py @@ -51,6 +51,7 @@ ) from powercontext.server.factory import create_server_app from powercontext.server.settings import AccessControlConfig, McpConfig, MetricsConfig, ServerSettings +from tests.e2e.server_storage import prepare_server_database ADMIN = PrincipalRef(type="service", id="admin") VIEWER = PrincipalRef(type="user", id="viewer") @@ -159,6 +160,19 @@ async def _server( tmp_path, provider, embedding, *, database=None, external_policy=None, access_factory=None, access_database=None ): database = database or SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'topics.db'}") + settings = ServerSettings( + database=database, + runtime=RuntimeConfig(artifact_processing_families=()), + access=AccessControlConfig(mode="enforced", deployment_id="topic-snapshot"), + metrics=MetricsConfig(enabled=False), + mcp=McpConfig(enabled=False), + ) + await asyncio.to_thread( + prepare_server_database, + settings, + evidence_dir=tmp_path / "migration-evidence", + embedding_profile=embedding.profile, + ) opener = open_casbin_access_control if provider == "casbin" else open_builtin_access_control async def evaluate(request): @@ -200,13 +214,7 @@ def allowed(evaluation): if access_factory is not None: access = await access_factory(access) app = create_server_app( - settings=ServerSettings( - database=database, - runtime=RuntimeConfig(artifact_processing_families=()), - access=AccessControlConfig(mode="enforced", deployment_id="topic-snapshot"), - metrics=MetricsConfig(enabled=False), - mcp=McpConfig(enabled=False), - ), + settings=settings, scheduler_path=tmp_path / "scheduler.db", authentication_provider=_Authentication(), access_control=access, diff --git a/tests/fixtures/database_migrations/crash_after_registered_ddl.py b/tests/fixtures/database_migrations/crash_after_registered_ddl.py new file mode 100644 index 0000000000..303f6c9f9b --- /dev/null +++ b/tests/fixtures/database_migrations/crash_after_registered_ddl.py @@ -0,0 +1,74 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Interrupt a migration client or its engine after registered DDL completes.""" + +from __future__ import annotations + +import json +import os +import sys +import time +from pathlib import Path + +from pydantic import SecretStr +from sqlalchemy import Engine, event + +from powercontext.builtin.persistence.oceanbase import OceanBaseConfig +from tests.builtin.persistence.test_mysql_migration_runner import _BackendTarget, _full_server_runner + + +def main() -> None: + evidence = Path(sys.argv[1]) + backend = _BackendTarget( + OceanBaseConfig(url=SecretStr(os.environ["POWERCONTEXT_MIGRATION_CRASH_TARGET"])), evidence + ) + + @event.listens_for(Engine, "after_cursor_execute") + def exit_after_ddl(connection, _cursor, statement, _parameters, _context, _executemany): + if statement.lstrip().startswith("CREATE TABLE pc_retained_dream_candidate_versions"): + connection_id = connection.exec_driver_sql("SELECT CONNECTION_ID()").scalar_one() + wait_for_engine = os.environ.get("POWERCONTEXT_MIGRATION_WAIT_FOR_ENGINE_CRASH") == "1" + marker_name = "engine-crash-ready.json" if wait_for_engine else "client-exit.json" + with (evidence / marker_name).open("w", encoding="utf-8") as marker: + json.dump({"connection_id": connection_id}, marker) + marker.flush() + os.fsync(marker.fileno()) + if not wait_for_engine: + # Bypass cleanup and leave the remote interruption unacknowledged. + os._exit(73) + deadline = time.monotonic() + 180 + while not (evidence / "engine-stopped").exists(): + if time.monotonic() >= deadline: + raise TimeoutError("The parent did not confirm engine termination") # noqa: TRY003 + time.sleep(0.05) + # Require a real connection failure after the parent kills observer. + connection.exec_driver_sql("SELECT 1") + raise AssertionError("The migration connection survived engine termination") # noqa: TRY003 + + runner = _full_server_runner(backend) + plan = runner.plan(backup_policy="manual") + runner.apply( + plan_id=plan.plan_id, + accepted=True, + maintenance_confirmed=True, + backup_policy="manual", + backup_confirmed=True, + backup_ref="operator://manual-backup", + ) + raise AssertionError("The registered DDL exit point was not reached") # noqa: TRY003 + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/database_migrations/schemas/oceanbase_v1_3_0_audit.json b/tests/fixtures/database_migrations/schemas/oceanbase_v1_3_0_audit.json new file mode 100644 index 0000000000..3159c569ce --- /dev/null +++ b/tests/fixtures/database_migrations/schemas/oceanbase_v1_3_0_audit.json @@ -0,0 +1,7 @@ +{ + "tables": { + "pc_access_audit": "CREATE TABLE `pc_access_audit` (\n `cursor` int(11) NOT NULL AUTO_INCREMENT,\n `event_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `occurred_at` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `request_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `transport` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `operation` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `principal_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `principal_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `principal_description` text DEFAULT NULL,\n `actor_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `actor_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `actor_description` text DEFAULT NULL,\n `action` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `resource_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `deployment_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `selector_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `selector_entry_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `allowed` tinyint(1) NOT NULL,\n `reason_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `policy_revision` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `matched_subject_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `matched_subject_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `matched_subject_description` text DEFAULT NULL,\n `binding_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `target_description` text DEFAULT NULL,\n `role` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `expected_version` int(11) DEFAULT NULL,\n `result_version` int(11) DEFAULT NULL,\n PRIMARY KEY (`cursor`),\n UNIQUE KEY `event_id` (`event_id`) BLOCK_SIZE 16384 LOCAL\n) ORGANIZATION INDEX AUTO_INCREMENT = 37 AUTO_INCREMENT_MODE = 'ORDER' DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE ENABLE_MACRO_BLOCK_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0" + }, + "engine": "5.7.25-OceanBase_CE-v4.3.5.6", + "source_release": "1.3.0" +} diff --git a/tests/fixtures/database_migrations/schemas/seekdb_v1_3_0_fulltext.json b/tests/fixtures/database_migrations/schemas/seekdb_v1_3_0_fulltext.json new file mode 100644 index 0000000000..a18f9be511 --- /dev/null +++ b/tests/fixtures/database_migrations/schemas/seekdb_v1_3_0_fulltext.json @@ -0,0 +1,13 @@ +{ + "source_release": "1.3.0", + "source_wheel_sha256": "8da7983763f3a6ab80b413c770a4973a46c557a54e1a1fa675099e58d47305e5", + "capture": "Installed public release Server with enforced Access, all Processing roles, and no model or embedding provider; public HTTP fixture; maintenance reflection after process exit.", + "backend": "5.7.25-OceanBase seekdb-v1.4.0.0", + "pylibseekdb": "1.4.0.post1", + "tables": { + "pc_artifact_heads": "CREATE TABLE `pc_artifact_heads` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `searchable_text` mediumtext DEFAULT NULL,\n `lifecycle_state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'active',\n `replacement_artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `merged_into_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `governance_generation` bigint(20) NOT NULL DEFAULT '0',\n PRIMARY KEY (`scope_id`, `family`, `artifact_id`),\n CONSTRAINT `pc_artifact_heads_OBFK_1792659503573247` FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `test`.`pc_artifacts`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FULLTEXT KEY `ix_pc_artifact_heads_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_artifact_heads_revision_positive` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_artifact_heads_lifecycle_state` CHECK ((`lifecycle_state` in ('active','deprecated','retired'))),\n CONSTRAINT `ck_pc_artifact_heads_governance_generation_nonnegative` CHECK ((`governance_generation` >= 0)),\n CONSTRAINT `ck_pc_artifact_heads_replacement_deprecated` CHECK (((`replacement_artifact_id` is null) or (`lifecycle_state` = 'deprecated'))),\n CONSTRAINT `ck_pc_artifact_heads_merge_target` CHECK (((`merged_into_id` is null) or ((`lifecycle_state` = 'deprecated') and (`merged_into_id` <> `artifact_id`) and (`replacement_artifact_id` is null))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "pc_atomic_memory_current": "CREATE TABLE `pc_atomic_memory_current` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` bigint(20) NOT NULL,\n `state_version` bigint(20) NOT NULL,\n `content_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `projection_format` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `kind` mediumtext NOT NULL,\n `text` mediumtext NOT NULL,\n `searchable_text` mediumtext NOT NULL,\n `tag_keys` mediumtext NOT NULL,\n `embedding` blob DEFAULT NULL,\n `profile_fingerprint` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n `embedding_input_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n PRIMARY KEY (`scope_id`, `artifact_id`),\n FULLTEXT KEY `ix_pc_atomic_memory_current_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_atomic_memory_current_revision` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_atomic_memory_current_state_version` CHECK ((`state_version` >= 0)),\n CONSTRAINT `ck_pc_atomic_memory_current_embedding_metadata` CHECK ((((`embedding` is null) and (`profile_fingerprint` is null) and (`embedding_input_hash` is null)) or ((`embedding` is not null) and (`profile_fingerprint` is not null) and (`embedding_input_hash` is not null))))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "pc_topic_memory_active_chunks": "CREATE TABLE `pc_topic_memory_active_chunks` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `chunk_ordinal` int(11) NOT NULL,\n `start_offset` int(11) NOT NULL,\n `end_offset` int(11) NOT NULL,\n `chunk_text` mediumtext NOT NULL,\n `searchable_text` mediumtext NOT NULL,\n `policy_version` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n PRIMARY KEY (`scope_id`, `artifact_id`, `chunk_ordinal`),\n CONSTRAINT `pc_topic_memory_active_chunks_OBFK_1792659503857007` FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `test`.`pc_topic_memory_revision_publications`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FULLTEXT KEY `ix_pc_topic_memory_active_chunks_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_topic_memory_active_chunks_family` CHECK ((`family` = 'topic-memory')),\n CONSTRAINT `ck_pc_topic_memory_active_chunks_revision_positive` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_topic_memory_active_chunks_ordinal_nonnegative` CHECK ((`chunk_ordinal` >= 0)),\n CONSTRAINT `ck_pc_topic_memory_active_chunks_start_nonnegative` CHECK ((`start_offset` >= 0)),\n CONSTRAINT `ck_pc_topic_memory_active_chunks_offsets` CHECK ((`end_offset` > `start_offset`))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0", + "pc_topic_memory_active_topics": "CREATE TABLE `pc_topic_memory_active_topics` (\n `scope_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `family` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `artifact_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,\n `revision` int(11) NOT NULL,\n `title` mediumtext NOT NULL,\n `summary` mediumtext NOT NULL,\n `searchable_text` mediumtext NOT NULL,\n `source_count` int(11) NOT NULL,\n PRIMARY KEY (`scope_id`, `artifact_id`),\n CONSTRAINT `pc_topic_memory_active_topics_OBFK_1792659503833750` FOREIGN KEY (`scope_id`, `family`, `artifact_id`, `revision`) REFERENCES `test`.`pc_topic_memory_revision_publications`(`scope_id`, `family`, `artifact_id`, `revision`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n FULLTEXT KEY `ix_pc_topic_memory_active_topics_fts` (`searchable_text`) WITH PARSER space PARSER_PROPERTIES=(min_token_size=3,max_token_size=84) BLOCK_SIZE 16384,\n CONSTRAINT `ck_pc_topic_memory_active_topics_family` CHECK ((`family` = 'topic-memory')),\n CONSTRAINT `ck_pc_topic_memory_active_topics_revision_positive` CHECK ((`revision` > 0)),\n CONSTRAINT `ck_pc_topic_memory_active_topics_sources_nonnegative` CHECK ((`source_count` >= 0))\n) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' BLOCK_SIZE = 16384 TABLET_SIZE = 134217728 PCTFREE = 0" + } +} diff --git a/tests/native/test_oceanbase_engine_recovery.py b/tests/native/test_oceanbase_engine_recovery.py new file mode 100644 index 0000000000..480febf54c --- /dev/null +++ b/tests/native/test_oceanbase_engine_recovery.py @@ -0,0 +1,211 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Actual observer crash/reopen acceptance in an explicitly owned disposable container.""" + +from __future__ import annotations + +import json +import os +import re +import subprocess +import sys +import time +from collections.abc import Iterator +from pathlib import Path +from uuid import uuid4 + +import pytest +from pydantic import SecretStr +from sqlalchemy.engine import make_url + +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.oceanbase import OceanBaseConfig +from tests.builtin.persistence.test_mysql_migration_runner import ( + _apply, + _BackendTarget, + _full_server_runner, + _load_full_server_baseline, +) + +pytestmark = pytest.mark.skipif( + not os.environ.get("POWERCONTEXT_TEST_MIGRATION_OCEANBASE_ENGINE_CRASH_ID"), + reason="requires the exact ID and loopback URL of a disposable OceanBase container owned by this test run", +) + + +def _docker(container: str, operation: str, *arguments: str, check: bool = True) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["docker", operation, container, *arguments], # noqa: S607 + capture_output=True, + text=True, + timeout=30, + check=check, + ) + + +@pytest.fixture +def engine_target(tmp_path: Path) -> Iterator[tuple[str, _BackendTarget]]: + container = os.environ["POWERCONTEXT_TEST_MIGRATION_OCEANBASE_ENGINE_CRASH_ID"] + assert re.fullmatch(r"[0-9a-f]{64}", container), "Supply the exact owned container ID" + (info,) = json.loads(_docker(container, "inspect").stdout) + assert info["Id"] == container and info["State"]["Running"] + config = OceanBaseConfig(url=SecretStr(os.environ["POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL"])) + url = make_url(config.url.get_secret_value()) + assert url.host in {"127.0.0.1", "localhost"} and url.database + assert url.database.startswith("pc_migration_probe_") + assert not any(key.lower() in {"db", "database"} for key in url.query), ( + "The crash target must not override its selected database" + ) + (binding,) = info["NetworkSettings"]["Ports"]["2881/tcp"] + assert binding["HostIp"] == "127.0.0.1" and int(binding["HostPort"]) == (url.port or 2881) + (tmp_path / "container.json").write_text( + json.dumps({"id": container, "image": info["Config"]["Image"], "image_id": info["Image"], "port": url.port}), + encoding="utf-8", + ) + evidence = tmp_path / "evidence" + management = _BackendTarget(config, evidence) + database = f"pc_migration_probe_crash_{uuid4().hex}" + + def create(connection, _identity, _guard): + assert connection is not None + connection.exec_driver_sql(f"CREATE DATABASE `{database}`") + + management.connections().run(create) + child = config.model_copy( + update={"url": SecretStr(url.set(database=database).render_as_string(hide_password=False))} + ) + try: + yield container, _BackendTarget(child, evidence) + finally: + # Keep the owned database and journal for diagnosis, including on failure. + result = _docker(container, "logs", check=False) + (tmp_path / "oceanbase.log").write_text(result.stdout + result.stderr, encoding="utf-8") + + +def test_oceanbase_observer_crash_resumes_original_migration_and_preserves_history( + engine_target: tuple[str, _BackendTarget], tmp_path: Path +) -> None: + container, backend = engine_target + assert isinstance(backend.config, OceanBaseConfig) + _load_full_server_baseline(backend) + evidence = backend.evidence_directory + root = Path(__file__).resolve().parents[2] + environment = { + **os.environ, + "PYTHONPATH": os.pathsep.join(filter(None, (str(root), os.environ.get("PYTHONPATH")))), + "POWERCONTEXT_MIGRATION_CRASH_TARGET": backend.config.url.get_secret_value(), + "POWERCONTEXT_MIGRATION_WAIT_FOR_ENGINE_CRASH": "1", + } + with (tmp_path / "migration-client.log").open("w", encoding="utf-8") as log: + child = subprocess.Popen( + [ + sys.executable, + str(root / "tests/fixtures/database_migrations/crash_after_registered_ddl.py"), + str(evidence), + ], + env=environment, + stdout=log, + stderr=subprocess.STDOUT, + ) + try: + deadline = time.monotonic() + 180 + while not (evidence / "engine-crash-ready.json").exists(): + assert child.poll() is None, "Migration exited before reaching the engine crash boundary" + assert time.monotonic() < deadline, "Migration did not reach the registered DDL" + time.sleep(0.1) + (journal,) = evidence.rglob("maintenance.json") + interrupted = json.loads(journal.read_text()) + assert interrupted["state"] == "active" + (pid,) = _docker(container, "exec", "pgrep", "-x", "observer").stdout.split() + assert pid.isdigit() + cwd = _docker(container, "exec", "readlink", f"/proc/{pid}/cwd").stdout.strip() + executable = _docker(container, "exec", "readlink", f"/proc/{pid}/exe").stdout.strip() + expected = _docker(container, "exec", "readlink", "-f", "/root/demo/bin/observer").stdout.strip() + assert cwd == "/root/demo" and executable == expected + _docker(container, "exec", "kill", "-KILL", pid) + assert _docker(container, "exec", "readlink", f"/proc/{pid}/exe", check=False).returncode != 0 + (evidence / "engine-stopped").write_text("Owned observer terminated with SIGKILL.\n", encoding="utf-8") + assert child.wait(timeout=120) != 0 + finally: + if child.poll() is None: + child.terminate() + child.wait(timeout=30) + # The slim image's entrypoint unfolds a snapshot and truncates logs. Restart + # only observer so this test recovers the same data and commit logs. + subprocess.run( + ["docker", "exec", "--workdir", cwd, "--env", "LD_LIBRARY_PATH=/root/demo/lib", container, "bin/observer"], # noqa: S607 + capture_output=True, + text=True, + timeout=30, + check=True, + ) + deadline = time.monotonic() + 240 + + def ping(connection, _identity, _guard): + assert connection is not None + return connection.exec_driver_sql("SELECT 1").scalar_one() + + while True: + try: + assert backend.connections().run(ping) == 1 + break + except Exception: + if time.monotonic() >= deadline: + raise + time.sleep(1) + (new_pid,) = _docker(container, "exec", "pgrep", "-x", "observer").stdout.split() + assert new_pid != pid + recovery = _full_server_runner(backend).plan(backup_policy="manual") + assert recovery.state == "recovery_required" and recovery.source_revision == "s1300" + token = (recovery.coordination or {}).get("pending_run_id") + assert token + with pytest.raises(MigrationError, match="recovery_required"): + _apply(_full_server_runner(backend), policy="manual") + resumed = _full_server_runner(backend, acknowledge_previous_run=token) + result = _apply(resumed, policy="manual") + assert result.state == "ready" and result.revision == "s1402" and result.readiness_scope == "server" + assert result.backup_state == "user_confirmed" and result.backup_ref == interrupted["manual_ref"] + assert resumed.verify().state == "ready" + completed = json.loads(journal.read_text()) + assert completed["state"] == "complete" + for key in ("maintenance_window_id", "source_fingerprint", "manual_ref"): + assert completed[key] == interrupted[key] + + def history(connection, _identity, _guard): + assert connection is not None + return ( + connection.exec_driver_sql( + "SELECT content FROM pc_artifacts WHERE scope_id='historical' AND artifact_id='preserved'" + ).scalar_one(), + connection.exec_driver_sql("SELECT title,summary FROM pc_scopes WHERE scope_id='historical'").one(), + ) + + assert backend.connections().run(history) == ( + b'{"history":"unchanged"}', + ("retained title", "retained summary"), + ) + assert not _apply(_full_server_runner(backend), policy="manual").changed + (tmp_path / "engine-recovery.json").write_text( + json.dumps({ + "previous_pid": pid, + "pid": new_pid, + "revision": result.revision, + "history_preserved": True, + "recovery_identity_preserved": True, + "repeat_unchanged": True, + "backup": "operator declaration, restoration not tested", + }), + encoding="utf-8", + ) diff --git a/tests/native/test_personal_service_lifecycle.py b/tests/native/test_personal_service_lifecycle.py index 9d2c807c10..60fcb622ec 100644 --- a/tests/native/test_personal_service_lifecycle.py +++ b/tests/native/test_personal_service_lifecycle.py @@ -14,29 +14,41 @@ from __future__ import annotations +import errno import json import os import plistlib import re import socket +import sqlite3 import subprocess import sys import time import uuid import xml.etree.ElementTree as ET -from collections.abc import Callable -from contextlib import suppress +from collections.abc import Callable, Iterator +from contextlib import closing, contextmanager, suppress from importlib.metadata import version from pathlib import Path from typing import Any +import httpx import pytest - +from alembic import command +from sqlalchemy import create_engine +from typer.testing import CliRunner + +from powercontext.builtin.persistence.migrations.deployment import deployment_runner, server_bundle +from powercontext.cli.app import create_cli +from powercontext.server import database_migration +from powercontext.server.cli import app as server_app +from powercontext.server.configuration import server_settings_context from powercontext.service.adapters.base import NativeServiceAdapter, service_python_executable from powercontext.service.adapters.launchd import LaunchdUserAdapter from powercontext.service.adapters.systemd import SystemdUserAdapter from powercontext.service.adapters.windows import WindowsTaskSchedulerAdapter from powercontext.service.controller import ServiceController +from powercontext.service.environment import load_protected_environment_file from powercontext.service.model import ( DEFINITION_VERSION, OWNERSHIP_MARKER, @@ -135,6 +147,341 @@ def test_native_maintenance_preserves_registration_and_original_running_state(tm _cleanup(adapter) +def test_native_public_migration_upgrades_a_stopped_historical_database( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """Exercise the installed CLI, frozen SQLite baseline and real native manager together.""" + + adapter = _native_adapter(suffix="database-upgrade") + controller = ServiceController(adapter) + environment = _environment_file(tmp_path) + database = tmp_path / "data" / "powercontext.db" + database.parent.mkdir(parents=True, exist_ok=True) + with server_settings_context(env_file=environment) as settings: + runner = deployment_runner(settings) + bundle = server_bundle() + engine = create_engine(f"sqlite:///{database.as_posix()}") + try: + with engine.begin() as connection: + config = bundle.config(connection) + config.attributes["configuration"] = runner.configuration + command.upgrade(config, "s1300") + connection.exec_driver_sql("DROP TABLE pc_schema_revision") + connection.exec_driver_sql( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version) " + "VALUES ('historical','retained title','retained summary','historical','retained title','retained summary',1)" + ) + finally: + engine.dispose() + port = _service_port(tmp_path) + assert port is not None + # A stopped registration represents the historical deployment. Its database + # comes from frozen migration resources, not an installed 1.3.0 executable. + definition = ServiceDefinition( + ownership=OWNERSHIP_MARKER, + definition_version=DEFINITION_VERSION, + package_version="frozen-s1300-fixture", + python_executable=service_python_executable(), + endpoint=f"http://127.0.0.1:{port}", + data_dir=str(database.parent), + env_file=load_protected_environment_file(environment).identity, + ) + try: + adapter.write(adapter.render(definition)) + adapter.update_suspended() + controller.stop() + # Select only this uniquely named native registration; all manager, + # database, backup and readiness operations remain real. + monkeypatch.setattr(database_migration, "ServiceController", lambda: controller) + cli = create_cli([server_app]) + reviewed = _native_migration_command(cli, environment, "plan") + assert reviewed["state"] == "migration_required" + assert reviewed["service"]["originally_running"] is False + + result = _native_migration_command( + cli, + environment, + "apply", + "--backup", + "auto", + "--plan-id", + reviewed["plan_id"], + "--yes", + "--maintenance-confirmed", + ) + + assert result["server_ready"] is True + assert result["backup_state"] == "completed" + assert result["backup_ref"] and Path(result["backup_ref"]).is_file() + assert runner.verify().state == "ready" + assert adapter.manager_state() in {ManagerState.INACTIVE, ManagerState.FAILED} + with closing(sqlite3.connect(database)) as connection: + assert connection.execute("SELECT title,summary FROM pc_scopes WHERE scope_id='historical'").fetchone() == ( + "retained title", + "retained summary", + ) + assert controller.maintenance_path.exists() + assert controller.start().ok, _failure_diagnostics(adapter, tmp_path) + assert not controller.maintenance_path.exists() + finally: + _capture_native_failure(adapter, tmp_path) + with suppress(Exception): + controller.uninstall() + _cleanup(adapter) + + +def test_native_public_migration_of_ready_storage_restores_a_running_service( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """A reviewed ready-database retry must still finish native service maintenance.""" + + adapter = _native_adapter(suffix="ready-database") + controller = ServiceController(adapter) + environment = _environment_file(tmp_path) + try: + assert controller.install(env_file=environment).ok + monkeypatch.setattr(database_migration, "ServiceController", lambda: controller) + cli = create_cli([server_app]) + reviewed = _native_migration_command(cli, environment, "plan") + assert reviewed["state"] == "ready" + assert reviewed["service"]["originally_running"] is True + + result = _native_migration_command( + cli, + environment, + "apply", + "--backup", + "auto", + "--plan-id", + reviewed["plan_id"], + "--yes", + "--maintenance-confirmed", + ) + + assert result["server_ready"] is True and result["changed"] is False + assert controller.status().ok, _failure_diagnostics(adapter, tmp_path) + assert adapter.manager_state() is ManagerState.ACTIVE + assert not controller.maintenance_path.exists() + finally: + _capture_native_failure(adapter, tmp_path) + with suppress(Exception): + controller.uninstall() + _cleanup(adapter) + + +@pytest.mark.parametrize( + ("originally_running", "failure"), + [(True, None), (False, None), (True, "backup"), (False, "backup"), (True, "startup"), (True, "switch")], + ids=["running", "stopped", "backup-retry-running", "backup-retry-stopped", "startup-retry", "switch-retry"], +) +def test_native_public_migration_switches_an_installed_130_service( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, originally_running: bool, failure: str | None +) -> None: + """Upgrade storage populated by the released service through its actual native manager.""" + + source = Path(os.environ["POWERCONTEXT_NATIVE_UPGRADE_SOURCE_PYTHON"]) + source_version = _run( + str(source), "-I", "-c", 'from importlib.metadata import version; print(version("powercontext"))' + ).stdout.strip() + assert source_version == "1.3.0" + source_launcher = source.with_name("pythonw.exe") if os.name == "nt" else source + assert source_launcher.is_file() + adapter = _native_adapter(suffix=f"installed-upgrade-{'running' if originally_running else 'stopped'}") + controller = ServiceController(adapter) + environment = _environment_file(tmp_path) + with environment.open("a", encoding="utf-8") as output: + output.write( + "POWERCONTEXT_SERVER_AUTH_TOKEN=native-upgrade-fixture-token\nPOWERCONTEXT_SERVER_ACCESS_MODE=enforced\n" + ) + port = _service_port(tmp_path) + assert port is not None + definition = ServiceDefinition( + ownership=OWNERSHIP_MARKER, + definition_version=DEFINITION_VERSION, + package_version=source_version, + python_executable=str(source_launcher.absolute()), + endpoint=f"http://127.0.0.1:{port}", + data_dir=str(tmp_path / "data"), + env_file=load_protected_environment_file(environment).identity, + ) + try: + adapter.write(adapter.render(definition)) + adapter.update_suspended() + # Starting a valid older executable intentionally leaves a stale-version + # status until migration switches the registration to this environment. + try: + controller.start() + except ServiceError as error: + pytest.fail(f"{error}\n\n{_failure_diagnostics(adapter, tmp_path)}") + assert adapter.manager_state() is ManagerState.ACTIVE, _failure_diagnostics(adapter, tmp_path) + with httpx.Client( + base_url=definition.endpoint, + headers={"Authorization": "Bearer native-upgrade-fixture-token"}, + timeout=30, + trust_env=False, + ) as client: + created = client.post( + "/v1/scopes", + json={ + "title": "Installed service upgrade", + "summary": "Preserve data across the native executable switch.", + "idempotency_key": "native-installed-upgrade", + }, + ) + assert created.is_success, created.text + scope_id = created.json()["scope_id"] + if not originally_running: + controller.stop() + # Select this isolated registration; the manager and migration remain real. + monkeypatch.setattr(database_migration, "ServiceController", lambda: controller) + cli = create_cli([server_app]) + reviewed = _native_migration_command(cli, environment, "plan") + assert reviewed["state"] == "migration_required" + assert reviewed["service"]["originally_running"] is originally_running + assert reviewed["service"]["definition"]["python_executable"] == definition.python_executable + assert reviewed["service"]["target_definition"]["python_executable"] == service_python_executable() + database = tmp_path / "data" / "powercontext.db" + if failure == "backup": + blocked_directory = database.with_name(database.name + ".pc-migration-backups") + blocked_directory.write_text("Backup storage is unavailable.", encoding="utf-8") + failed = CliRunner().invoke( + cli, + [ + "server", + "db-migrate", + "apply", + "--env-file", + str(environment), + "--manage-service", + "--backup", + "auto", + "--plan-id", + reviewed["plan_id"], + "--yes", + "--maintenance-confirmed", + ], + ) + assert failed.exit_code == 1, failed.output + assert json.loads(failed.output)["error"] == "backup_failed" + assert adapter.manager_state() in {ManagerState.INACTIVE, ManagerState.FAILED} + assert controller.maintenance_path.exists() + assert adapter.inspect().definition == definition + with sqlite3.connect(database.as_uri() + "?mode=ro", uri=True) as unchanged: + assert unchanged.execute( + "SELECT title FROM pc_scopes WHERE scope_id=?", (scope_id,) + ).fetchone() == ("Installed service upgrade",) + assert ( + unchanged.execute("SELECT name FROM sqlite_master WHERE name='pc_schema_revision'").fetchall() + == [] + ) + blocked_directory.unlink() + reviewed = _native_migration_command(cli, environment, "plan") + assert reviewed["service"]["originally_running"] is originally_running + with _native_upgrade_fault(adapter, port, failure, monkeypatch): + applied = CliRunner().invoke( + cli, + [ + "server", + "db-migrate", + "apply", + "--env-file", + str(environment), + "--manage-service", + "--backup", + "auto", + "--plan-id", + reviewed["plan_id"], + "--yes", + "--maintenance-confirmed", + ], + ) + result = json.loads(applied.output) + if failure in {"startup", "switch"}: + assert applied.exit_code == 1, applied.output + assert result["error"] == "service_start_failed" + assert result["database_state"] == "ready" and result["revision"] == "s1402" + assert adapter.manager_state() in {ManagerState.INACTIVE, ManagerState.FAILED} + phase = "switching" if failure == "switch" else "start_failed" + assert json.loads(controller.maintenance_path.read_text())["phase"] == phase + if failure == "switch": + assert adapter.inspect().definition == definition + else: + with socket.create_connection(("127.0.0.1", port), timeout=2): + pass + else: + assert applied.exit_code == 0, f"{applied.output}\n{applied.exception!r}" + assert result["server_ready"] is True + assert result["backup_state"] == "completed" + assert result["backup_ref"] and Path(result["backup_ref"]).is_file() + if failure in {"startup", "switch"}: + before = database.read_bytes() + with server_settings_context(env_file=environment) as settings: + assert deployment_runner(settings).verify().state == "ready" + assert database.read_bytes() == before + # Recovery starts the verified registration without another + # migration apply or backup. + assert controller.start().ok, _failure_diagnostics(adapter, tmp_path) + installed = adapter.inspect().definition + assert installed is not None + assert installed.python_executable == service_python_executable() + assert installed.package_version == version("powercontext") + if not originally_running: + assert adapter.manager_state() in {ManagerState.INACTIVE, ManagerState.FAILED} + assert controller.maintenance_path.exists() + assert controller.start().ok, _failure_diagnostics(adapter, tmp_path) + assert controller.status().ok, _failure_diagnostics(adapter, tmp_path) + assert not controller.maintenance_path.exists() + retained = client.get(f"/v1/scopes/{scope_id}") + assert retained.is_success, retained.text + assert retained.json()["title"] == "Installed service upgrade" + assert client.get("/health/ready").status_code == 200 + finally: + _capture_native_failure(adapter, tmp_path) + with suppress(Exception): + controller.uninstall() + _cleanup(adapter) + + +@contextmanager +def _native_upgrade_fault( + adapter: NativeServiceAdapter, port: int, failure: str | None, monkeypatch: pytest.MonkeyPatch +) -> Iterator[None]: + with closing(socket.socket()) as listener, monkeypatch.context() as fault: + if failure == "startup": + native_start = adapter.start + + def occupy_then_start(*, reload_definition: bool) -> None: + # Bind after migration and stopped checks, then let the actual + # native manager attempt to start the registered executable. + if sys.platform != "win32": + # Reuse the stopped server's port even while its accepted + # connections remain in TIME_WAIT. + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(("127.0.0.1", port)) + listener.listen(16) + native_start(reload_definition=reload_definition) + + fault.setattr(adapter, "start", occupy_then_start) + elif failure == "switch": + native_replace = os.replace + + def fail_definition_replace(source: str | os.PathLike[str], destination: str | os.PathLike[str]) -> None: + if Path(destination) == adapter.artifact_path: + raise OSError(errno.ENOSPC, "Test registration storage is full") + native_replace(source, destination) + + fault.setattr(os, "replace", fail_definition_replace) + yield + + +def _native_migration_command(cli, environment: Path, action: str, *options: str) -> dict[str, Any]: + result = CliRunner().invoke( + cli, ["server", "db-migrate", action, "--env-file", str(environment), "--manage-service", *options] + ) + assert result.exit_code == 0, f"{result.output}\n{result.exception!r}" + return json.loads(result.output) + + def test_native_stopped_service_registration_updates_without_starting(tmp_path: Path) -> None: """Updating a stopped registration must leave activation to an explicit start.""" diff --git a/tests/release_upgrades/README.md b/tests/release_upgrades/README.md new file mode 100644 index 0000000000..50a44add81 --- /dev/null +++ b/tests/release_upgrades/README.md @@ -0,0 +1,152 @@ +# Installed-release upgrade acceptance + +`release/upgrade-support.json` requires **positive** 1.3.0 upgrades on SQLite, embedded seekDB, and OceanBase. +The current `upgrade-required` mode keeps the upgrade support declaration unverified while requiring every case +to run and pass. Missing full migration integration (#2154) fails the gate. `supported-upgrades` runs the same +matrix and enables publication after the reviewed support declaration is updated. `fresh-install-only` is rejected. + +## Fixed source and current target + +The `automatic_sources` policy uses `strategy: "fixed-version"` and `source_version: "1.3.0"`, with SQLite, +embedded seekDB, and OceanBase profiles. Every run expands that exact source across the three backends. The target +is the **actual wheel built by this job**, not the latest published PyPI package. Thus releases 1.4.1 and 1.5.0 +exercise 1.3.0 → 1.4.1 and 1.3.0 → 1.5.0 without adding test scripts or changing the source version. Newly published +intermediate versions do not become additional sources. This fixed baseline does not establish support for every +intermediate release's database or data shapes; add explicit `cases` if those need separate acceptance. + +The runner resolves the exact source wheel from public PyPI and installs its URL with a SHA-256. Missing/yanked +source wheels, invalid artifact metadata, catalog outages, and a target at or below the source version fail the gate. +Evidence records the catalog digest, exact source artifact, target artifact digest, and expanded matrix. The resolver +requires universal `py3-none-any` wheels; a distribution format change must extend it explicitly. + +All twelve profiles use `test_full_server.py`: Code enabled or disabled, with or without vectors, on SQLite, +seekDB and OceanBase. Vector cases use exact unit-normalized L2 profiles: dimension three without Code and +dimension four with Code. The local controlled embedding endpoint serves actual installed source/target processes; +it does not replace either runtime or its persistence. Each source Server writes nonempty Atomic Memory and Topic +vectors. Maintenance must preserve the exact historical embedding digests without calling that endpoint. Target +acceptance checks historical and new vector retrieval, Topic content, configured profile drift without writes, +and fresh-storage equivalence. SQLite additionally rejects a missing vector helper without repairing the clone. +These assertions establish deterministic execution and space preservation, not embedding model quality. +Code cases preserve immutable cache files and original fingerprint/symbol identities. Embedded seekDB additionally +preserves the exact graph-table row digests through maintenance; its frozen extension creates the same schema for fresh storage. +The suite starts the installed 1.3.0 Server, writes data through HTTP, +stops it, seeds a pending processing intent using the installed SDK, and runs the target wheel's public +`server db-migrate status/plan/apply/verify` commands. Verification must report `state=ready` and `server_ready=true`. +The target Server must then start, preserve data and permissions, accept new writes, and support retrieval. +Partial migration success, missing framework/baseline support, and startup rejection all fail. SQLite profiles use +automatic backup and restore that recovery point into a separate database. The suite compares the complete +restored source schema and row digests, reads historical Memory, Candidates, grants and cursors with installed +1.3.0, upgrades the restored database again, then verifies target reads, FTS and new writes. Code-enabled SQLite +also preserves its separate immutable cache files. The original backup checksum must remain unchanged. +seekDB and OceanBase fixtures explicitly accept no backup; these cases do not establish backup restoration. +This is not a production upgrade procedure. + +`cases` remains available for additional exact source versions/configurations. Each case needs `source_version`, +`backend`, `configuration`, and `suite`; overlapping cases fail. Optional features that create persistent objects +require additional profiles. The fixed CI baseline and advertised upgrade support must stay consistent. + +PR/master and publication CI start an isolated OceanBase CE container using the image and MySQL tenant setup from +the existing E2E workflow. They wait for SQL readiness and adequate datafile capacity before testing, save diagnostics, +and remove the container even when a check fails. No external database credentials are needed. Embedded seekDB +runs directly in a temporary runner directory. A backend startup failure fails the job instead of skipping it. + +The runner creates separate source/target environments, installs the exact published source package and the built +target wheel, verifies their versions, then executes the reviewed suite in an isolated working directory. Repository +pytest configuration and deployment environment variables are excluded. The suite must exercise those installed +processes, not import the current checkout's runtime in place of either version. Use `-I` when invoking their Python. + +The suite receives: + +- `PC_UPGRADE_SOURCE_PYTHON`, `PC_UPGRADE_TARGET_PYTHON`: isolated installed interpreters. +- `PC_UPGRADE_SOURCE_VERSION`: the exact source release selected for this case. +- `PC_UPGRADE_BACKEND`, `PC_UPGRADE_CONFIGURATION`: the declared case. +- `PC_UPGRADE_WORK`: disposable workspace for both databases, controlled inference fixtures, and environment files. +- `PC_UPGRADE_EVIDENCE`: destination for backend evidence JSON. +- `PC_UPGRADE_REPORT_DIR`: persistent per-case command logs, fixture snapshots and schema inventories. +- `PC_UPGRADE_OCEANBASE_URL`, `PC_UPGRADE_OCEANBASE_FRESH_URL`: dedicated disposable MySQL-tenant databases for + the upgraded and fresh-install comparisons. CI supplies both; local positive OceanBase suites must supply their + own isolated databases. Each suite must isolate/reset its own fixtures when multiple configurations run. + +Provide these behavioral tests (additional tests are allowed): + +| Test | Required behavior | +| --- | --- | +| `test_source_release_database` | Start the actual source Server to create its complete database. Populate representative Memory history, references, permissions, Candidates, tasks and cursors through that version's public interfaces; verify the data before shutdown. Do not create a minimal schema by hand. | +| `test_legacy_startup_is_rejected` | Target startup on the old database must fail without changing schema or row digests. | +| `test_repeated_migration` | A second plan/apply/verify returns unchanged and preserves every table row digest. | +| `test_public_migration_commands` | Stop writers, run the target artifact's public maintenance commands, and validate full readiness. SQLite additionally restores the actual automatic recovery point, reads it with the source Server, upgrades it and verifies target use. A limited bundle verification does not pass. | +| `test_target_startup` | Start the target Server against the migrated database and verify readiness. | +| `test_read_write_and_retrieval` | Read the old records, write new ones, and retrieve both through public interfaces. | +| `test_authorization` | Verify permitted and denied access and preservation of relevant grants. | +| `test_candidates_and_processing` | Verify retained Candidates and CAS approval; load and acknowledge the old pending intent using the target installed SDK, including idempotency. | +| `test_background_processing` | Run the installed target's actual Memory Worker against a local controlled inference endpoint; capture a new Source, verify the published Memory's exact Source reference and FTS retrieval, and require the child Worker's usage report. | +| `test_preserved_history_and_cursors` | Verify exact history/references and unchanged or declared-transformed cursor and receipt semantics. | +| `test_fresh_upgrade_equivalence` | Initialize a fresh target database; compare schema/backend objects and behavior with the upgraded database. The current suite has no retained/archive object exclusions. | + +Use a session fixture for the shared lifecycle; do not depend on pytest's test execution order. All collected tests +must execute and pass. Empty collection, missing phases, skips (including missing backends), xfail, failure, command +failure, and missing/malformed reports fail the gate. A real OceanBase suite must provision an isolated database and +fail if its required environment is unavailable; it cannot replace execution with mocks. Controlled model responses +may test domain behavior, but do not establish model quality. + +Write backend evidence containing `backend`, nonempty `engine_version`, `driver_version`, and recorded schema +inventories or their digests in `source_schema`, `target_schema`, and `fresh_schema`. OceanBase additionally requires +`tenant_mode: "mysql"` and `proxy_version` (or `direct`). Do not record credentials or business payloads. The controller +records installed Python/package versions, target wheel SHA-256, policy SHA-256, and the individual case result. + +Evidence is created during the gate run in a new directory. There is no option to import old successful reports. +Test names and metadata do not themselves prove correctness: reviewers must inspect the assertions and confirm +they follow actual installed operations and compare persisted data. The profile exercises explicit writes, FTS and +actual background Memory processing with controlled model responses. Both installed releases declare the Memory +capability and use the same processing ownership; the Worker check changes only the execution interval after migration. +The separate SDK fixture tests the old pending intent's format and acknowledgment. Controlled responses establish +execution and evidence preservation, not real-model quality. Vector storage uses its separately registered profiles; +passing a no-vector case cannot establish vector acceptance. +SQLite inventories include indexes, views and triggers; MySQL-family table DDL includes indexes and constraints, +and views are collected separately. MySQL comparison parses columns, indexes (including parser properties), constraints, and CHECK expressions, ignoring only physical formatting and backend-generated constraint names. Views and triggers are collected independently. SQLite normalizes whitespace and identifier quoting. Structural differences fail rather than being silently excluded. + +Regenerate installation support regions with +`uv run python scripts/upgrade_release_gate.py --write-docs` and run `make upgrade-policy-check`. When persistence +inputs change, calculate `managed_digest(ROOT)` from `upgrade_release_gate`, review the compatibility impact, and +update both the digest and rationale in `schema_review`. The inputs include persistence, optional code storage, +Server identity and authorization, and shared limits used in table definitions. Add new schema owners to +`MANAGED_INPUTS` when introducing them. The digest intentionally includes non-schema code: it forces a review of +changes, but does not infer semantic compatibility or replace real upgrade tests. + +Build one wheel and its matching `.tar.gz` source distribution into `dist/`, then run `make upgrade-release-test`. Before release, additionally run +`uv run python scripts/run_upgrade_release_gate.py --release --release-notes /path/to/notes.md`. The notes must contain +the exact English support declaration generated in the installation guide. The release workflow performs this check +against the GitHub event's notes and gates both PyPI publication and release assets on the same tested wheel. + +Repository administrators must require **Upgrade release gate** in branch protection. Workflow code cannot configure +that setting. Because the release workflow starts on `release.published`, it can block package/asset publication, +not the earlier manual creation of a GitHub Release; run the prepublication command before publishing that page. + +## Historical rejection regressions + +`scripts/ci_legacy_upgrade_rejection.py` remains a diagnostic tool for historical rejection behavior. PR and release +workflows do not invoke it as upgrade acceptance. An expected rejection or its `--respect-policy` result can never +replace a successful positive upgrade. The gate attempts every required backend even after an earlier case fails, +then fails overall and archives each case's logs and result. Empty matrices and missing suites are errors. + +## Publication artifacts + +The runner validates every distribution input. Publication requires exactly one wheel and one source archive with +matching names and metadata. It rebuilds the source archive with `uv build --wheel` and compares wheel member bytes, +excluding only the derived `RECORD` file. A same-version stale source archive fails just like an older-version one. +PRs may test a wheel alone, but the PR workflow builds both formats to exercise this validation too. + +Acceptance uses a private copy of those files. Only a passing run creates `validated-dist/` and `publication.json` +in its evidence directory. CI uploads that directory; local `make publish` calls the runner with `--release --publish` +and uploads its exact returned paths, never a fresh glob of `dist/`. Failed acceptance produces no publication set. +Managed persistence hashes normalize CRLF/LF checkout line endings; artifact SHA-256s remain byte-exact. + + +Installed-release execution requires Linux. Each gate command runs under a separate +Linux child subreaper, which owns detached Server and embedded-engine descendants. +On timeout it first interrupts the command so fixtures can close the Server and +seekDB normally, then terminates and reaps any remaining owned descendants before +collecting native logs, deleting the workspace, or starting the next case. Normal +success and failure also clean up leftover descendants. If the gate is terminated, +the owner receives a Linux parent-death signal and completes the same cleanup. +Policy and documentation checks remain available on other platforms. diff --git a/tests/release_upgrades/processing_fixture.py b/tests/release_upgrades/processing_fixture.py new file mode 100644 index 0000000000..87afd80ffd --- /dev/null +++ b/tests/release_upgrades/processing_fixture.py @@ -0,0 +1,60 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Exercise stored invocation format compatibility using each installed SDK. + +Run with the selected installed interpreter and -I, with Server processes stopped. +No schema creation or direct SQL writes: the installed repository owns its rows. +""" + +import asyncio +import json +import sys +from pathlib import Path + +from powercontext.builtin.persistence.oceanbase import OceanBaseProfile +from powercontext.builtin.persistence.processing_intents import ArtifactProcessingIntentRepository +from powercontext.builtin.persistence.seekdb import SeekDBProfile +from powercontext.builtin.persistence.sqlite import SQLiteProfile +from powercontext.server.settings import ServerSettings + + +async def main(): + operation, scope = sys.argv[1:3] + config = ServerSettings().database + if config.kind == "sqlite": + storage_context = SQLiteProfile.open(config, tables=()) + elif config.kind == "seekdb": + storage_context = SeekDBProfile.open(config, tables=()) + else: + storage_context = OceanBaseProfile.open(config, tables=()) + repository = ArtifactProcessingIntentRepository() + binding = "topic-memory-source-window" + async with storage_context as storage, storage.database.transaction() as connection: + if operation == "seed": + row = await repository.request(connection, scope, binding) + result = row.model_dump(mode="json") + else: + row = await repository.load(connection, scope, binding) + assert row is not None and row.requested_generation > row.handled_generation + before = row.model_dump(mode="json") + after = await repository.acknowledge(connection, scope, binding, row.requested_generation) + repeated = await repository.acknowledge(connection, scope, binding, row.requested_generation) + assert after == repeated, "Acknowledgment must be idempotent" + result = {"before": before, "after": after.model_dump(mode="json")} + return result + + +if __name__ == "__main__": + Path(sys.argv[3]).write_text(json.dumps(asyncio.run(main()), indent=2) + "\n", encoding="utf-8") diff --git a/tests/release_upgrades/test_full_server.py b/tests/release_upgrades/test_full_server.py new file mode 100644 index 0000000000..1b30626e30 --- /dev/null +++ b/tests/release_upgrades/test_full_server.py @@ -0,0 +1,1102 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Installed 1.3.0 -> built wheel acceptance; every required phase must succeed. + +No checkout runtime imports, migration fallbacks, skips or expected failures. +Fixtures establish dependencies rather than relying on test execution order. +""" + +from __future__ import annotations + +import hashlib +import json +import os +import re +import shlex +import shutil +import signal +import socket +import subprocess +import threading +import time +from contextlib import contextmanager +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from urllib.parse import urlsplit + +import httpx +import pytest + +ROOT = Path(__file__).resolve().parents[2] +TOKEN = "isolated-upgrade-acceptance-token" # noqa: S105 -- disposable fixture, never a deployment credential +AUTOMATIC_MEMORY = "Upgrade fixture background processing retains source evidence." + + +def objects(value): + if isinstance(value, dict): + yield value + for child in value.values(): + yield from objects(child) + elif isinstance(value, list): + for child in value: + yield from objects(child) + elif isinstance(value, str): + try: + decoded = json.loads(value) + except ValueError: + try: + decoded, _ = json.JSONDecoder().raw_decode(value[value.index("{") :]) + except ValueError: + return + if isinstance(decoded, (dict, list)): + yield from objects(decoded) + + +@pytest.fixture(scope="session") +def model_gateway(): + stages = [] + embeddings = [] + + class Handler(BaseHTTPRequestHandler): + def log_message(self, format, *args): # noqa: A002 -- stdlib signature + pass + + def do_POST(self): + body = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) + if self.path.endswith("/embeddings"): + model = re.fullmatch(r"upgrade-vector-([34])", body["model"]) + assert model is not None, "Unregistered controlled embedding model" + dimension = int(model[1]) + inputs = body["input"] + if isinstance(inputs, str): + inputs = [inputs] + embeddings.append(dimension) + response = json.dumps({ + "object": "list", + "model": body["model"], + "data": [ + {"object": "embedding", "index": index, "embedding": [1.0, *([0.0] * (dimension - 1))]} + for index in range(len(inputs)) + ], + "usage": {"prompt_tokens": len(inputs), "total_tokens": len(inputs)}, + }).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(response))) + self.end_headers() + self.wfile.write(response) + return + payload = next((obj for obj in objects(body["messages"]) if "evidence" in obj), None) + if payload is None: + value = {"ready": True} + elif "proposal" in payload: + stages.append("reconcile") + proposal = payload["proposal"] + duplicate = next((item for item in payload["related"] if item["text"] == proposal["text"]), None) + value = { + "action": "noop" if duplicate else "create", + "compared_ids": [item["item_id"] for item in payload["related"]], + "target_ids": [duplicate["item_id"]] if duplicate else [], + "content": None if duplicate else {"kind": proposal["kind"], "text": proposal["text"]}, + "evidence_ids": [] if duplicate else proposal["evidence_ids"], + "reason": "Controlled upgrade acceptance", + } + else: + stages.append("extract") + value = { + "candidates": [ + {"kind": "fact", "text": AUTOMATIC_MEMORY, "evidence_ids": [item["evidence_id"]]} + for item in payload["evidence"] + if AUTOMATIC_MEMORY in json.dumps(item["content"]) + ] + } + output = json.dumps(value) + tools = body.get("tools", []) + message = {"role": "assistant", "content": output} + if tools: + message = { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "upgrade-generation", + "type": "function", + "function": {"name": tools[0]["function"]["name"], "arguments": output}, + } + ], + } + response = json.dumps({ + "id": "upgrade-fixture", + "object": "chat.completion", + "created": 1, + "model": "upgrade-fixture", + "choices": [{"index": 0, "message": message, "finish_reason": "tool_calls" if tools else "stop"}], + "usage": {"prompt_tokens": 10, "completion_tokens": 10, "total_tokens": 20}, + }).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(response))) + self.end_headers() + self.wfile.write(response) + + gateway = ThreadingHTTPServer(("127.0.0.1", 0), Handler) + thread = threading.Thread(target=gateway.serve_forever, daemon=True) + thread.start() + try: + yield {"url": f"http://127.0.0.1:{gateway.server_port}/v1", "stages": stages, "embeddings": embeddings} + finally: + gateway.shutdown() + gateway.server_close() + thread.join(timeout=5) + + +def digest(value): + return hashlib.sha256(json.dumps(value, sort_keys=True).encode()).hexdigest() + + +def console(python): + return str(python.with_name("powercontext.exe" if os.name == "nt" else "powercontext")) + + +def command(arguments, environment, work, log): + with log.open("w", encoding="utf-8") as stream: + result = subprocess.run( + arguments, cwd=work, env=environment, stdout=stream, stderr=subprocess.STDOUT, timeout=1800, check=False + ) + assert result.returncode == 0, f"Command failed ({result.returncode}); see {log}" + return log.read_text(encoding="utf-8") + + +def post(client, path, body): + response = client.post(path, json=body) + assert response.is_success, f"{path}: HTTP {response.status_code}: {response.text}" + return response.json() + + +@contextmanager +def server(python, environment, work, log): + with socket.socket() as reservation: + reservation.bind(("127.0.0.1", 0)) + port = reservation.getsockname()[1] + with log.open("w", encoding="utf-8") as stream: + process = subprocess.Popen( + [console(python), "server", "run", "--no-env-file", "--host", "127.0.0.1", "--port", str(port)], + cwd=work, + env=environment, + stdout=stream, + stderr=subprocess.STDOUT, + start_new_session=os.name != "nt", + text=True, + ) + try: + with httpx.Client( + base_url=f"http://127.0.0.1:{port}", + headers={"Authorization": f"Bearer {TOKEN}"}, + trust_env=False, + timeout=30, + ) as client: + deadline = time.monotonic() + 180 + while True: + assert process.poll() is None, f"Server exited before readiness; see {log}" + try: + response = client.get("/health/ready") + if response.status_code == 200 and response.json().get("status") in {"ready", "degraded"}: + break + except httpx.TransportError: + pass + assert time.monotonic() < deadline, f"Server readiness timed out; see {log}" + time.sleep(0.2) + yield client + finally: + if process.poll() is None: + # Let the Server close SQL sessions and its embedded engine in + # order. Signalling the whole group also kills seekDB mid-close. + process.terminate() + try: + process.wait(timeout=30) + except subprocess.TimeoutExpired: + if os.name == "nt": + process.kill() + else: + os.killpg(process.pid, signal.SIGKILL) + process.wait(timeout=10) + + +def environment_for(case, *, fresh=False): + environment = { + key: value + for key, value in os.environ.items() + if not key.startswith(("POWERCONTEXT_", "PYTEST_", "OPENAI_", "ANTHROPIC_", "OTEL_")) + and key not in {"PYTHONPATH", "PYTHONHOME", "VIRTUAL_ENV"} + } + environment.update({ + "POWERCONTEXT_HOME": str(case["work"] / ("fresh-home" if fresh else "home")), + "POWERCONTEXT_SERVER_AUTH_TOKEN": TOKEN, + "POWERCONTEXT_SERVER_ACCESS_MODE": "enforced", + "POWERCONTEXT_SERVER_CURSOR_SIGNING_SECRET": "isolated-upgrade-cursor-signing-key-0001", + "POWERCONTEXT_SERVER_RUNTIME_ARTIFACT_PROCESSING_ROLE": "all", + "POWERCONTEXT_SERVER_RUNTIME_ARTIFACT_PROCESSING_FAMILIES": '["memory"]', + "POWERCONTEXT_SERVER_RUNTIME_SCHEDULE_SECONDS": "3600", + "POWERCONTEXT_SERVER_INFERENCE_GENERATION_MODEL": "openai-chat:upgrade-fixture", + "POWERCONTEXT_SERVER_INFERENCE_GENERATION_BASE_URL": case["model"]["url"], + "OPENAI_API_KEY": "isolated-upgrade-fixture", + "POWERCONTEXT_SERVER_MCP_ENABLED": "false", + "NO_PROXY": "localhost,127.0.0.1", + "no_proxy": "localhost,127.0.0.1", + }) + if case["vector_dimension"]: + dimension = case["vector_dimension"] + environment.update({ + "POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_MODEL": f"openai:upgrade-vector-{dimension}", + "POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_BASE_URL": case["model"]["url"], + "POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_PROFILE_ID": f"upgrade-vector-{dimension}-unit", + "POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_DIMENSION": str(dimension), + "POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_NORMALIZATION": "unit", + }) + if case["code_enabled"]: + scope = case.get("code_scope") + environment["POWERCONTEXT_SERVER_CODE"] = json.dumps({ + "enabled": True, + "cache_dir": str(case["work"] / ("fresh-code-cache" if fresh else "code-cache")), + "repositories": {} if fresh or scope is None else {scope: {"root": str(case["work"] / "repository")}}, + }) + database = case["work"] / ("fresh" if fresh else "database") + backend = case["backend"] + environment["POWERCONTEXT_SERVER_DATABASE_KIND"] = backend + if backend == "sqlite": + environment["POWERCONTEXT_SERVER_DATABASE_URL"] = f"sqlite+aiosqlite:///{database}" + elif backend == "seekdb": + environment["POWERCONTEXT_SERVER_DATABASE_PATH"] = str(database) + else: + name = "PC_UPGRADE_OCEANBASE_FRESH_URL" if fresh else "PC_UPGRADE_OCEANBASE_URL" + url = os.environ[name] + parsed = urlsplit(url) + expected = "/pc_upgrade_fresh" if fresh else "/pc_upgrade_test" + assert parsed.scheme == "mysql+aoceanbase" and parsed.hostname in {"127.0.0.1", "localhost"} + if case["configuration"] != "full-server-no-vector": + suffix = { + "full-server-code": "code", + "full-server-vector": "vector", + "full-server-vector-code": "vector_code", + }[case["configuration"]] + profile_path = f"/pc_upgrade_{suffix}_{'fresh' if fresh else 'test'}" + if parsed.path == expected: + parsed = parsed._replace(path=profile_path) + else: + assert re.fullmatch(re.escape(profile_path) + r"_[0-9a-f]{8}", parsed.path), ( + "Only dedicated disposable profile databases may be used" + ) + url = parsed.geturl() + else: + assert parsed.path == expected, "Only dedicated disposable OceanBase databases may be used" + environment["POWERCONTEXT_SERVER_DATABASE_URL"] = url + environment["PC_UPGRADE_OCEANBASE_URL"] = url + return environment + + +def snapshot(case, role, name, *, fresh=False): + path = case["report"] / f"{name}-schema.json" + command( + [ + str(case[role]), + "-I", + str(ROOT / "scripts/upgrade_database_probe.py"), + *(["--canonical"] if role == "target" else []), + "--backend", + case["backend"], + "--database", + str(case["work"] / ("fresh" if fresh else "database")), + "--output", + str(path), + ], + environment_for(case, fresh=fresh), + case["work"], + case["report"] / f"{name}-probe.log", + ) + return json.loads(path.read_text()) + + +def task_state(case, role, operation, scope): + path = case["report"] / f"task-{operation}.json" + command( + [ + str(case[role]), + "-I", + str(ROOT / "tests/release_upgrades/processing_fixture.py"), + operation, + scope, + str(path), + ], + environment_for(case), + case["work"], + case["report"] / f"task-{operation}.log", + ) + return json.loads(path.read_text()) + + +def seed(client, *, candidate_prefix): + scopes = [ + post( + client, + "/v1/scopes", + { + "title": f"Upgrade fixture {i}", + "summary": "Installed release acceptance", + "idempotency_key": f"upgrade-scope-{i}", + }, + ) + for i in range(3) + ] + scope = scopes[0]["scope_id"] + memory = [ + post(client, "/v1/memory/remember", {"scope_id": scope, "kind": "fact", "text": text})["records"][0] + for text in ("Upgrade fixture preserves historical storage.", "Upgrade fixture retains exact references.") + ] + captured = post( + client, + "/v1/sources/content", + { + "scope_id": scope, + "source_id": "upgrade-evidence", + "content": "Historical source evidence for upgrade candidate review.", + }, + )["source"] + proposal = { + "situation": "A release upgrades storage", + "action": "Run migration acceptance", + "outcome": "History survives", + "lesson": "Validate real old databases", + } + body = {"scope_id": scope, "proposal": proposal, "source_refs": [captured], "artifact_refs": []} + candidate = post(client, "/v1/experience/propose", body) + first = candidate + candidate = post( + client, + f"{candidate_prefix}/revise", + { + **body, + "candidate_id": candidate["candidate_id"], + "expected_version": candidate["version"], + "proposal": {**proposal, "lesson": "Validate every required backend"}, + }, + ) + published = post(client, "/v1/experience/propose", body) + published = post( + client, + f"{candidate_prefix}/approve", + {"scope_id": scope, "candidate_id": published["candidate_id"], "expected_version": published["version"]}, + ) + artifact = post(client, "/v1/experience/get", {"scope_id": scope, "artifact": published["result_artifact"]}) + principal = client.get("/v1/access/me").json()["principal"] + grant = { + "subject": principal, + "resource": {"type": "scope", "scope_id": scope}, + "role": "scope.viewer", + "idempotency_key": "upgrade-viewer", + } + binding = post(client, "/v1/access/bindings/create", grant) + first_page = client.get("/v1/scopes", params={"limit": 1}).json() + cursor = first_page["next_cursor"] + assert cursor + continuation = client.get("/v1/scopes", params={"limit": 1, "cursor": cursor}).json() + assert continuation["items"] + readback = post(client, "/v1/memory/entries/list", {"scope_id": scope})["entries"] + assert {digest(item) for item in readback} >= {digest(item) for item in memory} + return { + "scope": scope, + "memory": memory, + "candidate": candidate, + "first_candidate": first, + "published": published, + "artifact": artifact, + "grant": grant, + "binding": binding, + "cursor": cursor, + "continuation": continuation, + } + + +@pytest.fixture(scope="session") +def case(model_gateway): + # Missing gate inputs are errors, never skips or an implicit local database. + assert os.environ["PC_UPGRADE_SOURCE_VERSION"] == "1.3.0", "Add a reviewed adapter for another source release" + configuration = os.environ["PC_UPGRADE_CONFIGURATION"] + assert configuration in { + "full-server-no-vector", + "full-server-code", + "full-server-vector", + "full-server-vector-code", + }, "Unregistered acceptance configuration" + code_enabled = configuration.endswith("-code") + return { + "source": Path(os.environ["PC_UPGRADE_SOURCE_PYTHON"]), + "target": Path(os.environ["PC_UPGRADE_TARGET_PYTHON"]), + "backend": os.environ["PC_UPGRADE_BACKEND"], + "work": Path(os.environ["PC_UPGRADE_WORK"]), + "report": Path(os.environ["PC_UPGRADE_REPORT_DIR"]), + "model": model_gateway, + "configuration": configuration, + "code_enabled": code_enabled, + "vector_dimension": {"full-server-vector": 3, "full-server-vector-code": 4}.get(configuration, 0), + } + + +def code_cache_snapshot(case): + directory = case["work"] / "code-cache" + return { + path.relative_to(directory).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() + for path in sorted(directory.rglob("*")) + if path.is_file() and path.suffix in {".json", ".sqlite"} + } + + +def historical_code(case, scope): + repository = case["work"] / "repository" + repository.mkdir() + (repository / "evidence.py").write_text("def upgrade_code_evidence():\n return 2084\n", encoding="utf-8") + for arguments in (("init",), ("add", ".")): + command( + ["git", "-C", str(repository), *arguments], + environment_for(case), + case["work"], + case["report"] / f"code-git-{arguments[0]}.log", + ) + case["code_scope"] = scope + indexed = json.loads( + command( + [console(case["source"]), "code", "index", "--scope", scope], + environment_for(case), + case["work"], + case["report"] / "source-code-index.log", + ) + ) + with server( + case["source"], environment_for(case), case["work"], case["report"] / "source-code-server.log" + ) as client: + result = post( + client, + f"/v1/scopes/{scope}/code/query", + {"operation": {"kind": "symbols", "query": "upgrade_code_evidence"}}, + ) + assert result["status"] == "ok" and result["fingerprint"] == indexed["fingerprint"] + symbols = [item for item in result["items"] if item["name"] == "upgrade_code_evidence"] + assert symbols and all(item["path"] == "evidence.py" for item in symbols) + cache = code_cache_snapshot(case) + graph_file = "/graph.json" if case["backend"] == "seekdb" else "/graph.sqlite" + assert any(name.endswith(graph_file) for name in cache) + return {"fingerprint": result["fingerprint"], "symbols": symbols, "cache_sha256": cache} + + +@pytest.fixture(scope="session") +def historical(case): + if case["backend"] == "oceanbase": + assert not snapshot(case, "source", "empty")["tables"], "Refusing to write into a nonempty test database" + assert not snapshot(case, "target", "fresh-empty", fresh=True)["tables"] + with server(case["source"], environment_for(case), case["work"], case["report"] / "source-server.log") as client: + value = seed(client, candidate_prefix="/v1/artifact-candidates") + if case["vector_dimension"]: + value["topic_content"] = { + "title": "Upgrade topic evidence", + "summary": "Historical semantic retrieval", + "detail": "# Upgrade evidence\nHistorical Topic vectors must remain usable after maintenance.", + } + value["topic"] = post( + client, + f"/v1/scopes/{value['scope']}/artifacts", + { + "family": "topic-memory", + "content": value["topic_content"], + }, + ) + assert_vector_reads(client, value) + if case["code_enabled"]: + value["code"] = historical_code(case, value["scope"]) + value["task"] = task_state(case, "source", "seed", value["scope"]) + value["schema"] = snapshot(case, "source", "source") + assert {"pc_artifact_candidate_heads", "pc_artifact_candidate_versions", "pc_scopes", "pc_artifacts"} <= set( + value["schema"]["tables"] + ) + if case["vector_dimension"]: + assert set(value["schema"]["vectors"]) == { + "pc_atomic_memory_current", + "pc_topic_memory_vector_topics", + "pc_topic_memory_vector_chunks", + } + assert all(item["rows"] > 0 for item in value["schema"]["vectors"].values()) + (case["report"] / "source-fixture.json").write_text(json.dumps(value, indent=2) + "\n") + return value + + +@pytest.fixture(scope="session") +def rejected_legacy_startup(case, historical): + before = snapshot(case, "target", "before-rejected-startup") + log = case["report"] / "legacy-target-server.log" + with ( + pytest.raises(AssertionError, match="Server exited before readiness"), + server(case["target"], environment_for(case), case["work"], log), + ): + pytest.fail("The target Server admitted an unmigrated database") + assert "migration_required" in log.read_text() + after = snapshot(case, "target", "after-rejected-startup") + assert canonical_schema(before) == canonical_schema(after) + assert before["data_sha256"] == after["data_sha256"] + return True + + +@pytest.fixture(scope="session") +def migrated(case, historical, rejected_legacy_startup): + embedding_requests = len(case["model"]["embeddings"]) + environment = environment_for(case) + env_file = case["work"] / "deployment.env" + env_file.write_text( + "".join( + f"{key}={shlex.quote(value)}\n" for key, value in environment.items() if key.startswith("POWERCONTEXT_") + ) + ) + common = ["--env-file", str(env_file)] + if case["backend"] == "oceanbase": + common += ["--evidence-dir", str(case["report"] / "maintenance"), "--lock-coordination", "single-host"] + + def migration(operation, *options, label=""): + text = command( + [console(case["target"]), "server", "db-migrate", operation, *common, *options], + environment, + case["work"], + case["report"] / f"migration-{label}{operation}.log", + ) + result = json.loads(text) + assert not result.get("error"), result + return result + + migration("status") + backup_policy = "auto" if case["backend"] == "sqlite" else "skip" + plan = migration("plan", "--backup", backup_policy) + assert plan.get("plan_id"), "The public plan must identify exactly what will be applied" + # SQLite exercises its supported automatic recovery point. The other + # disposable backend fixtures explicitly accept no backup. + applied = migration( + "apply", + "--backup", + backup_policy, + "--plan-id", + plan["plan_id"], + "--yes", + "--maintenance-confirmed", + *(["--accept-no-backup"] if backup_policy == "skip" else []), + ) + verified = migration("verify") + assert verified.get("state") == "ready" and verified.get("server_ready") is True, ( + "Full Server readiness is required; registered-bundle verification is insufficient", + verified, + ) + before_repeat = snapshot(case, "target", "before-repeat") + repeat = migration("plan", "--backup", "skip", label="repeat-") + repeated = migration( + "apply", + "--backup", + "skip", + "--plan-id", + repeat["plan_id"], + "--yes", + "--maintenance-confirmed", + "--accept-no-backup", + label="repeat-", + ) + assert repeated["changed"] is False + assert migration("verify", label="repeat-")["server_ready"] is True + after_repeat = snapshot(case, "target", "after-repeat") + assert canonical_schema(before_repeat) == canonical_schema(after_repeat) + assert before_repeat["data_sha256"] == after_repeat["data_sha256"] + # Capture pending work before later behavioral tests legitimately dirty the + # same scope by publishing new Artifacts. Compare exact old counters here. + processing = task_state(case, "target", "verify", historical["scope"]) + result = { + "applied": applied, + "verified": verified, + "repeated": repeated, + "schema": snapshot(case, "target", "migrated"), + "historical": historical, + "processing": processing, + } + if case["vector_dimension"]: + assert result["schema"]["vectors"] == historical["schema"]["vectors"] + assert len(case["model"]["embeddings"]) == embedding_requests, "Maintenance must not re-embed history" + return result + + +@pytest.fixture(scope="session") +def restored_backup(case, migrated): + if case["backend"] != "sqlite": + return None + backup = Path(migrated["applied"].get("backup_ref") or "") + assert backup.is_file(), "SQLite installed-release acceptance requires an actual automatic backup" + before = hashlib.sha256(backup.read_bytes()).hexdigest() + restored = { + **case, + "work": case["work"] / "restored", + "report": case["report"] / "restored", + } + restored["work"].mkdir() + restored["report"].mkdir() + shutil.copyfile(backup, restored["work"] / "database") + historical = migrated["historical"] + if case["code_enabled"]: + # Code caches are separate from the database recovery point. The + # operator must preserve those immutable files as well. + for name in ("code-cache", "repository"): + shutil.copytree(case["work"] / name, restored["work"] / name) + assert code_cache_snapshot(restored) == historical["code"]["cache_sha256"] + source = snapshot(restored, "source", "restored-source") + assert canonical_schema(source) == canonical_schema(historical["schema"]) + assert source["data_sha256"] == historical["schema"]["data_sha256"] + with server( + restored["source"], environment_for(restored), restored["work"], restored["report"] / "source-server.log" + ) as client: + records = post(client, "/v1/memory/entries/list", {"scope_id": historical["scope"]})["entries"] + assert {digest(item) for item in records} == {digest(item) for item in historical["memory"]} + candidate = post( + client, + "/v1/artifact-candidates/get", + {"scope_id": historical["scope"], "candidate_id": historical["candidate"]["candidate_id"]}, + ) + assert candidate == historical["candidate"] + assert post(client, "/v1/access/bindings/create", historical["grant"]) == historical["binding"] + continuation = client.get("/v1/scopes", params={"limit": 1, "cursor": historical["cursor"]}) + assert continuation.status_code == 200 + assert continuation.json()["items"] == historical["continuation"]["items"] + if case["vector_dimension"]: + assert_vector_reads(client, historical) + environment = environment_for(restored) + + def migration(operation, *options): + return json.loads( + command( + [console(restored["target"]), "server", "db-migrate", operation, *options], + environment, + restored["work"], + restored["report"] / f"migration-{operation}.log", + ) + ) + + plan = migration("plan", "--backup", "auto") + assert plan["source_revision"] == "s1300" + applied = migration("apply", "--backup", "auto", "--plan-id", plan["plan_id"], "--yes", "--maintenance-confirmed") + assert applied["changed"] is True and applied["backup_state"] == "completed" + assert migration("verify")["server_ready"] is True + with server(restored["target"], environment, restored["work"], restored["report"] / "target-server.log") as client: + records = post(client, "/v1/memory/entries/list", {"scope_id": historical["scope"]})["entries"] + assert {digest(item) for item in records} == {digest(item) for item in historical["memory"]} + found = post( + client, "/v1/memory/search", {"scope_id": historical["scope"], "query": "Upgrade fixture", "mode": "fts"} + )["hits"] + assert {item["memory"]["text"] for item in found} >= {item["text"] for item in historical["memory"]} + candidate = post( + client, + "/v1/candidates/get", + {"scope_id": historical["scope"], "candidate_id": historical["candidate"]["candidate_id"]}, + ) + assert candidate["proposal"] == historical["candidate"]["proposal"] + assert candidate["version"] == historical["candidate"]["version"] + text = "A restored backup accepts writes after its registered upgrade." + written = post(client, "/v1/memory/remember", {"scope_id": historical["scope"], "kind": "fact", "text": text})[ + "records" + ] + assert written and written[0]["text"] == text + if case["vector_dimension"]: + assert_vector_reads(client, historical, new_text=text) + assert hashlib.sha256(backup.read_bytes()).hexdigest() == before + if case["code_enabled"]: + assert code_cache_snapshot(restored) == historical["code"]["cache_sha256"] + return { + "method": "sqlite_online", + "source_schema_and_rows_preserved": True, + "source_server_readback": True, + "target_upgrade_and_read_write": True, + "backup_sha256_unchanged": before, + "separate_code_cache_preserved": case["code_enabled"], + } + + +@pytest.fixture +def target(case, migrated, preserved_code, vector_admission): + with server(case["target"], environment_for(case), case["work"], case["report"] / "target-server.log") as client: + yield client + + +@pytest.fixture(scope="session") +def vector_admission(case, migrated): + if not case["vector_dimension"]: + return None + before = snapshot(case, "target", "before-profile-drift") + environment = environment_for(case) + environment["POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_PROFILE_ID"] = "different-embedding-space" + log = case["report"] / "profile-drift-server.log" + with ( + pytest.raises(AssertionError, match="Server exited before readiness"), + server(case["target"], environment, case["work"], log), + ): + pytest.fail("Managed storage admitted a different embedding profile") + assert "embedding_profile_mismatch" in log.read_text() + after = snapshot(case, "target", "after-profile-drift") + assert canonical_schema(before) == canonical_schema(after) + assert before["data_sha256"] == after["data_sha256"] + if case["backend"] == "sqlite": + clone = {**case, "work": case["work"] / "missing-helper", "report": case["report"] / "missing-helper"} + clone["work"].mkdir() + clone["report"].mkdir() + command( + [ + str(case["target"]), + "-I", + "-c", + "import sqlite3,sys\n" + "from contextlib import closing\n" + "with closing(sqlite3.connect(sys.argv[1])) as source, " + "closing(sqlite3.connect(sys.argv[2])) as target:\n" + " source.backup(target)\n" + " target.execute('DROP TRIGGER pc_atomic_memory_vec_insert')\n" + " target.commit()\n", + str(case["work"] / "database"), + str(clone["work"] / "database"), + ], + environment_for(case), + case["work"], + clone["report"] / "remove-helper.log", + ) + before = snapshot(clone, "target", "before-missing-helper-startup") + log = clone["report"] / "server.log" + with ( + pytest.raises(AssertionError, match="Server exited before readiness"), + server(clone["target"], environment_for(clone), clone["work"], log), + ): + pytest.fail("Managed startup repaired a missing vector helper") + assert "incompatible_schema" in log.read_text() + after = snapshot(clone, "target", "after-missing-helper-startup") + assert canonical_schema(before) == canonical_schema(after) + assert before["data_sha256"] == after["data_sha256"] + return {"profile_drift_rejected": True, "missing_sqlite_helper_rejected": case["backend"] == "sqlite"} + + +@pytest.fixture(scope="session") +def preserved_code(case, migrated): + if not case["code_enabled"]: + return None + historical = migrated["historical"] + source = historical["code"] + assert code_cache_snapshot(case) == source["cache_sha256"] + if case["backend"] == "seekdb": + names = ("pc_code_generations", "pc_code_nodes", "pc_code_edges") + before = historical["schema"]["data_sha256"] + after = migrated["schema"]["data_sha256"] + assert {name: before[name] for name in names} == {name: after[name] for name in names} + with server( + case["target"], environment_for(case), case["work"], case["report"] / "target-code-server.log" + ) as client: + result = post( + client, + f"/v1/scopes/{historical['scope']}/code/query", + {"operation": {"kind": "symbols", "query": "upgrade_code_evidence"}}, + ) + assert result["status"] == "ok" and result["fingerprint"] == source["fingerprint"] + symbols = [item for item in result["items"] if item["name"] == "upgrade_code_evidence"] + assert symbols == source["symbols"] + assert code_cache_snapshot(case) == source["cache_sha256"] + return { + "fingerprint": source["fingerprint"], + "immutable_cache_files_preserved": True, + "symbol_ids_preserved": True, + "server_query_ready": True, + } + + +@pytest.fixture(scope="session") +def processed_background(case, migrated): + historical = migrated["historical"] + environment = environment_for(case) + environment["POWERCONTEXT_SERVER_RUNTIME_SCHEDULE_SECONDS"] = "0.05" + source_id = "upgrade-background-worker" + stages = case["model"]["stages"] + start = len(stages) + with server(case["target"], environment, case["work"], case["report"] / "worker-server.log") as client: + post( + client, + "/v1/sources/content", + {"scope_id": historical["scope"], "source_id": source_id, "content": AUTOMATIC_MEMORY}, + ) + deadline = time.monotonic() + 90 + while True: + entries = post(client, "/v1/memory/entries/list", {"scope_id": historical["scope"]})["entries"] + generated = next((item for item in entries if item["text"] == AUTOMATIC_MEMORY), None) + metrics = client.get("/metrics") + metrics.raise_for_status() + report = re.search( + r'^powercontext_server_model_usage_worker_reports_total\{outcome="received"\} (\S+)', + metrics.text, + re.MULTILINE, + ) + if generated and report and float(report[1]) > 0: + break + assert time.monotonic() < deadline, "The installed target Worker did not publish and report completion" + time.sleep(0.2) + artifact = generated["artifact"] + revision = client.get( + f"/v1/scopes/{historical['scope']}/artifacts/{artifact['family']}/" + f"{artifact['artifact_id']}/revisions/{artifact['revision']}" + ) + revision.raise_for_status() + assert source_id in {item["source_id"] for item in revision.json()["sources"]} + assert {"extract", "reconcile"} <= set(stages[start:]) + found = post( + client, + "/v1/memory/search", + {"scope_id": historical["scope"], "query": "background processing", "mode": "fts"}, + )["hits"] + assert AUTOMATIC_MEMORY in {item["memory"]["text"] for item in found} + return { + "model_stages": sorted(set(stages[start:])), + "worker_report_received": True, + "source_evidence_verified": True, + } + + +def test_source_release_database(historical): + assert historical["memory"] and historical["candidate"]["version"] == 2 + assert historical["task"]["requested_generation"] > historical["task"]["handled_generation"] + + +def test_legacy_startup_is_rejected(rejected_legacy_startup): + assert rejected_legacy_startup + + +def test_repeated_migration(migrated): + assert migrated["repeated"]["changed"] is False + + +def test_public_migration_commands(migrated, restored_backup): + assert migrated["verified"]["server_ready"] is True + + +def test_target_startup(target): + response = target.get("/health/ready") + assert response.status_code == 200 + assert response.json()["status"] in {"ready", "degraded"} # No inference provider in this explicit-write profile. + + +def assert_vector_reads(client, historical, *, new_text=None): + scope = historical["scope"] + hits = post(client, "/v1/memory/search", {"scope_id": scope, "query": "Upgrade fixture", "mode": "vector"})["hits"] + expected = {item["text"] for item in historical["memory"]} + if new_text is not None: + expected.add(new_text) + assert {item["memory"]["text"] for item in hits} >= expected + topics = post( + client, + f"/v1/scopes/{scope}/artifacts/topic-memory/search", + {"query": "Upgrade evidence", "mode": "hybrid", "include_scores": True}, + )["results"] + topic = next(item for item in topics if item["artifact_id"] == historical["topic"]["artifact_id"]) + assert topic["content"] == historical["topic_content"] + assert topic["revision"] == historical["topic"]["revision"] + assert {"topic_vector", "detail_vector"} <= topic["scores"]["channels"].keys() + + +def test_read_write_and_retrieval(case, target, historical): + scope = historical["scope"] + records = post(target, "/v1/memory/entries/list", {"scope_id": scope})["entries"] + assert {digest(item) for item in records} >= {digest(item) for item in historical["memory"]} + text = "Upgrade fixture accepts new writes after migration." + written = post(target, "/v1/memory/remember", {"scope_id": scope, "kind": "fact", "text": text})["records"] + assert written and written[0]["text"] == text + found = post(target, "/v1/memory/search", {"scope_id": scope, "query": "Upgrade fixture", "mode": "fts"})["hits"] + texts = {item["memory"]["text"] for item in found} + assert texts >= {text, *(item["text"] for item in historical["memory"])} + if case["vector_dimension"]: + assert_vector_reads(target, historical, new_text=text) + + +def test_authorization(target, historical): + assert target.get("/v1/scopes", headers={"Authorization": "Bearer invalid-fixture-token"}).status_code == 401 + # The stable idempotency key must resolve to the SAME persisted grant. + assert post(target, "/v1/access/bindings/create", historical["grant"]) == historical["binding"] + response = target.get(f"/v1/scopes/{historical['scope']}") + assert response.status_code == 200 + + +def test_candidates_and_processing(case, migrated): + historical = migrated["historical"] + # Native typed SDK loading/acknowledgment verifies stored invocation formats + # and generation idempotency; model-driven Worker quality is a separate suite. + state = migrated["processing"] + assert {key: state["before"][key] for key in historical["task"]} == historical["task"] + assert state["after"]["handled_generation"] == historical["task"]["requested_generation"] + with server(case["target"], environment_for(case), case["work"], case["report"] / "candidate-server.log") as client: + candidate = historical["candidate"] + request = {"scope_id": historical["scope"], "candidate_id": candidate["candidate_id"]} + current = post(client, "/v1/candidates/get", request) + for key in ("candidate_id", "version", "status", "proposal", "source_refs", "artifact_refs"): + assert current[key] == candidate[key] + approved = post(client, "/v1/candidates/approve", {**request, "expected_version": candidate["version"]}) + assert approved["status"] == "approved" and approved["result_artifact"] + repeated = client.post("/v1/candidates/approve", json={**request, "expected_version": candidate["version"]}) + assert repeated.status_code == 200 and repeated.json() == approved, ( + "Approval retry must return the same publication" + ) + stale = client.post("/v1/candidates/approve", json={**request, "expected_version": candidate["version"] - 1}) + assert stale.status_code == 409, "A stale revision must not publish a duplicate Artifact" + + +def test_background_processing(processed_background): + assert processed_background["worker_report_received"] and processed_background["source_evidence_verified"] + + +def test_preserved_history_and_cursors(target, historical): + expected = historical["continuation"] + page = target.get("/v1/scopes", params={"limit": 1, "cursor": historical["cursor"]}) + assert page.status_code == 200 + assert page.json()["items"] == expected["items"] + assert bool(page.json()["next_cursor"]) == bool(expected["next_cursor"]) + if expected["next_cursor"]: + # New tokens have new expiry times; both old and newly issued tokens + # must continue from the same persisted position. + old_next = target.get("/v1/scopes", params={"limit": 1, "cursor": expected["next_cursor"]}) + new_next = target.get("/v1/scopes", params={"limit": 1, "cursor": page.json()["next_cursor"]}) + assert old_next.status_code == new_next.status_code == 200 + assert old_next.json()["items"] == new_next.json()["items"] + artifact = post( + target, + "/v1/experience/get", + {"scope_id": historical["scope"], "artifact": historical["published"]["result_artifact"]}, + ) + assert artifact == historical["artifact"] + history = post( + target, + "/v1/candidates/history", + {"scope_id": historical["scope"], "candidate_id": historical["candidate"]["candidate_id"]}, + )["versions"] + for version in (historical["first_candidate"], historical["candidate"]): + stored = next(item for item in history if item["version"] == version["version"]) + for key in ("version", "proposal", "source_refs", "artifact_refs"): + assert stored[key] == version[key] + + +def canonical_schema(snapshot): + if snapshot.get("logical_tables") is not None: + return { + "tables": snapshot["logical_tables"], + "views": snapshot.get("views", {}), + "triggers": snapshot.get("triggers", {}), + } + + def normalize(sql): + sql = re.sub(r"\bAUTO_INCREMENT=\d+", "", sql) + sql = re.sub(r'["`]([A-Za-z_][A-Za-z_0-9]*)["`]', r"\1", sql) + return " ".join(sql.split()) + + return { + kind: {name: normalize(sql or "") for name, sql in snapshot.get(kind, {}).items()} + for kind in ("tables", "indexes", "views", "triggers") + } + + +def test_fresh_upgrade_equivalence( + case, migrated, processed_background, preserved_code, restored_backup, vector_admission +): + if case["backend"] == "oceanbase": + environment = environment_for(case, fresh=True) + env_file = case["work"] / "fresh.env" + env_file.write_text( + "".join( + f"{key}={shlex.quote(value)}\n" for key, value in environment.items() if key.startswith("POWERCONTEXT_") + ) + ) + common = [ + "--env-file", + str(env_file), + "--evidence-dir", + str(case["report"] / "fresh-maintenance"), + "--lock-coordination", + "single-host", + ] + plan = json.loads( + command( + [console(case["target"]), "server", "db-migrate", "plan", *common, "--backup", "skip"], + environment, + case["work"], + case["report"] / "fresh-plan.log", + ) + ) + command( + [ + console(case["target"]), + "server", + "db-migrate", + "apply", + *common, + "--backup", + "skip", + "--plan-id", + plan["plan_id"], + "--yes", + "--maintenance-confirmed", + "--accept-no-backup", + ], + environment, + case["work"], + case["report"] / "fresh-apply.log", + ) + with server( + case["target"], environment_for(case, fresh=True), case["work"], case["report"] / "fresh-server.log" + ) as client: + fresh = seed(client, candidate_prefix="/v1/candidates") + hits = post( + client, "/v1/memory/search", {"scope_id": fresh["scope"], "query": "Upgrade fixture", "mode": "fts"} + )["hits"] + assert {item["memory"]["text"] for item in hits} >= {item["text"] for item in fresh["memory"]} + if case["vector_dimension"]: + hits = post( + client, "/v1/memory/search", {"scope_id": fresh["scope"], "query": "Upgrade fixture", "mode": "vector"} + )["hits"] + assert {item["memory"]["text"] for item in hits} >= {item["text"] for item in fresh["memory"]} + inventory = snapshot(case, "target", "fresh", fresh=True) + actual = canonical_schema(migrated["schema"]) + expected = canonical_schema(inventory) + assert actual == expected, ( + "Upgraded and fresh database objects differ; no blanket table/constraint exclusions allowed" + ) + evidence = { + "backup_restore": restored_backup, + "backend": case["backend"], + "engine_version": inventory["engine_version"], + "driver_version": inventory["driver_version"], + "source_schema": digest(migrated["historical"]["schema"]), + "target_schema": digest(actual), + "fresh_schema": digest(expected), + "background_processing": processed_background, + "configuration": case["configuration"], + "code": preserved_code, + "vectors": None + if not case["vector_dimension"] + else { + "dimension": case["vector_dimension"], + "historical_atomic_and_topic_embeddings_preserved": True, + "historical_and_new_vector_reads": True, + "maintenance_embedding_requests": 0, + "fresh_vector_read": True, + **vector_admission, + }, + } + if case["backend"] == "oceanbase": + evidence.update(tenant_mode=inventory["tenant_mode"], proxy_version=inventory["proxy_version"]) + Path(os.environ["PC_UPGRADE_EVIDENCE"]).write_text(json.dumps(evidence, indent=2) + "\n") diff --git a/tests/test_background_metrics.py b/tests/test_background_metrics.py index 133677d847..ec3cc4560b 100644 --- a/tests/test_background_metrics.py +++ b/tests/test_background_metrics.py @@ -25,6 +25,7 @@ from pydantic import SecretStr, ValidationError from powercontext.builtin.persistence.oceanbase import OceanBaseConfig +from powercontext.builtin.persistence.sqlite import SQLiteConfig from powercontext.builtin.runtime.config import RuntimeConfig from powercontext.server.authz import AccessAction from powercontext.server.authz.errors import AccessDeniedError, AccessUnavailableError @@ -166,6 +167,7 @@ def test_background_runner_closes_runtime_and_removes_signal_handlers(monkeypatc async def scenario(): _, settings = _app(enabled=outcome != "disabled") + settings = settings.model_copy(update={"database": SQLiteConfig()}) events = [] signals = [] loop = asyncio.get_running_loop() diff --git a/tests/test_bub_compose_startup.py b/tests/test_bub_compose_startup.py new file mode 100644 index 0000000000..58dc7cadde --- /dev/null +++ b/tests/test_bub_compose_startup.py @@ -0,0 +1,116 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Compose initialization must finish before a managed remote Server starts.""" + +import json +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +_ROOT = Path(__file__).resolve().parents[1] +pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="The Compose launcher requires a POSIX shell") + + +def _run(tmp_path, monkeypatch, *, vector=False, code=False, failed_action=""): + commands = tmp_path / "bin" + commands.mkdir() + events = tmp_path / "events.jsonl" + # Substitute only process boundaries: execute the real shell launcher and + # its inline maintenance program without Docker or a database service. + executable = ( + f"#!{sys.executable}\n" + + r""" +import json +import os +import subprocess +import sys +from pathlib import Path + +def record(action): + from powercontext.server.settings import ServerSettings + settings = ServerSettings() + with Path(os.environ["BUB_TEST_EVENTS"]).open("a") as output: + output.write(json.dumps({"action": action, + "vector": settings.inference.embedding_model is not None, + "code": settings.code.enabled}) + "\n") + +args = sys.argv[1:] +if Path(sys.argv[0]).name == "docker": + if "--entrypoint" in args: + raise SystemExit(subprocess.run([sys.executable, "-c", args[-1]]).returncode) + if "up" in args and args[-1] == "powercontext": + record("server-start") + elif "run" in args and "harness" in args: + record("harness") +else: + action = args[2] + record(action) + if action == os.environ.get("BUB_TEST_FAILED_ACTION"): + raise SystemExit(9) + if action == "apply": + assert args[args.index("--plan-id") + 1] == "reviewed-plan" + print(json.dumps({"plan_id": "reviewed-plan", "state": "ready", "server_ready": True})) +""" + ) + for name in ("docker", "powercontext"): + path = commands / name + path.write_text(executable, encoding="utf-8") + path.chmod(0o755) + monkeypatch.setenv("PATH", f"{commands}{os.pathsep}{os.defpath}") + monkeypatch.setenv("PYTHONPATH", str(_ROOT / "src")) + monkeypatch.setenv("POWERCONTEXT_VERSION", "1.4.1") + monkeypatch.setenv("GITHUB_SHA", "compose-regression") + monkeypatch.setenv("POWERCONTEXT_E2E_DATABASE", "oceanbase") + monkeypatch.setenv("POWERCONTEXT_E2E_OUTPUT", str(tmp_path / "output")) + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_KIND", "oceanbase") + monkeypatch.setenv( + "POWERCONTEXT_SERVER_DATABASE_URL", "mysql+aoceanbase://test@database.invalid:2881/test?charset=utf8mb4" + ) + monkeypatch.setenv("POWERCONTEXT_SERVER_CODE", json.dumps({"enabled": code})) + monkeypatch.setenv("BUB_TEST_EVENTS", str(events)) + monkeypatch.setenv("BUB_TEST_FAILED_ACTION", failed_action) + if vector: + monkeypatch.setenv("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_MODEL", "openai:test-embedding") + monkeypatch.setenv("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_PROFILE_ID", "test-embedding") + monkeypatch.setenv("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_DIMENSION", "3") + result = subprocess.run( + ["/bin/sh", str(_ROOT / "e2e/bub/run.sh"), "acceptance"], + capture_output=True, + text=True, + check=False, + timeout=60, + ) + return result, [json.loads(line) for line in events.read_text().splitlines()] if events.exists() else [] + + +@pytest.mark.parametrize(("vector", "code"), [(False, False), (True, False), (False, True), (True, True)]) +def test_oceanbase_compose_initializes_configured_profile_before_server(tmp_path, monkeypatch, vector, code): + result, events = _run(tmp_path, monkeypatch, vector=vector, code=code) + assert result.returncode == 0, result.stderr + assert [event["action"] for event in events] == ["plan", "apply", "verify", "server-start", "harness"] + assert all(event["vector"] == vector and event["code"] == code for event in events) + + +@pytest.mark.parametrize("failed_action", ["plan", "apply", "verify"]) +def test_oceanbase_compose_does_not_start_server_after_migration_failure(tmp_path, monkeypatch, failed_action): + result, events = _run(tmp_path, monkeypatch, vector=True, code=True, failed_action=failed_action) + assert result.returncode != 0 + actions = [event["action"] for event in events] + assert failed_action in actions + assert "server-start" not in actions + assert "harness" not in actions diff --git a/tests/test_cli.py b/tests/test_cli.py index 463db822bb..f6d0598529 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -670,7 +670,9 @@ def test_cli_reports_broken_command_providers(monkeypatch, name: str, value: str def test_server_identity_reset_requires_offline_confirmation_and_rotates(tmp_path) -> None: database = tmp_path / "runtime.db" environment = tmp_path / "server.env" - environment.write_text(f"POWERCONTEXT_SERVER_DATABASE_URL=sqlite+aiosqlite:///{database}\n") + environment.write_text( + f"POWERCONTEXT_SERVER_DATABASE_URL=sqlite+aiosqlite:///{database.as_posix()}\n", encoding="utf-8" + ) cli = create_cli([server_app]) refused = CliRunner().invoke(cli, ["server", "identity-reset", "--env-file", str(environment)]) diff --git a/tests/test_dashboard_profile_handoff.py b/tests/test_dashboard_profile_handoff.py index 4f9fb6a034..54e9fb8bfa 100644 --- a/tests/test_dashboard_profile_handoff.py +++ b/tests/test_dashboard_profile_handoff.py @@ -23,12 +23,17 @@ from urllib.parse import parse_qs, urlencode, urlsplit import pytest +from alembic import command from fastapi.testclient import TestClient from markdown_it import MarkdownIt +from sqlalchemy import create_engine from powercontext.builtin.artifacts.prompt import PROMPT_KEYS, PromptRegistry from powercontext.builtin.artifacts.prompt.builtin import builtin_prompt_definitions +from powercontext.builtin.persistence.migrations.deployment import deployment_runner, server_bundle from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.runtime.config import BuiltinConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest from powercontext.server.dashboard.api import DashboardAPI from powercontext.server.dashboard.pagination import PAGE_SIZE from powercontext.server.factory import create_server_app @@ -194,27 +199,60 @@ def test_rollback_is_offered_when_access_control_is_disabled(tmp_path: Path) -> def test_existing_database_without_revision_metadata_columns_keeps_working(tmp_path: Path) -> None: + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path}/existing.db"), + dashboard=DashboardConfig(enabled=True), + mcp=McpConfig(enabled=False), + ) + def open_app(): return create_server_app( - settings=ServerSettings( - database=SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path}/existing.db"), - dashboard=DashboardConfig(enabled=True), - mcp=McpConfig(enabled=False), - ), + settings=settings, scheduler_path=tmp_path / "scheduler.db", ) - with TestClient(open_app()) as client: - scope = create_scope(client, "Existing database")["scope_id"] - _profile(client, scope, "# First profile\n\nOriginal preference.") - # A database written before revision metadata existed has none of these columns. - with sqlite3.connect(tmp_path / "existing.db") as connection: - for column in ("created_at", "created_by", "restored_from_revision", "rollback_reason"): - connection.execute(f"ALTER TABLE pc_artifacts DROP COLUMN {column}") + # Use the registered release layout, then migrate before normal admission. + scope = "existing" + content = json.dumps({ + "schema": "powercontext.profile.v1", + "media_type": "text/markdown", + "content": "# First profile\n\nOriginal preference.\n", + "generation": {"mode": "manual_create", "created_at": "2026-01-01T00:00:00Z"}, + }).encode() + engine = create_engine(f"sqlite:///{tmp_path}/existing.db") + bundle = server_bundle() + try: + with engine.begin() as connection: + config = bundle.config(connection) + config.attributes["configuration"] = {"processing_manifest": canonical_processing_manifest(BuiltinConfig())} + command.upgrade(config, "s1300") + connection.exec_driver_sql("DROP TABLE pc_schema_revision") + connection.exec_driver_sql( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version) " + "VALUES (?, 'Existing database', '', ?, 'existing database', '', 1)", + (scope, scope), + ) + connection.exec_driver_sql( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content) VALUES (?, 'profile', 'profile', 1, ?)", + (scope, content), + ) + connection.exec_driver_sql( + "INSERT INTO pc_artifact_heads (scope_id,family,artifact_id,revision) VALUES (?, 'profile', 'profile', 1)", + (scope,), + ) + columns = {row[1] for row in connection.exec_driver_sql("PRAGMA table_info(pc_artifacts)")} + assert not columns & {"created_at", "created_by", "restored_from_revision", "rollback_reason"} + finally: + engine.dispose() + runner = deployment_runner(settings) + plan = runner.plan() + assert plan.source_revision == "s1300" + assert runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True).state == "ready" with TestClient(open_app()) as client: path = f"/v1/scopes/{scope}/artifacts/profile/profile" current = client.get(path) assert current.status_code == 200, current.text + assert current.json()["content"]["content"] == "# First profile\n\nOriginal preference.\n" assert current.json()["created_at"] is None changed = client.put( path, diff --git a/tests/test_database_migration_cli.py b/tests/test_database_migration_cli.py index 8a188d0f37..55b329578a 100644 --- a/tests/test_database_migration_cli.py +++ b/tests/test_database_migration_cli.py @@ -72,7 +72,7 @@ async def initialize() -> None: def _invoke(command: list[str], action: str, tmp_path: Path, *options: str, prompt_input: str | None = None): return CliRunner().invoke( create_cli([app]), - [*command, action, "--env-file", str(tmp_path / "deployment.env"), *options], + [*command, action, "--bundle", "artifacts", "--env-file", str(tmp_path / "deployment.env"), *options], input=prompt_input, ) @@ -161,6 +161,41 @@ def test_read_only_commands_never_create_missing_target(tmp_path, monkeypatch): assert not database.parent.exists() +@pytest.mark.parametrize( + "declaration", + [ + """POWERCONTEXT_SERVER_RUNTIME_ARTIFACT_PROCESSING_FAMILIES='["memory"]'""", + "POWERCONTEXT_SERVER_RUNTIME_ARTIFACT_PROCESSING_SUPERVISOR_MODE=dedicated", + ], +) +def test_processing_ownership_changes_invalidate_an_accepted_migration_plan(tmp_path, monkeypatch, declaration): + database, command = _configure(tmp_path, monkeypatch) + original = _invoke(command, "plan", tmp_path) + assert original.exit_code == 0, original.output + reviewed = json.loads(original.output) + environment = tmp_path / "deployment.env" + environment.write_text(environment.read_text(encoding="utf-8") + declaration + "\n", encoding="utf-8") + changed = _invoke(command, "plan", tmp_path) + assert changed.exit_code == 0, changed.output + current = json.loads(changed.output) + assert current["configuration_digest"] != reviewed["configuration_digest"] + assert current["plan_id"] != reviewed["plan_id"] + rejected = _invoke( + command, + "apply", + tmp_path, + "--backup", + "auto", + "--plan-id", + reviewed["plan_id"], + "--yes", + "--maintenance-confirmed", + ) + assert rejected.exit_code == 1, rejected.output + assert json.loads(rejected.output)["error"] == "plan_changed" + assert not database.parent.exists() + + def test_sqlite_url_authority_is_rejected_without_creating_a_local_target(tmp_path, monkeypatch): _database, command = _configure(tmp_path, monkeypatch) monkeypatch.chdir(tmp_path) diff --git a/tests/test_managed_atomic_maintenance.py b/tests/test_managed_atomic_maintenance.py new file mode 100644 index 0000000000..d7e87d6270 --- /dev/null +++ b/tests/test_managed_atomic_maintenance.py @@ -0,0 +1,141 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Atomic compatibility commands preserve managed Server migration ownership.""" + +import json +import sqlite3 +from contextlib import closing + +import pytest +from alembic import command +from sqlalchemy import create_engine +from typer.testing import CliRunner + +from powercontext.builtin.persistence.migrations.deployment import deployment_runner, server_bundle +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.runtime.config import BuiltinConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest +from powercontext.cli.app import create_cli +from powercontext.server.cli import app +from powercontext.server.settings import ServerSettings + + +@pytest.fixture +def historical(tmp_path, monkeypatch): + path = tmp_path / "historical.db" + bundle = server_bundle() + engine = create_engine(f"sqlite:///{path}") + with engine.connect() as connection: + connection.exec_driver_sql("BEGIN IMMEDIATE") + config = bundle.config(connection) + config.attributes["configuration"] = {"processing_manifest": canonical_processing_manifest(BuiltinConfig())} + command.upgrade(config, "s1300") + connection.exec_driver_sql("DROP TABLE pc_schema_revision") + connection.exec_driver_sql( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version) " + "VALUES ('scope','title','summary','scope','title','summary',1)" + ) + connection.exec_driver_sql( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content) " + "VALUES ('scope','custom-family','historical',1,?)", + (b'{"history":"unchanged"}',), + ) + connection.commit() + engine.dispose() + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_KIND", "sqlite") + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_URL", f"sqlite+aiosqlite:///{path}") + return path + + +def upgraded(path): + runner = deployment_runner(ServerSettings(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{path}"))) + plan = runner.plan() + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + + +def test_atomic_alias_upgrades_complete_historical_server_with_shared_plan_and_backup(historical): + cli = create_cli([app]) + arguments = ["server", "atomic-memory-migrate"] + original = historical.read_bytes() + with closing(sqlite3.connect(historical)) as connection: + original_dump = tuple(connection.iterdump()) + + inspected = CliRunner().invoke(cli, [*arguments, "--action", "plan"]) + assert inspected.exit_code == 0, inspected.output + plan = json.loads(inspected.output) + assert plan["source_revision"] == "s1300" + assert plan["target_revision"] == "s1402" + assert plan["readiness_scope"] == "server" + assert historical.read_bytes() == original + + denied = CliRunner().invoke(cli, [*arguments, "--action", "apply", "--maintenance-confirmed"]) + assert denied.exit_code != 0 + assert historical.read_bytes() == original + accepted = CliRunner().invoke( + cli, + [ + *arguments, + "--action", + "apply", + "--maintenance-confirmed", + "--plan-id", + plan["plan_id"], + "--backup", + "auto", + "--yes", + ], + ) + assert accepted.exit_code == 0, accepted.output + result = json.loads(accepted.output) + assert result["readiness_scope"] == "server" and result["backup_state"] == "completed" + with closing(sqlite3.connect(result["backup_ref"])) as connection: + assert tuple(connection.iterdump()) == original_dump + with closing(sqlite3.connect(historical)) as connection: + assert connection.execute("SELECT version_num FROM pc_schema_revision").fetchall() == [("s1402",)] + assert connection.execute("SELECT content FROM pc_artifacts").fetchall() == [(b'{"history":"unchanged"}',)] + verified = CliRunner().invoke(cli, [*arguments, "--action", "verify"]) + assert verified.exit_code == 0, verified.output + assert json.loads(verified.output)["state"] == "ready" + after = historical.read_bytes() + ready_plan = CliRunner().invoke(cli, [*arguments, "--action", "plan"]) + assert ready_plan.exit_code == 0, ready_plan.output + assert json.loads(ready_plan.output)["state"] == "ready" + assert historical.read_bytes() == after + repeated = CliRunner().invoke(cli, [*arguments, "--action", "apply", "--maintenance-confirmed"]) + assert repeated.exit_code == 0, repeated.output + assert json.loads(repeated.output)["changed"] is False + assert historical.read_bytes() == after + + +def test_atomic_alias_rejects_unregistered_owner_decisions_on_managed_server(historical): + upgraded(historical) + decisions = historical.with_suffix(".json") + decisions.write_text('{"format":"powercontext.atomic-memory-owner-decisions.v1","decisions":[]}', encoding="utf-8") + original = historical.read_bytes() + result = CliRunner().invoke( + create_cli([app]), + [ + "server", + "atomic-memory-migrate", + "--action", + "apply", + "--maintenance-confirmed", + "--owner-decisions", + str(decisions), + ], + ) + assert result.exit_code != 0 + assert "decision files" in result.output + assert historical.read_bytes() == original diff --git a/tests/test_managed_atomic_projection.py b/tests/test_managed_atomic_projection.py new file mode 100644 index 0000000000..547eda8cbf --- /dev/null +++ b/tests/test_managed_atomic_projection.py @@ -0,0 +1,243 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Managed projection repair must preserve authority and shared maintenance ownership.""" + +import asyncio +import json +import os +import sqlite3 +import subprocess +import sys +from contextlib import closing +from pathlib import Path + +import pytest +from fastapi.testclient import TestClient +from typer.testing import CliRunner + +from powercontext.builtin.artifacts.atomic_memory.models import AtomicMemoryContent +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.builtin.persistence.migrations.locking import local_migration_lock +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.runtime import BuiltinConfig, open_builtin_contexts +from powercontext.cli.app import create_cli +from powercontext.server.cli import app +from powercontext.server.factory import create_server_app +from powercontext.server.settings import ServerSettings + + +@pytest.fixture +def damaged(tmp_path, monkeypatch): + path = tmp_path / "managed.db" + settings = ServerSettings(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{path}")) + runner = deployment_runner(settings) + plan = runner.plan() + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + with closing(sqlite3.connect(path)) as connection, connection: + connection.execute( + "INSERT INTO pc_scopes (scope_id,title,summary,scope_id_search,title_search,summary_search,version) " + "VALUES ('scope','title','summary','scope','title','summary',1)" + ) + connection.execute( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content) " + "VALUES ('scope','atomic-memory','fact',1,?)", + ( + AtomicMemoryContent(kind="fact", text="Preserve immutable nebula evidence.") + .model_dump_json(by_alias=True) + .encode(), + ), + ) + connection.execute( + "INSERT INTO pc_artifact_heads (scope_id,family,artifact_id,revision) " + "VALUES ('scope','atomic-memory','fact',1)" + ) + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_KIND", "sqlite") + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_URL", f"sqlite+aiosqlite:///{path}") + return path, settings + + +def authority(path): + with closing(sqlite3.connect(path)) as connection: + return tuple( + (table, tuple(connection.execute(f"SELECT * FROM {table} ORDER BY 1,2,3").fetchall())) # noqa: S608 -- Fixed fixture tables. + for table in ("pc_artifacts", "pc_artifact_heads", "pc_artifact_tags", "pc_access_owners") + ) + + +@pytest.mark.parametrize("damage", ["missing_current", "helper_only", "helper_identity"]) +def test_managed_repair_uses_shared_plan_backup_and_lock_without_changing_authority(damaged, damage): + path, settings = damaged + cli = create_cli([app]) + arguments = ["server", "atomic-memory-rebuild-projection"] + if damage != "missing_current": + initial = CliRunner().invoke(cli, [*arguments, "--action", "plan"]) + assert initial.exit_code == 0, initial.output + first_plan = json.loads(initial.output) + repaired = CliRunner().invoke( + cli, + [ + *arguments, + "--maintenance-confirmed", + "--plan-id", + first_plan["plan_id"], + "--backup", + "auto", + "--yes", + ], + ) + assert repaired.exit_code == 0, repaired.output + with closing(sqlite3.connect(path)) as connection, connection: + if damage == "helper_only": + connection.execute("DELETE FROM pc_atomic_memory_current_fts") + else: + connection.execute("UPDATE pc_atomic_memory_current_fts SET identity_token='broken'") + original_authority = authority(path) + original = path.read_bytes() + for operation in ("status", "plan", "verify"): + rejected = CliRunner().invoke(cli, ["server", "db-migrate", operation]) + assert rejected.exit_code != 0, rejected.output + assert json.loads(rejected.output)["error"] == "projection_verification_failed" + + async def reject_runtime(): + with pytest.raises(MigrationError, match="projection_verification_failed"): + async with open_builtin_contexts(BuiltinConfig(database=settings.database)): + pytest.fail("Incomplete projections must block runtime admission") + + asyncio.run(reject_runtime()) + server = create_server_app(settings=settings, scheduler_path=path.parent / "scheduler.db") + with pytest.raises(MigrationError, match="projection_verification_failed"), TestClient(server): + pytest.fail("Incomplete projections must block Server startup") + assert path.read_bytes() == original + inspected = CliRunner().invoke(cli, [*arguments, "--action", "plan"]) + assert inspected.exit_code == 0, inspected.output + plan = json.loads(inspected.output) + assert plan["state"] == "migration_required" and plan["readiness_scope"] == "server" + assert path.read_bytes() == original + denied = CliRunner().invoke(cli, [*arguments, "--maintenance-confirmed"]) + assert denied.exit_code != 0 and path.read_bytes() == original + accepted = [ + *arguments, + "--maintenance-confirmed", + "--plan-id", + plan["plan_id"], + "--backup", + "auto", + "--yes", + ] + with local_migration_lock(path): + locked = CliRunner().invoke(cli, accepted) + assert locked.exit_code != 0 and "migration_locked" in locked.output + assert path.read_bytes() == original + repaired = CliRunner().invoke(cli, accepted) + assert repaired.exit_code == 0, repaired.output + result = json.loads(repaired.output) + assert result["backup_state"] == "completed" and Path(result["backup_ref"]).is_file() + assert authority(Path(result["backup_ref"])) == original_authority == authority(path) + assert deployment_runner(settings).verify().state == "ready" + verified = CliRunner().invoke(cli, [*arguments, "--action", "verify"]) + assert verified.exit_code == 0, verified.output + after = path.read_bytes() + repeated = CliRunner().invoke(cli, [*arguments, "--maintenance-confirmed"]) + assert repeated.exit_code == 0, repeated.output + assert json.loads(repeated.output)["changed"] is False and path.read_bytes() == after + + async def search(): + async with open_builtin_contexts(BuiltinConfig(database=settings.database)) as contexts: + result = await contexts.atomic_memory.for_scope("scope").search("nebula", mode="text") + assert [hit.hit.artifact_ref.artifact_id for hit in result.hits] == ["fact"] + + asyncio.run(search()) + + +def test_managed_repair_rejects_broken_authority_before_backup_or_writes(damaged): + path, _settings = damaged + with closing(sqlite3.connect(path)) as connection, connection: + connection.execute("DELETE FROM pc_artifact_heads WHERE artifact_id='fact'") + original = path.read_bytes() + result = CliRunner().invoke(create_cli([app]), ["server", "atomic-memory-rebuild-projection", "--action", "plan"]) + assert result.exit_code != 0 and path.read_bytes() == original + assert json.loads(result.output)["error"] == "invalid_authority" + assert not list(path.parent.glob("*.pc-migration-state/atomic_projection/*/maintenance.json")) + + +def test_projection_repair_rejects_a_processing_configuration_switch(damaged, monkeypatch): + path, _settings = damaged + original = path.read_bytes() + monkeypatch.setenv("POWERCONTEXT_SERVER_RUNTIME_ARTIFACT_PROCESSING_SUPERVISOR_MODE", "dedicated") + result = CliRunner().invoke(create_cli([app]), ["server", "atomic-memory-rebuild-projection", "--action", "plan"]) + assert result.exit_code != 0, result.output + assert json.loads(result.output)["error"] == "incompatible_processing_configuration" + assert path.read_bytes() == original + + +def test_process_exit_during_projection_repair_rolls_back_and_reuses_original_backup(damaged): + path, settings = damaged + runner = deployment_runner(settings, bundle="atomic-projection", batch_size=1) + plan = runner.plan() + original_authority = authority(path) + environment = {key: value for key, value in os.environ.items() if not key.startswith("POWERCONTEXT_SERVER_")} + environment.update( + POWERCONTEXT_SERVER_DATABASE_KIND="sqlite", + POWERCONTEXT_SERVER_DATABASE_URL=f"sqlite+aiosqlite:///{path}", + ) + interrupted = subprocess.run( + [ + sys.executable, + "-c", + """ +import os +from sqlalchemy import Engine, event +from powercontext.cli.app import main +@event.listens_for(Engine, "after_cursor_execute") +def interrupt(connection, cursor, statement, parameters, context, executemany): + if statement.startswith("INSERT INTO pc_atomic_memory_current "): + os._exit(73) +main() +""", + "server", + "db-migrate", + "apply", + "--bundle", + "atomic-projection", + "--batch-size", + "1", + "--plan-id", + plan.plan_id, + "--backup", + "auto", + "--yes", + "--maintenance-confirmed", + ], + env=environment, + capture_output=True, + text=True, + timeout=30, + ) + assert interrupted.returncode == 73, interrupted.stdout + interrupted.stderr + journal = next(path.parent.glob("*.pc-migration-state/atomic_projection/*/maintenance.json")) + original = json.loads(journal.read_text()) + assert original["state"] == "active" + with closing(sqlite3.connect(path)) as connection: + assert connection.execute("SELECT COUNT(*) FROM pc_atomic_memory_current").fetchone() == (0,) + recovery = runner.plan() + assert recovery.state == "recovery_required" + result = runner.apply(plan_id=recovery.plan_id, accepted=True, maintenance_confirmed=True) + finished = json.loads(journal.read_text()) + assert finished["state"] == "complete" + assert finished["maintenance_window_id"] == original["maintenance_window_id"] + assert result.backup_ref == original["backup"]["location"] + assert authority(path) == original_authority + assert deployment_runner(settings).verify().state == "ready" diff --git a/tests/test_managed_processing_maintenance.py b/tests/test_managed_processing_maintenance.py new file mode 100644 index 0000000000..0b3b88488f --- /dev/null +++ b/tests/test_managed_processing_maintenance.py @@ -0,0 +1,275 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Offline configuration changes must share managed database maintenance.""" + +import json +import os +import sqlite3 +import subprocess +import sys +from contextlib import closing +from pathlib import Path + +import pytest +from alembic import command +from sqlalchemy import create_engine +from typer.testing import CliRunner + +from powercontext.builtin.persistence.migrations.deployment import deployment_runner, server_bundle +from powercontext.builtin.persistence.migrations.locking import local_migration_lock +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.runtime.config import BuiltinConfig, RuntimeConfig +from powercontext.builtin.runtime.processing_registry import canonical_processing_manifest +from powercontext.cli.app import create_cli +from powercontext.server.cli import app +from powercontext.server.settings import ServerSettings + + +@pytest.fixture +def managed(tmp_path): + path = tmp_path / "managed.db" + settings = ServerSettings(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{path}")) + runner = deployment_runner(settings) + plan = runner.plan() + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + with closing(sqlite3.connect(path)) as connection, connection: + connection.execute( + "INSERT INTO pc_artifact_processing_leases " + "(supervisor_group,holder_id,supervisor_generation,lease_expires_at) VALUES ('global','old-worker',7,NULL)" + ) + return path, settings + + +def switched(settings): + return settings.model_copy(update={"runtime": RuntimeConfig(artifact_processing_supervisor_mode="dedicated")}) + + +def test_registered_mode_switch_backs_up_preserves_revision_and_fences_old_worker(managed): + path, settings = managed + runner = deployment_runner(switched(settings), bundle="processing") + plan = runner.plan() + assert plan.state == "migration_required" + with closing(sqlite3.connect(path)) as connection: + assert ( + json.loads(connection.execute("SELECT config_manifest FROM pc_artifact_processing_schema").fetchone()[0])[ + "mode" + ] + == "global" + ) + + result = runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + + assert result.backup_state == "completed" + assert result.backup_ref and Path(result.backup_ref).is_file() + with closing(sqlite3.connect(path)) as connection: + assert connection.execute("SELECT version_num FROM pc_schema_revision").fetchall() == [("s1402",)] + assert ( + json.loads(connection.execute("SELECT config_manifest FROM pc_artifact_processing_schema").fetchone()[0])[ + "mode" + ] + == "dedicated" + ) + assert connection.execute("SELECT supervisor_generation FROM pc_artifact_processing_leases").fetchone()[0] > 7 + after = tuple(connection.iterdump()) + with closing(sqlite3.connect(result.backup_ref)) as connection: + assert ( + json.loads(connection.execute("SELECT config_manifest FROM pc_artifact_processing_schema").fetchone()[0])[ + "mode" + ] + == "global" + ) + assert deployment_runner(switched(settings)).verify().state == "ready" + assert not runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True).changed + with closing(sqlite3.connect(path)) as connection: + assert tuple(connection.iterdump()) == after + + +def test_mode_switch_uses_the_whole_database_migration_lock(managed): + path, settings = managed + runner = deployment_runner(switched(settings), bundle="processing") + plan = runner.plan() + with local_migration_lock(path), pytest.raises(MigrationError, match="migration_locked"): + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + assert deployment_runner(settings).verify().state == "ready" + + +def test_reused_mode_switch_id_is_rejected_before_creating_new_recovery_evidence(managed): + path, settings = managed + first = deployment_runner(switched(settings), bundle="processing", migration_id="first-switch") + plan = first.plan() + first.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + journal = next(path.parent.glob("*.pc-migration-state/processing_configuration/*/maintenance.json")) + completed = journal.read_bytes() + reused = deployment_runner(settings, bundle="processing", migration_id="first-switch") + with pytest.raises(MigrationError, match="migration_id_reused"): + reused.plan() + assert journal.read_bytes() == completed + reverse = deployment_runner(settings, bundle="processing", migration_id="second-switch") + plan = reverse.plan() + reverse.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + assert deployment_runner(settings).verify().state == "ready" + + +def test_process_exit_during_mode_switch_rolls_back_and_reuses_original_backup(managed): + path, settings = managed + runner = deployment_runner(switched(settings), bundle="processing") + plan = runner.plan() + environment = {key: value for key, value in os.environ.items() if not key.startswith("POWERCONTEXT_SERVER_")} + environment.update( + POWERCONTEXT_SERVER_DATABASE_KIND="sqlite", + POWERCONTEXT_SERVER_DATABASE_URL=f"sqlite+aiosqlite:///{path}", + POWERCONTEXT_SERVER_RUNTIME_ARTIFACT_PROCESSING_SUPERVISOR_MODE="dedicated", + ) + interrupted = subprocess.run( + [ + sys.executable, + "-c", + """ +import os +from sqlalchemy import event, Engine +from powercontext.cli.app import main +@event.listens_for(Engine, "after_cursor_execute") +def interrupt(connection, cursor, statement, parameters, context, executemany): + if statement.startswith("UPDATE pc_artifact_processing_leases "): + os._exit(73) +main() +""", + "server", + "db-migrate", + "apply", + "--bundle", + "processing", + "--plan-id", + plan.plan_id, + "--backup", + "auto", + "--yes", + "--maintenance-confirmed", + ], + env=environment, + capture_output=True, + text=True, + timeout=30, + ) + assert interrupted.returncode == 73, interrupted.stdout + interrupted.stderr + journal = next(path.parent.glob("*.pc-migration-state/processing_configuration/*/maintenance.json")) + original = json.loads(journal.read_text()) + assert original["state"] == "active" + with closing(sqlite3.connect(path)) as connection: + assert connection.execute("SELECT supervisor_generation FROM pc_artifact_processing_leases").fetchone() == (7,) + assert ( + json.loads(connection.execute("SELECT config_manifest FROM pc_artifact_processing_schema").fetchone()[0])[ + "mode" + ] + == "global" + ) + recovery = runner.plan() + assert recovery.state == "recovery_required" + result = runner.apply(plan_id=recovery.plan_id, accepted=True, maintenance_confirmed=True) + finished = json.loads(journal.read_text()) + assert finished["state"] == "complete" + assert finished["maintenance_window_id"] == original["maintenance_window_id"] + assert result.backup_ref == original["backup"]["location"] + assert deployment_runner(switched(settings)).verify().state == "ready" + + +def test_processing_alias_delegates_with_reviewed_plan_and_preserves_operator_migration_id(managed, monkeypatch): + path, _settings = managed + for key in tuple(os.environ): + if key.startswith("POWERCONTEXT_SERVER_"): + monkeypatch.delenv(key) + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_KIND", "sqlite") + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_URL", f"sqlite+aiosqlite:///{path}") + monkeypatch.setenv("POWERCONTEXT_SERVER_RUNTIME_ARTIFACT_PROCESSING_SUPERVISOR_MODE", "dedicated") + command = ["server", "processing-migrate", "--migration-id", "operator-switch"] + cli = create_cli([app]) + result = CliRunner().invoke(cli, [*command, "--action", "plan"]) + assert result.exit_code == 0, result.output + plan = json.loads(result.output) + assert plan["domain_steps"] == ["processing_configuration"] + denied = CliRunner().invoke(cli, [*command, "--action", "apply", "--maintenance-confirmed"]) + assert denied.exit_code != 0 + applied = CliRunner().invoke( + cli, + [ + *command, + "--action", + "apply", + "--maintenance-confirmed", + "--plan-id", + plan["plan_id"], + "--backup", + "auto", + "--yes", + ], + ) + assert applied.exit_code == 0, applied.output + with closing(sqlite3.connect(path)) as connection: + assert connection.execute("SELECT migration_id FROM pc_artifact_processing_schema").fetchone() == ( + "operator-switch", + ) + + +def test_processing_alias_does_not_modify_an_unversioned_complete_historical_server(tmp_path, monkeypatch): + path = tmp_path / "historical.db" + bundle = server_bundle() + engine = create_engine(f"sqlite:///{path}") + with engine.connect() as connection: + connection.exec_driver_sql("BEGIN IMMEDIATE") + config = bundle.config(connection) + config.attributes["configuration"] = {"processing_manifest": canonical_processing_manifest(BuiltinConfig())} + command.upgrade(config, "s1300") + connection.exec_driver_sql("DROP TABLE pc_schema_revision") + connection.commit() + engine.dispose() + original = path.read_bytes() + for key in tuple(os.environ): + if key.startswith("POWERCONTEXT_SERVER_"): + monkeypatch.delenv(key) + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_KIND", "sqlite") + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_URL", f"sqlite+aiosqlite:///{path}") + result = CliRunner().invoke( + create_cli([app]), + [ + "server", + "processing-migrate", + "--action", + "apply", + "--maintenance-confirmed", + "--migration-id", + "wrong-owner", + ], + ) + assert result.exit_code == 1, result.output + assert json.loads(result.output)["error"] == "migration_required" + assert path.read_bytes() == original + + +def test_processing_alias_inspection_and_execution_use_the_same_discovered_environment(managed, monkeypatch): + path, _settings = managed + monkeypatch.chdir(path.parent) + (path.parent / ".env").write_text( + f'POWERCONTEXT_SERVER_DATABASE_KIND=sqlite\nPOWERCONTEXT_SERVER_DATABASE_URL="sqlite+aiosqlite:///{path.as_posix()}"\n' + "POWERCONTEXT_SERVER_RUNTIME_ARTIFACT_PROCESSING_SUPERVISOR_MODE=dedicated\n", + encoding="utf-8", + ) + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_KIND", "sqlite") + other = path.parent / "other.db" + monkeypatch.setenv("POWERCONTEXT_SERVER_DATABASE_URL", f"sqlite+aiosqlite:///{other}") + result = CliRunner().invoke(create_cli([app]), ["server", "processing-migrate", "--action", "plan"]) + assert result.exit_code == 0, result.output + assert json.loads(result.output)["domain_steps"] == ["processing_configuration"] + assert not other.exists() diff --git a/tests/test_managed_startup_concurrency.py b/tests/test_managed_startup_concurrency.py new file mode 100644 index 0000000000..0fb3b8bc4c --- /dev/null +++ b/tests/test_managed_startup_concurrency.py @@ -0,0 +1,73 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Managed startup validation must allow concurrent SQLite WAL writes.""" + +import asyncio + +import pytest + +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.persistence.topic_memory import TopicMemoryRepository +from powercontext.builtin.runtime import BuiltinConfig, composition, open_builtin_contexts +from powercontext.builtin.scope import ScopeDraft +from powercontext.server.settings import ServerSettings + + +@pytest.mark.parametrize("topic_worker", [False, True]) +def test_managed_runtime_startup_allows_an_existing_runtime_to_commit(tmp_path, monkeypatch, topic_worker): + database = SQLiteConfig( + url=f"sqlite+aiosqlite:///{tmp_path / 'managed.db'}", busy_timeout_ms=250, journal_mode="WAL" + ) + runner = deployment_runner(ServerSettings(database=database)) + plan = runner.plan() + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + + async def scenario(): + validating = asyncio.Event() + resume = asyncio.Event() + config = BuiltinConfig(database=database) + + class PausedTopicValidation(TopicMemoryRepository): + async def initialize(self, connection, /, **options): + await super().initialize(connection, **options) + validating.set() + await resume.wait() + + async def open_runtime(): + async with open_builtin_contexts(config, _topic_memory_worker=topic_worker): + pass + + async with open_builtin_contexts(config) as running: + monkeypatch.setattr(composition, "TopicMemoryRepository", PausedTopicValidation) + opening = asyncio.create_task(open_runtime()) + try: + await asyncio.wait_for(validating.wait(), timeout=10) + created = await asyncio.wait_for( + running.scopes.create( + ScopeDraft( + title="Concurrent capture", + summary="Committed while another runtime validates its managed storage.", + idempotency_key="concurrent-startup", + ) + ), + timeout=2, + ) + assert created.title == "Concurrent capture" + finally: + resume.set() + await opening + + asyncio.run(scenario()) diff --git a/tests/test_metrics_merge.py b/tests/test_metrics_merge.py index 66a5a37c69..2ae8ce7122 100644 --- a/tests/test_metrics_merge.py +++ b/tests/test_metrics_merge.py @@ -27,6 +27,7 @@ from prometheus_client.parser import text_string_to_metric_families from pydantic import SecretStr +from powercontext.builtin.persistence.sqlite import SQLiteConfig from powercontext.builtin.runtime import BuiltinRuntime from powercontext.builtin.runtime.observations import DurationObservations from powercontext.builtin.runtime.processing_contracts import ArtifactProcessingModelUsageDelta @@ -185,7 +186,9 @@ async def serve(app, settings, stopped): monkeypatch.setattr("powercontext.server.cli.open_builtin_runtime", open_runtime) monkeypatch.setattr("powercontext.server.cli.serve_background_metrics", serve) - await _run_background_async(ServerSettings(), ServerTracing.context_only(role="background")) + await _run_background_async( + ServerSettings(database=SQLiteConfig()), ServerTracing.context_only(role="background") + ) assert len(rendered) == 2 for samples in rendered: assert _value(samples, "service_role_info", role="background") == 1 diff --git a/tests/test_processing_security.py b/tests/test_processing_security.py index b3aeb57eb8..8033a80bf2 100644 --- a/tests/test_processing_security.py +++ b/tests/test_processing_security.py @@ -22,6 +22,7 @@ import pytest from pydantic import SecretStr +from powercontext.builtin.persistence.migrations.deployment import deployment_runner from powercontext.builtin.persistence.sqlite import SQLiteConfig, SQLiteProfile from powercontext.builtin.runtime.composition import BuiltinConfigurationError from powercontext.builtin.runtime.config import InferenceConfig @@ -81,28 +82,33 @@ async def scenario(): asyncio.run(scenario()) -def test_server_rejects_injected_authorization_before_registering_actual_background_workers(tmp_path): +@pytest.mark.parametrize("generation_model", ["test", None]) +def test_server_rejects_injected_authorization_before_registering_actual_background_workers(tmp_path, generation_model): async def scenario(): config = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'server.db'}") settings = ServerSettings( database=config, auth=BearerAuthConfig(token=SecretStr("test-token")), access=AccessControlConfig(mode="enforced", background_principal_id="worker"), - inference=InferenceConfig(generation_model="test"), + inference=InferenceConfig(generation_model=generation_model), ) + runner = deployment_runner(settings) + plan = await asyncio.to_thread(runner.plan) + result = await asyncio.to_thread(runner.apply, plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + assert result.state == "ready" async with SQLiteProfile.open(config, tables=ACCESS_TABLES) as profile: repository = RelationalAccessRepository(profile.database) access = AccessControlService( BuiltinAuthorizationProvider(repository), relationships=repository, audit=repository ) app = create_server_app(settings=settings, access_control=access) - with pytest.raises(BuiltinConfigurationError, match="reconstructible Authorization Provider"): + if generation_model is not None: + with pytest.raises(BuiltinConfigurationError, match="reconstructible Authorization Provider"): + async with app.router.lifespan_context(app): + pass + else: async with app.router.lifespan_context(app): - pass - settings = settings.model_copy(update={"inference": InferenceConfig()}) - app = create_server_app(settings=settings, access_control=access) - async with app.router.lifespan_context(app): - assert app.state.application.artifact_processing_supervisor is None + assert app.state.application.artifact_processing_supervisor is None asyncio.run(scenario()) @@ -110,9 +116,10 @@ async def scenario(): def test_background_cli_preserves_deployment_identity_when_building_the_worker_spec(monkeypatch): async def scenario(): settings = ServerSettings( + database=SQLiteConfig(), access=AccessControlConfig( mode="enforced", background_principal_id="background-worker", deployment_id="test" - ) + ), ) observed = {} stop_callbacks = [] diff --git a/tests/test_release_version.py b/tests/test_release_version.py index 9629fd7c07..b7542c699e 100644 --- a/tests/test_release_version.py +++ b/tests/test_release_version.py @@ -49,6 +49,7 @@ def release_repo(tmp_path: Path) -> Path: source = Path(__file__).parents[1] for relative in ( "scripts/release_version.py", + "release/upgrade-support.json", "openapi/powercontext.yaml", *INSTALL_PAGES, *HISTORICAL_AND_INDEPENDENT_FILES, @@ -98,13 +99,11 @@ def test_release_updates_installation_references_and_preserves_history(release_r english = (release_repo / "docs/en/docs/get-started/install-and-run.md").read_text(encoding="utf-8") chinese = (release_repo / "docs/zh/docs/get-started/install-and-run.md").read_text(encoding="utf-8") assert f"package `{version}`" in english - assert f"To upgrade to {version}:" in english + assert f"To install {version} in a separate environment" in english assert f"Python 包版本为 `{version}`" in chinese - assert f"升级到 {version}" in chinese - assert "Version 1.1.0 upgrades legacy" in english - assert "1.0.0 also need" in english - assert "1.1.0 会在 Server 启动时升级旧标签表约束" in chinese - assert "对于 1.0.0 之前的数据库" in chinese + assert f"安装 {version} 到独立环境" in chinese + assert "Upgrades from 1.3.0 require passing full Server acceptance" in english + assert "1.3.0 升级必须通过 SQLite、seekDB 和 OceanBase 的完整 Server 验收" in chinese dsh = (release_repo / "integrations/dsh/plugins/powercontext/README.md").read_text(encoding="utf-8") assert "Release 1.1.0 includes direct-operation Scope failure handling" in dsh for relative, original in preserved.items(): diff --git a/tests/test_server_identity.py b/tests/test_server_identity.py index 845b27a3b1..b225d781f0 100644 --- a/tests/test_server_identity.py +++ b/tests/test_server_identity.py @@ -24,12 +24,34 @@ from sqlalchemy.exc import OperationalError from sqlalchemy.schema import CreateTable +from powercontext.builtin.persistence.migrations.deployment import deployment_runner from powercontext.builtin.persistence.sqlite import SQLiteConfig, SQLiteProfile from powercontext.server.identity import ( SERVER_IDENTITY_TABLE, ServerIdentityRepository, open_server_identity_repository, ) +from powercontext.server.settings import ServerSettings + + +def test_managed_server_identity_initialization_requires_no_ddl(tmp_path) -> None: + config = SQLiteConfig(url=f"sqlite+aiosqlite:///{tmp_path / 'managed.db'}") + runner = deployment_runner(ServerSettings(database=config)) + plan = runner.plan() + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + + async def scenario() -> None: + async with SQLiteProfile.open_readonly(config) as profile: + + @event.listens_for(profile.database.engine.sync_engine, "before_cursor_execute") + def reject_ddl(_connection, _cursor, statement, _parameters, _context, _executemany): + assert not statement.lstrip().upper().startswith(("CREATE ", "ALTER ", "DROP ")), ( + "Managed identity initialization must not issue DDL" + ) + + await ServerIdentityRepository(profile.database).initialize() + + asyncio.run(scenario()) def test_server_identity_singleton_key_is_not_auto_incremented_by_mysql_profiles() -> None: diff --git a/tests/test_service_maintenance.py b/tests/test_service_maintenance.py index 5ebe4dcb85..6800af0cc3 100644 --- a/tests/test_service_maintenance.py +++ b/tests/test_service_maintenance.py @@ -23,9 +23,17 @@ from pathlib import Path import pytest +from alembic import command +from sqlalchemy import create_engine from typer.testing import CliRunner import powercontext.service.cli as service_cli +from powercontext.builtin.persistence.migrations.deployment import deployment_runner, server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.cli.app import create_cli +from powercontext.server import database_migration +from powercontext.server.cli import app as server_app +from powercontext.server.configuration import server_settings_context from powercontext.service.adapters.base import service_python_executable from powercontext.service.adapters.systemd import SystemdUserAdapter from powercontext.service.adapters.windows import WindowsTaskSchedulerAdapter @@ -41,7 +49,183 @@ ServiceError, ) from powercontext.service.probe import probe_readiness -from tests.test_service import FakeAdapter, _definition, _LivenessHandler, _manager_probe, _serve +from tests.test_service import FakeAdapter, _definition, _LivenessHandler, _manager_probe, _secure_windows_file, _serve + + +@pytest.fixture +def managed_upgrade(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + for name in tuple(os.environ): + if name == "POWERCONTEXT_HOME" or name.startswith("POWERCONTEXT_SERVER_"): + monkeypatch.delenv(name) + database = tmp_path / "server.db" + environment = tmp_path / "deployment.env" + environment.write_text( + f"POWERCONTEXT_HOME={tmp_path.as_posix()}\n" + "POWERCONTEXT_SERVER_DATABASE_KIND=sqlite\n" + f"POWERCONTEXT_SERVER_DATABASE_URL=sqlite+aiosqlite:///{database.as_posix()}\n", + encoding="utf-8", + ) + environment.chmod(0o600) + _secure_windows_file(environment) + with server_settings_context(env_file=environment) as settings: + runner = deployment_runner(settings) + bundle = server_bundle() + engine = create_engine(f"sqlite:///{database}") + try: + with engine.begin() as connection: + config = bundle.config(connection) + config.attributes["configuration"] = runner.configuration + command.upgrade(config, "s1300") + connection.exec_driver_sql("DROP TABLE pc_schema_revision") + finally: + engine.dispose() + controller, adapter = _installed(tmp_path) + assert adapter.definition is not None + from powercontext.service.environment import load_protected_environment_file + + adapter.definition = replace( + adapter.definition, + data_dir=str(tmp_path), + env_file=load_protected_environment_file(environment).identity, + ) + adapter.content = adapter.render(adapter.definition) + adapter.loaded_definition = adapter.definition + monkeypatch.setattr(database_migration, "ServiceController", lambda: controller) + cli = create_cli([server_app]) + + def invoke(action: str, *options: str): + return CliRunner().invoke( + cli, + ["server", "db-migrate", action, "--env-file", str(environment), "--manage-service", *options], + ) + + def apply(plan): + return invoke( + "apply", + "--backup", + "auto", + "--plan-id", + plan["plan_id"], + "--yes", + "--maintenance-confirmed", + ) + + return controller, adapter, runner, environment, invoke, apply + + +@pytest.mark.parametrize("running", [True, False]) +def test_full_server_migration_switches_service_and_preserves_original_running_state(managed_upgrade, running): + controller, adapter, runner, _environment, invoke, apply = managed_upgrade + if not running: + controller.stop() + original = adapter.definition + planned = invoke("plan") + assert planned.exit_code == 0, planned.output + plan = json.loads(planned.output) + assert plan["service"]["originally_running"] is running + assert adapter.definition == original + assert runner.plan().state == "migration_required" + + migrated = apply(plan) + + assert migrated.exit_code == 0, migrated.output + assert json.loads(migrated.output)["server_ready"] is True + assert runner.verify().state == "ready" + assert adapter.definition is not None + assert adapter.definition.python_executable == service_python_executable() + assert adapter.manager is (ManagerState.ACTIVE if running else ManagerState.INACTIVE) + assert adapter.suspended is not running + + +def test_failed_full_server_migration_keeps_service_stopped_and_can_resume(managed_upgrade, monkeypatch): + controller, adapter, runner, _environment, invoke, apply = managed_upgrade + original = adapter.definition + planned = invoke("plan") + assert planned.exit_code == 0, planned.output + plan = json.loads(planned.output) + from powercontext.builtin.persistence.migrations.domains import DomainRegistry + + original_invoke = DomainRegistry._invoke + + def interrupted(registry, step, operation, connection, configuration): + original_invoke(registry, step, operation, connection, configuration) + if step.id == "server_upgrade" and operation == "verify": + raise MigrationError("injected_failure", "Interrupted domain verification") + + with monkeypatch.context() as fault: + fault.setattr(DomainRegistry, "_invoke", interrupted) + failed = apply(plan) + assert failed.exit_code != 0 + assert json.loads(failed.output)["error"] == "injected_failure" + assert adapter.definition == original + assert adapter.manager is ManagerState.INACTIVE + assert adapter.suspended + with pytest.raises(ServiceError, match="not been verified"): + controller.start() + retry = invoke("plan") + assert retry.exit_code == 0, retry.output + resumed = apply(json.loads(retry.output)) + assert resumed.exit_code == 0, resumed.output + assert runner.verify().state == "ready" + assert adapter.manager is ManagerState.ACTIVE + + +def test_service_start_failure_reports_ready_database_and_retries_only_service(managed_upgrade, monkeypatch): + controller, adapter, runner, _environment, invoke, apply = managed_upgrade + planned = invoke("plan") + assert planned.exit_code == 0, planned.output + with monkeypatch.context() as fault: + fault.setattr(controller, "_readiness_probe", lambda _: ProbeResult(ProbeState.CONFLICT, "startup failed")) + failed = apply(json.loads(planned.output)) + assert failed.exit_code != 0 + result = json.loads(failed.output) + assert result["error"] == "service_start_failed" + assert result["database_state"] == "ready" + assert adapter.suspended + assert adapter.manager is ManagerState.INACTIVE + before = runner.database.read_bytes() + assert controller.start().ok + assert runner.database.read_bytes() == before + + +def test_database_ready_before_service_switch_can_complete_managed_retry(managed_upgrade): + controller, adapter, runner, environment, invoke, apply = managed_upgrade + summary = controller.maintenance_summary(env_file=environment) + with controller.maintenance(expected_fingerprint=summary.fingerprint, env_file=environment): + plan = runner.plan() + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + original = adapter.definition + before = runner.database.read_bytes() + planned = invoke("plan") + assert planned.exit_code == 0, planned.output + completed = apply(json.loads(planned.output)) + assert completed.exit_code == 0, completed.output + assert adapter.definition != original + assert adapter.manager is ManagerState.ACTIVE + assert not controller.maintenance_path.exists() + assert runner.database.read_bytes() == before + + +def test_managed_migration_rejects_a_different_database_before_stopping_service(managed_upgrade, tmp_path): + controller, adapter, runner, _environment, _invoke, _apply = managed_upgrade + different = tmp_path / "different.env" + different.write_text( + "POWERCONTEXT_SERVER_DATABASE_KIND=sqlite\n" + f"POWERCONTEXT_SERVER_DATABASE_URL=sqlite+aiosqlite:///{(tmp_path / 'other.db').as_posix()}\n", + encoding="utf-8", + ) + different.chmod(0o600) + _secure_windows_file(different) + result = CliRunner().invoke( + create_cli([server_app]), + ["server", "db-migrate", "plan", "--env-file", str(different), "--manage-service"], + ) + assert result.exit_code != 0 + assert adapter.manager is ManagerState.ACTIVE + assert not adapter.suspended + assert not controller.maintenance_path.exists() + assert not (tmp_path / "other.db").exists() + assert runner.plan().state == "migration_required" def _installed(tmp_path: Path, *, running: bool = True) -> tuple[ServiceController, FakeAdapter]: diff --git a/tests/test_upgrade_publication.py b/tests/test_upgrade_publication.py new file mode 100644 index 0000000000..3065054d84 --- /dev/null +++ b/tests/test_upgrade_publication.py @@ -0,0 +1,182 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Exercise publication inputs with actual offline sdist builds; intercept uploads.""" + +import io +import json +import sys +import tarfile +import zipfile +from pathlib import Path + +import pytest +import run_upgrade_release_gate as runner +from upgrade_release_gate import GateError, sha256 + + +def write_wheel(path, content=b"current implementation", *, version="2.0.0"): + with zipfile.ZipFile(path, "w") as archive: + archive.writestr("powercontext/__init__.py", content) + archive.writestr(f"powercontext-{version}.dist-info/METADATA", f"Name: powercontext\nVersion: {version}\n") + archive.writestr(f"powercontext-{version}.dist-info/WHEEL", "Wheel-Version: 1.0\nTag: py3-none-any\n") + archive.writestr(f"powercontext-{version}.dist-info/RECORD", "") + + +def write_source(path, content=b"current implementation", *, version="2.0.0", metadata_version=None): + # No external build dependencies or network. uv invokes a real local PEP 517 + # backend so stale same-version source changes alter the rebuilt wheel. + backend = f"""import pathlib, zipfile + +def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): + name = "powercontext-{version}-py3-none-any.whl" + with zipfile.ZipFile(pathlib.Path(wheel_directory) / name, "w") as archive: + archive.writestr("powercontext/__init__.py", pathlib.Path("payload.py").read_bytes()) + archive.writestr("powercontext-{version}.dist-info/METADATA", "Name: powercontext\\nVersion: {version}\\n") + archive.writestr("powercontext-{version}.dist-info/WHEEL", "Wheel-Version: 1.0\\nTag: py3-none-any\\n") + archive.writestr("powercontext-{version}.dist-info/RECORD", "") + return name +""" + files = { + "PKG-INFO": f"Name: powercontext\nVersion: {metadata_version or version}\n".encode(), + "pyproject.toml": b'[build-system]\nrequires = []\nbuild-backend = "fixture_backend"\nbackend-path = ["."]\n', + "fixture_backend.py": backend.encode(), + "payload.py": content, + } + with tarfile.open(path, "w:gz") as archive: + for name, data in files.items(): + member = tarfile.TarInfo(f"powercontext-{version}/{name}") + member.size = len(data) + archive.addfile(member, io.BytesIO(data)) + + +@pytest.fixture +def publication(tmp_path, monkeypatch): + monkeypatch.setenv("UV_OFFLINE", "true") + dist = tmp_path / "dist" + dist.mkdir() + wheel = dist / "powercontext-2.0.0-py3-none-any.whl" + source = dist / "powercontext-2.0.0.tar.gz" + write_wheel(wheel) + write_source(source) + (dist / ".gitignore").write_text("*") + return dist, wheel, source, tmp_path / "evidence" + + +@pytest.mark.parametrize( + "invalid", ["old-version", "metadata-mismatch", "same-version-stale-content", "extra-file", "missing-sdist"] +) +def test_invalid_publication_set_never_reaches_acceptance(publication, monkeypatch, invalid): + dist, _wheel, source, output = publication + if invalid == "old-version": + source.unlink() + write_source(dist / "powercontext-1.3.0.tar.gz", version="1.3.0") + elif invalid == "metadata-mismatch": + write_source(source, metadata_version="1.3.0") + elif invalid == "same-version-stale-content": + write_source(source, b"old implementation") + elif invalid == "extra-file": + (dist / "leftover.zip").write_bytes(b"unchecked archive") + else: + source.unlink() + + def unexpected_gate(*_args, **_kwargs): + pytest.fail("Invalid publication inputs must fail before upgrade acceptance") + + monkeypatch.setattr(runner, "gate", unexpected_gate) + with pytest.raises(GateError): + runner.accept_distributions(dist, output, release=True, release_notes="fixture") + assert not (output / "validated-dist").exists() + + +def test_failed_upgrade_cannot_produce_publishable_files(publication, monkeypatch): + dist, _, _, output = publication + + def fail(*_args, **_kwargs): + raise GateError("migration_required") + + monkeypatch.setattr(runner, "gate", fail) + with pytest.raises(GateError, match="migration_required"): + runner.accept_distributions(dist, output, release=True, release_notes="fixture") + assert not (output / "validated-dist").exists() + + +def test_publish_uses_only_tested_snapshot_even_if_dist_changes(publication, monkeypatch): + dist, wheel, source, output = publication + expected = {path.name: sha256(path) for path in (wheel, source)} + notes = dist.parent / "notes.md" + notes.write_text("fixture release notes") + + def accept(_root, tested_wheel, evidence, **_kwargs): + assert sha256(tested_wheel) == expected[wheel.name] + # A concurrent build must not replace bytes being tested or introduce an + # unchecked package into the later upload glob. + write_wheel(wheel, b"replacement after validation") + write_source(source, b"replacement after validation") + write_source(dist / "powercontext-1.3.0.tar.gz", version="1.3.0") + evidence.mkdir() + (evidence / "result.json").write_text( + json.dumps({ + "mode": "supported-upgrades", + "resolved_matrix": {"candidate_cases": [1, 2, 3]}, + "cases": [1, 2, 3], + }) + ) + + monkeypatch.setattr(runner, "gate", accept) + original_run = runner.subprocess.run + uploads = [] + + def intercept_upload(command, **kwargs): + if command[:2] == ["uv", "publish"]: + uploads.extend(Path(name) for name in command[2:]) + return None + return original_run(command, **kwargs) + + monkeypatch.setattr(runner.subprocess, "run", intercept_upload) + monkeypatch.setattr( + sys, + "argv", + [ + "run_upgrade_release_gate.py", + "--dist", + str(dist), + "--output", + str(output), + "--release", + "--publish", + "--release-notes", + str(notes), + ], + ) + runner.main() + assert {path.name: sha256(path) for path in uploads} == expected + assert all(path.parent == output / "validated-dist" for path in uploads) + assert json.loads((output / "publication.json").read_text())["artifacts"] == expected + + +def test_wheel_only_pr_build_still_runs_upgrade_acceptance(publication, monkeypatch): + dist, wheel, source, output = publication + source.unlink() + tested = [] + + def accept(_root, tested_wheel, evidence, **kwargs): + tested.append(sha256(tested_wheel)) + assert not kwargs["release"] + evidence.mkdir() + + monkeypatch.setattr(runner, "gate", accept) + accepted = runner.accept_distributions(dist, output, release=False, release_notes=None) + assert tested == [sha256(wheel)] + assert [path.name for path in accepted] == [wheel.name] diff --git a/tests/test_upgrade_release_gate.py b/tests/test_upgrade_release_gate.py new file mode 100644 index 0000000000..7064a19ddc --- /dev/null +++ b/tests/test_upgrade_release_gate.py @@ -0,0 +1,702 @@ +# Copyright (c) 2026 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Release-blocking regressions, not evidence that any database upgrade works.""" + +import json +import os +import shutil +import signal +import subprocess +import sys +import time +import zipfile +from pathlib import Path +from xml.etree import ElementTree + +import pytest +import upgrade_release_gate as gate +import upgrade_release_matrix as matrix +import yaml +from ci_legacy_upgrade_rejection import database_environment + +from powercontext.server.settings import ServerSettings + + +@pytest.fixture +def release(tmp_path, monkeypatch): + persistence = tmp_path / "src/powercontext/builtin/persistence/tables.py" + persistence.parent.mkdir(parents=True) + persistence.write_text("# disposable persistence input\n") + contract = tmp_path / "openapi/powercontext.yaml" + contract.parent.mkdir() + contract.write_text("info:\n version: 2.0.0\n") + (tmp_path / "release").mkdir() + policy = { + "format": 2, + "target_version": "2.0.0", + "mode": "upgrade-required", + "schema_review": {"persistence_sha256": gate.managed_digest(tmp_path), "rationale": "No upgrades supported"}, + "cases": [], + "automatic_sources": None, + } + suite = tmp_path / "tests/release_upgrades/test_example.py" + suite.parent.mkdir(parents=True) + suite.write_text("# Gate orchestration fixture; not real upgrade acceptance.\n") + automatic_policy(policy) + monkeypatch.setattr(gate, "run_case", lambda _root, _wheel, case, _output: {"case": case, "status": "passed"}) + (tmp_path / gate.MANIFEST).write_text(json.dumps(policy)) + monkeypatch.setattr( + matrix, + "fetch_catalog", + lambda: { + "releases": { + "1.3.0": [ + { + "filename": "powercontext-1.3.0-py3-none-any.whl", + "packagetype": "bdist_wheel", + "url": "https://files.pythonhosted.org/packages/test.whl", + "digests": {"sha256": "a" * 64}, + } + ] + } + }, + ) + for language in ("en", "zh"): + page = tmp_path / f"docs/{language}/docs/get-started/install-and-run.md" + page.parent.mkdir(parents=True) + page.write_text("\nplaceholder\n\n") + gate.check_docs(tmp_path, policy, write=True) + wheel = tmp_path / "powercontext-2.0.0-py3-none-any.whl" + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("powercontext-2.0.0.dist-info/METADATA", "Name: powercontext\nVersion: 2.0.0\n") + return tmp_path, wheel, policy + + +def test_required_mode_executes_three_cases_but_cannot_publish_as_supported(release): + root, wheel, policy = release + with pytest.raises(gate.GateError, match="support declaration"): + gate.gate(root, wheel, root / "evidence", release=True, release_notes=gate.support_text(policy, "en")) + result = json.loads((root / "evidence/result.json").read_text()) + assert result["status"] == "failed" + assert len(result["cases"]) == 3 + assert all(case["status"] == "passed" for case in result["cases"]) + assert result["target_wheel_sha256"] == gate.sha256(wheel) + + +def test_release_notes_cannot_omit_the_support_limit(release): + root, wheel, _ = release + with pytest.raises(gate.GateError, match="notes"): + gate.gate(root, wheel, root / "evidence", release=True, release_notes="Upgrade your deployment now.") + + +def test_release_rejects_wrong_artifact_version(release): + root, wheel, _ = release + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("powercontext-9.0.0.dist-info/METADATA", "Name: powercontext\nVersion: 9.0.0\n") + with pytest.raises(gate.GateError, match="Wheel version"): + gate.gate(root, wheel, root / "evidence", release=True) + + +def test_schema_change_needs_a_new_compatibility_review(release): + root, _, _ = release + (root / "src/powercontext/builtin/persistence/tables.py").write_text("# different schema\n") + with pytest.raises(gate.GateError, match="Persistence changed"): + gate.load_policy(root) + + +def test_supported_mode_cannot_succeed_with_an_empty_matrix(release): + root, _, policy = release + policy["mode"] = "supported-upgrades" + policy["automatic_sources"] = None + (root / gate.MANIFEST).write_text(json.dumps(policy)) + with pytest.raises(gate.GateError, match="nonempty matrix"): + gate.load_policy(root) + + +@pytest.mark.parametrize( + "relative", + [ + "src/powercontext/server/identity.py", + "src/powercontext/server/authz/repository.py", + "src/powercontext/builtin/code/seekdb.py", + "src/powercontext/limits.py", + ], +) +def test_schema_owners_outside_persistence_need_compatibility_review(release, relative): + root, _, _ = release + owner = root / relative + owner.parent.mkdir(parents=True, exist_ok=True) + owner.write_text("# changed managed schema input\n") + with pytest.raises(gate.GateError, match="Persistence changed"): + gate.load_policy(root) + + +def supported_case(root, policy): + policy["mode"] = "supported-upgrades" + policy["automatic_sources"] = None + policy["cases"] = [ + { + "source_version": "1.3.0", + "backend": backend, + "configuration": "full-server", + "suite": "tests/release_upgrades/test_example.py", + } + for backend in ("sqlite", "seekdb", "oceanbase") + ] + (root / gate.MANIFEST).write_text(json.dumps(policy)) + gate.check_docs(root, policy, write=True) + + +@pytest.mark.parametrize( + "blocker", ["migration_required", "unmanaged full Server layout", "migration_framework_not_ready"] +) +def test_any_upgrade_stage_failure_blocks_release_and_records_failure(release, monkeypatch, blocker): + root, wheel, policy = release + supported_case(root, policy) + + def fail(*_args): + raise gate.GateError(blocker) + + monkeypatch.setattr(gate, "run_case", fail) + with pytest.raises(gate.GateError, match="Required upgrade acceptance failed"): + gate.gate(root, wheel, root / "evidence") + evidence = json.loads((root / "evidence/result.json").read_text()) + assert evidence["status"] == "failed" + assert len(evidence["cases"]) == 3 + assert all(case["status"] == "failed" and blocker in case["error"] for case in evidence["cases"]) + + +def test_missing_suite_cannot_be_advertised(release): + root, _, policy = release + supported_case(root, policy) + (root / policy["cases"][0]["suite"]).unlink() + with pytest.raises(gate.GateError, match="Missing acceptance suite"): + gate.load_policy(root) + + +def test_installation_guidance_cannot_drift_from_matrix(release): + root, _, policy = release + page = root / "docs/en/docs/get-started/install-and-run.md" + page.write_text(page.read_text().replace(gate.NOTICE["en"], "All upgrades are supported.")) + with pytest.raises(gate.GateError, match="instructions differ"): + gate.check_docs(root, policy) + + +@pytest.mark.parametrize("invalid", ["empty", "missing-startup", "skipped", "failure", "error", "truncated"]) +def test_partial_or_skipped_acceptance_is_not_passing_evidence(tmp_path, invalid): + report = tmp_path / "junit.xml" + suite = ElementTree.Element("testsuite") + for phase in sorted(gate.PHASES): + if invalid == "empty" or (invalid == "missing-startup" and phase == "test_target_startup"): + continue + case = ElementTree.SubElement(suite, "testcase", name=phase) + if phase == "test_target_startup" and invalid in {"skipped", "failure", "error"}: + ElementTree.SubElement(case, invalid) + ElementTree.ElementTree(suite).write(report) + if invalid == "truncated": + report.write_text("") + with pytest.raises((gate.GateError, ElementTree.ParseError)): + gate.validate_junit(report) + + +def test_complete_passing_report_is_accepted(tmp_path): + report = tmp_path / "junit.xml" + suite = ElementTree.Element("testsuite") + for phase in gate.PHASES: + ElementTree.SubElement(suite, "testcase", name=phase) + ElementTree.ElementTree(suite).write(report) + gate.validate_junit(report) + + +def test_missing_backend_evidence_fails_even_with_a_passing_report(tmp_path): + report = tmp_path / "backend.json" + report.write_text(json.dumps({"backend": "oceanbase", "engine_version": "4.6.3"})) + with pytest.raises(gate.GateError, match="driver_version"): + gate.validate_backend_evidence(report, "oceanbase") + + +def test_old_evidence_directory_cannot_be_reused(release): + root, wheel, _ = release + gate.gate(root, wheel, root / "evidence") + with pytest.raises(FileExistsError): + gate.gate(root, wheel, root / "evidence") + + +def catalog_for(*versions): + return { + "releases": { + version: [ + { + "filename": f"powercontext-{version}-py3-none-any.whl", + "packagetype": "bdist_wheel", + "yanked": False, + "url": f"https://files.pythonhosted.org/packages/powercontext-{version}-py3-none-any.whl", + "digests": {"sha256": "a" * 64}, + } + ] + for version in versions + } + } + + +def automatic_policy(policy): + policy["automatic_sources"] = { + "strategy": "fixed-version", + "source_version": "1.3.0", + "profiles": [ + { + "backend": backend, + "configuration": "full-server-no-vector", + "suite": "tests/release_upgrades/test_example.py", + } + for backend in ("sqlite", "seekdb", "oceanbase") + ], + } + + +@pytest.mark.parametrize("target", ["1.4.0", "1.4.1", "1.5.0", "1.5.1.dev2"]) +def test_fixed_source_tracks_each_target_on_all_three_backends(release, monkeypatch, target): + _, _, policy = release + automatic_policy(policy) + monkeypatch.setattr( + matrix, "fetch_catalog", lambda: catalog_for("1.3.0", "1.4.0", "1.4.1", "1.5.0rc1", "1.5.0", "1.6.0") + ) + resolved = matrix.resolve_matrix(policy, target) + assert [(case["source_version"], case["backend"]) for case in resolved["candidate_cases"]] == [ + ("1.3.0", backend) for backend in ("sqlite", "seekdb", "oceanbase") + ] + assert resolved["required_cases"] == resolved["candidate_cases"] + assert all(case["source_artifact"]["sha256"] == "a" * 64 for case in resolved["candidate_cases"]) + + +def test_newly_published_versions_do_not_change_the_fixed_source(release, monkeypatch): + _, _, policy = release + automatic_policy(policy) + catalog = catalog_for("1.3.0") + monkeypatch.setattr(matrix, "fetch_catalog", lambda: catalog) + first = matrix.resolve_matrix(policy, "1.5.0") + catalog["releases"].update(catalog_for("1.4.1")["releases"]) + second = matrix.resolve_matrix(policy, "1.5.0") + assert first["candidate_cases"] == second["candidate_cases"] + assert len(second["candidate_cases"]) == 3 + + +@pytest.mark.parametrize("target", ["1.2.0", "1.3.0", "1.3.0rc1"]) +def test_fixed_source_must_precede_target(release, target): + _, _, policy = release + automatic_policy(policy) + with pytest.raises(ValueError, match="must precede target"): + matrix.resolve_matrix(policy, target) + + +@pytest.mark.parametrize("broken", ["empty", "missing-source", "yanked", "missing-wheel", "bad-hash", "foreign-origin"]) +def test_unavailable_source_cannot_silently_shrink_matrix(release, monkeypatch, broken): + _, _, policy = release + automatic_policy(policy) + catalog = catalog_for("1.3.0", "1.4.1") + wheel = catalog["releases"]["1.3.0"][0] + if broken == "empty": + catalog["releases"] = {} + elif broken == "missing-source": + del catalog["releases"]["1.3.0"] + elif broken == "yanked": + wheel["yanked"] = True + elif broken == "missing-wheel": + catalog["releases"]["1.3.0"] = [] + elif broken == "bad-hash": + wheel["digests"]["sha256"] = "invalid" + else: + wheel["url"] = "https://untrusted.invalid/package.whl" + monkeypatch.setattr(matrix, "fetch_catalog", lambda: catalog) + with pytest.raises(ValueError): + matrix.resolve_matrix(policy, "1.5.0") + + +def test_catalog_outage_records_a_failed_gate(release, monkeypatch): + root, wheel, policy = release + automatic_policy(policy) + (root / gate.MANIFEST).write_text(json.dumps(policy)) + + def unavailable(): + raise OSError("Catalog unavailable") # noqa: TRY003 -- exercise a distinct discovery outage + + monkeypatch.setattr(matrix, "fetch_catalog", unavailable) + with pytest.raises(OSError, match="Catalog unavailable"): + gate.gate(root, wheel, root / "evidence") + assert json.loads((root / "evidence/result.json").read_text())["status"] == "failed" + + +def test_switching_to_supported_upgrades_requires_all_backend_suites(release): + root, _, policy = release + automatic_policy(policy) + policy["automatic_sources"]["profiles"][0]["suite"] = None + (root / gate.MANIFEST).write_text(json.dumps(policy)) + with pytest.raises(gate.GateError, match="every backend profile"): + gate.load_policy(root) + + +def test_supported_fixed_source_executes_every_backend(release, monkeypatch): + root, wheel, policy = release + supported_case(root, policy) + suite = policy["cases"][0]["suite"] + policy["cases"] = [] + automatic_policy(policy) + for profile in policy["automatic_sources"]["profiles"]: + profile["suite"] = suite + (root / gate.MANIFEST).write_text(json.dumps(policy)) + gate.check_docs(root, policy, write=True) + monkeypatch.setattr(matrix, "fetch_catalog", lambda: catalog_for("1.3.0", "1.4.1")) + executed = [] + + def execute(_root, _wheel, case, _output): + executed.append((case["source_version"], case["backend"])) + return {"case": case, "status": "passed"} + + monkeypatch.setattr(gate, "run_case", execute) + gate.gate(root, wheel, root / "evidence") + assert executed == [(version, backend) for version in ("1.3.0",) for backend in ("sqlite", "seekdb", "oceanbase")] + result = json.loads((root / "evidence/result.json").read_text()) + assert len(result["cases"]) == len(result["resolved_matrix"]["required_cases"]) == 3 + + +def test_publishing_jobs_depend_on_nonoptional_preupload_upgrade_gate(): + root = Path(__file__).parents[1] + workflow = yaml.safe_load((root / ".github/workflows/release.yml").read_text()) + build = workflow["jobs"]["release-build"] + steps = build["steps"] + index = next(i for i, step in enumerate(steps) if "run_upgrade_release_gate.py" in step.get("run", "")) + assert "if" not in steps[index] and not steps[index].get("continue-on-error") + assert "--release --release-event" in steps[index]["run"] + upload = next(i for i, step in enumerate(steps) if step.get("with", {}).get("name") == "release-dists") + assert index < upload + assert steps[upload]["with"]["path"] == "${{ runner.temp }}/upgrade-release-evidence/validated-dist/*" + for name in ("pypi-publish", "release-assets"): + assert workflow["jobs"][name]["needs"] == "release-build" + + +@pytest.mark.parametrize( + "configuration", ["full-server-no-vector", "full-server-code", "full-server-vector", "full-server-vector-code"] +) +def test_repository_baseline_covers_all_three_databases(configuration): + policy = gate.load_policy(Path(__file__).parents[1]) + selection = policy["automatic_sources"] + assert selection["strategy"] == "fixed-version" + assert selection["source_version"] == "1.3.0" + assert {profile["backend"] for profile in selection["profiles"]} == {"sqlite", "seekdb", "oceanbase"} + assert {profile["backend"] for profile in selection["profiles"] if profile["configuration"] == configuration} == { + "sqlite", + "seekdb", + "oceanbase", + } + + +@pytest.mark.parametrize("workflow_name,job_name", [("master", "upgrade-release-gate"), ("release", "release-build")]) +def test_upgrade_jobs_require_real_backends_and_always_clean_up(workflow_name, job_name): + root = Path(__file__).parents[1] + workflow = yaml.safe_load((root / f".github/workflows/{workflow_name}.yml").read_text()) + job = workflow["jobs"][job_name] + steps = job["steps"] + setup_index = next( + i for i, step in enumerate(steps) if step.get("uses") == "./.github/actions/setup-upgrade-oceanbase" + ) + gate_index = next(i for i, step in enumerate(steps) if "run_upgrade_release_gate.py" in step.get("run", "")) + assert setup_index < gate_index + for index in (setup_index, gate_index): + assert "if" not in steps[index] and not steps[index].get("continue-on-error") + assert not any("ci_legacy_upgrade_rejection.py" in step.get("run", "") for step in steps) + assert "/pc_upgrade_test?" in job["env"]["PC_UPGRADE_OCEANBASE_URL"] + assert "/pc_upgrade_fresh?" in job["env"]["PC_UPGRADE_OCEANBASE_FRESH_URL"] + cleanup = next(step for step in steps if "docker rm --force upgrade-oceanbase" in step.get("run", "")) + assert cleanup["if"] == "always()" + + +def test_oceanbase_fixture_selects_the_oceanbase_server_profile(tmp_path, monkeypatch): + for key in os.environ: + if key.startswith("POWERCONTEXT_"): + monkeypatch.delenv(key) + url = "mysql+aoceanbase://root%40test:test@127.0.0.1:2881/pc_upgrade_test?charset=utf8mb4" + for key, value in database_environment("oceanbase", tmp_path / "unused", {"PC_UPGRADE_OCEANBASE_URL": url}).items(): + monkeypatch.setenv(key, value) + settings = ServerSettings() + assert settings.database.kind == "oceanbase" + assert settings.database.url.get_secret_value() == url + + +@pytest.mark.parametrize("mode", ["fresh-install-only", "upgrade-required", "supported-upgrades"]) +def test_declarations_cannot_bypass_missing_backend_or_suite(release, mode): + root, _, policy = release + policy["mode"] = mode + policy["automatic_sources"]["profiles"] = policy["automatic_sources"]["profiles"][:2] + (root / gate.MANIFEST).write_text(json.dumps(policy)) + with pytest.raises(gate.GateError): + gate.load_policy(root) + + +def test_fresh_install_only_is_not_an_upgrade_escape_hatch(release): + root, _, policy = release + policy["mode"] = "fresh-install-only" + (root / gate.MANIFEST).write_text(json.dumps(policy)) + with pytest.raises(gate.GateError, match="mode"): + gate.load_policy(root) + + +def test_failed_backend_does_not_skip_other_required_backends(release, monkeypatch): + root, wheel, _ = release + executed = [] + + def execute(_root, _wheel, case, _output): + executed.append(case["backend"]) + if case["backend"] == "sqlite": + raise gate.GateError("migration_required") + return {"case": case, "status": "passed"} + + monkeypatch.setattr(gate, "run_case", execute) + with pytest.raises(gate.GateError, match="failed: sqlite"): + gate.gate(root, wheel, root / "evidence") + assert executed == ["sqlite", "seekdb", "oceanbase"] + result = json.loads((root / "evidence/result.json").read_text()) + assert result["status"] == "failed" + assert [case["status"] for case in result["cases"]] == ["failed", "passed", "passed"] + + +def test_failed_seekdb_acceptance_keeps_native_logs_before_workspace_cleanup(tmp_path): + output = tmp_path / "evidence" + output.mkdir() + + with pytest.raises(gate.GateError, match="fixture migration failed"), gate.case_workspace("seekdb", output) as work: + native = work / "database/log" + native.mkdir(parents=True) + (native / "observer.log").write_bytes(b"native recovery diagnostic\n") + (work / "database/store").write_bytes(b"database payload must not become a log") + fresh = work / "fresh/log" + fresh.mkdir(parents=True) + (fresh / "observer.log.wf").write_bytes(b"fresh native diagnostic\n") + raise gate.GateError("fixture migration failed") # noqa: TRY003 -- synthetic acceptance failure + + assert not work.exists() + with zipfile.ZipFile(output / "seekdb-native-logs.zip") as archive: + assert set(archive.namelist()) == {"database/log/observer.log", "fresh/log/observer.log.wf"} + assert archive.read("database/log/observer.log") == b"native recovery diagnostic\n" + assert archive.read("fresh/log/observer.log.wf") == b"fresh native diagnostic\n" + manifest = json.loads((output / "seekdb-native-logs.json").read_text()) + assert len(manifest["files"]) == 2 + assert not any(item["truncated"] for item in manifest["files"]) + + +def test_seekdb_diagnostics_keep_a_bounded_tail_of_large_native_logs(tmp_path): + output = tmp_path / "evidence" + output.mkdir() + size = 5 * 1024 * 1024 + with gate.case_workspace("seekdb", output) as work: + native = work / "database/log" + native.mkdir(parents=True) + (native / "observer.log").write_bytes(b"x" * size + b"final diagnostic") + + with zipfile.ZipFile(output / "seekdb-native-logs.zip") as archive: + tail = archive.read("database/log/observer.log") + assert len(tail) <= 4 * 1024 * 1024 + assert tail.endswith(b"final diagnostic") + manifest = json.loads((output / "seekdb-native-logs.json").read_text()) + assert manifest["files"][0]["truncated"] is True + assert manifest["files"][0]["size_bytes"] == size + len(b"final diagnostic") + + +@pytest.mark.parametrize( + "relative", + [ + "src/powercontext/builtin/persistence/tables.py", + "src/powercontext/builtin/persistence/revision.sql", + "src/powercontext/builtin/code/layout.json", + "src/powercontext/server/authz/repository.py", + "src/powercontext/server/identity.py", + "src/powercontext/limits.py", + ], +) +def test_checkout_line_endings_preserve_compatibility_review(release, relative): + root, _, policy = release + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"first line\nsecond line\n") + policy["schema_review"]["persistence_sha256"] = gate.managed_digest(root) + (root / gate.MANIFEST).write_text(json.dumps(policy)) + path.write_bytes(b"first line\r\nsecond line\r\n") + assert gate.load_policy(root)["schema_review"] == policy["schema_review"] + path.write_bytes(b"first line\r\nchanged content\r\n") + with pytest.raises(gate.GateError, match="file set or content; CRLF/LF normalized"): + gate.load_policy(root) + + +def test_artifact_hashes_do_not_normalize_bytes(tmp_path): + artifact = tmp_path / "artifact" + artifact.write_bytes(b"payload\n") + before = gate.sha256(artifact) + artifact.write_bytes(b"payload\r\n") + assert gate.sha256(artifact) != before + + +_PROCESS_FIXTURE = """ +import os, signal, subprocess, sys, time +from pathlib import Path + +role, mode, control, evidence = sys.argv[1:] +control, evidence = Path(control), Path(evidence) +child = None +if mode == "forced-timeout": + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) +else: + def stop(_signal, _frame): + raise SystemExit(0) + signal.signal(signal.SIGTERM, stop) +try: + ready = evidence / (role + ".ready") + ready.write_text(str(os.getpid())) + ready.replace(evidence / (role + ".pid")) + if role != "engine": + next_role = "server" if role == "root" else "engine" + child = subprocess.Popen( + [sys.executable, __file__, next_role, mode, str(control), str(evidence)], + start_new_session=True, + ) + if role == "root" and mode in {"success", "failure"}: + while not (evidence / "engine.pid").exists(): + time.sleep(0.02) + # Deliberately leave both detached generations for the command owner. + os._exit(0 if mode == "success" else 7) + while control.exists(): + time.sleep(0.02) +finally: + if child is not None: + child.terminate() + try: + child.wait(timeout=2) + except subprocess.TimeoutExpired: + child.kill() + child.wait() + (evidence / (role + ".closed")).write_text("closed") +""" + + +@pytest.mark.skipif(sys.platform != "linux", reason="The upgrade supervisor uses Linux subreaper ownership") +@pytest.mark.parametrize("mode", ["success", "failure", "timeout", "forced-timeout"]) +def test_upgrade_command_reaps_detached_descendants_before_workspace_cleanup(tmp_path, mode): + fixture = tmp_path / "process_fixture.py" + fixture.write_text(_PROCESS_FIXTURE) + control = tmp_path / "keep-running" + control.touch() + report = tmp_path / "evidence" + report.mkdir() + # This child belongs to the test, not the isolated command supervisor. + sentinel = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(60)"]) + try: + with gate.case_workspace("sqlite", report) as work: + arguments = [sys.executable, str(fixture), "root", mode, str(control), str(report)] + options = { + "environment": dict(os.environ), + "cwd": work, + "log": report / "command.log", + "timeout": 5 if mode.endswith("timeout") else 20, + "shutdown_timeout": 0.5, + } + if mode == "success": + gate.run_owned(arguments, **options) + else: + with pytest.raises(gate.GateError, match="Command failed"): + gate.run_owned(arguments, **options) + assert sentinel.poll() is None + for role in ("root", "server", "engine"): + pid = int((report / f"{role}.pid").read_text()) + with pytest.raises(ProcessLookupError): + os.kill(pid, 0) + if mode != "forced-timeout": + assert (report / "server.closed").is_file() + assert (report / "engine.closed").is_file() + if mode.endswith("timeout"): + assert "exceeded 5 seconds" in (report / "command.log").read_text() + assert not work.exists() + finally: + # Also let controlled fixture processes exit if the regression fails. + # Never signal a stale PID obtained from a fixture file. + control.unlink(missing_ok=True) + sentinel.terminate() + sentinel.wait(timeout=5) + + +@pytest.mark.skipif(sys.platform != "linux", reason="The upgrade supervisor uses Linux subreaper ownership") +@pytest.mark.parametrize("cancellation", ["parent-term", "repeat-interrupt"]) +def test_upgrade_command_owner_finishes_cleanup_after_gate_cancellation(tmp_path, cancellation): + fixture = tmp_path / "process_fixture.py" + fixture.write_text(_PROCESS_FIXTURE) + control = tmp_path / "keep-running" + control.touch() + report = tmp_path / "evidence" + report.mkdir() + owner_script = tmp_path / "owner.py" + owner_script.write_text( + "import os,sys\nfrom pathlib import Path\n" + f"sys.path[:0] = {[str(Path(gate.__file__).parent), str(Path(matrix.__file__).parent)]!r}\n" + "import upgrade_release_gate as gate\n" + f"report = Path({str(report)!r})\n" + "with gate.case_workspace('sqlite', report) as work:\n" + " (report / 'workspace').write_text(str(work))\n" + f" gate.run_owned({[sys.executable, str(fixture), 'root', 'forced-timeout', str(control), str(report)]!r}, " + "environment=dict(os.environ), cwd=work, log=report / 'command.log', timeout=60, shutdown_timeout=0.5)\n" + ) + sentinel = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(60)"]) + with (report / "owner.log").open("w") as log: + owner = subprocess.Popen([sys.executable, str(owner_script)], stdout=log, stderr=subprocess.STDOUT) + pids = [] + try: + deadline = time.monotonic() + 15 + while not (report / "engine.pid").exists(): + assert owner.poll() is None, (report / "owner.log").read_text() + assert time.monotonic() < deadline, "Controlled processes did not become ready" + time.sleep(0.02) + pids = [int((report / f"{role}.pid").read_text()) for role in ("root", "server", "engine")] + work = Path((report / "workspace").read_text()) + owner.send_signal(signal.SIGTERM if cancellation == "parent-term" else signal.SIGINT) + if cancellation == "repeat-interrupt": + time.sleep(0.1) + assert owner.poll() is None, "Cancellation did not wait for owned processes" + owner.send_signal(signal.SIGINT) + assert owner.wait(timeout=10) != 0 + if cancellation == "parent-term": + # The dead gate cannot await its owner; PDEATHSIG initiates cleanup. + deadline = time.monotonic() + 10 + while any(_process_exists(pid) for pid in pids): + assert time.monotonic() < deadline, "Detached descendants survived gate termination" + time.sleep(0.02) + else: + # The second interrupt must not delete the workspace early. + assert not any(_process_exists(pid) for pid in pids) + assert not work.exists() + assert sentinel.poll() is None + finally: + control.unlink(missing_ok=True) + if owner.poll() is None: + owner.terminate() + owner.wait(timeout=5) + sentinel.terminate() + sentinel.wait(timeout=5) + if cancellation == "parent-term" and pids and not any(_process_exists(pid) for pid in pids): + shutil.rmtree(Path((report / "workspace").read_text()), ignore_errors=True) + + +def _process_exists(pid): + try: + os.kill(pid, 0) + except ProcessLookupError: + return False + return True