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..61a110c2ea --- /dev/null +++ b/.github/actions/setup-upgrade-oceanbase/action.yml @@ -0,0 +1,82 @@ +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; + CREATE DATABASE pc_upgrade_140_test; + CREATE DATABASE pc_upgrade_140_fresh; + CREATE DATABASE pc_upgrade_140_code_test; + CREATE DATABASE pc_upgrade_140_code_fresh; + CREATE DATABASE pc_upgrade_140_vector_test; + CREATE DATABASE pc_upgrade_140_vector_fresh; + CREATE DATABASE pc_upgrade_140_vector_code_test; + CREATE DATABASE pc_upgrade_140_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/candidate-schema-replay.yml b/.github/workflows/candidate-schema-replay.yml new file mode 100644 index 0000000000..dc30de99ff --- /dev/null +++ b/.github/workflows/candidate-schema-replay.yml @@ -0,0 +1,70 @@ +name: Candidate schema replay + +on: + pull_request: + paths: + - src/powercontext/builtin/persistence/** + - src/powercontext/builtin/artifacts/** + - tests/builtin/persistence/** + - scripts/replay_candidate_migration.py + - .github/workflows/candidate-schema-replay.yml + - .github/actions/setup-upgrade-oceanbase/** + - pyproject.toml + - uv.lock + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: candidate-replay-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + replay: + name: Candidate replay (${{ matrix.backend }}) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + backend: [sqlite, seekdb, oceanbase] + env: + POWERCONTEXT_TEST_MIGRATION_SEEKDB: "1" + POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL: mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_migration_probe_candidate_replay?charset=utf8mb4 + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python + uses: ./.github/actions/setup-python-env + - name: Start isolated OceanBase + if: matrix.backend == 'oceanbase' + uses: ./.github/actions/setup-upgrade-oceanbase + - name: Create the scratch database namespace + if: matrix.backend == 'oceanbase' + run: >- + docker exec upgrade-oceanbase + obclient -h127.0.0.1 -P2881 -uroot@test -ppowercontext-e2e + -e 'CREATE DATABASE pc_migration_probe_candidate_replay' + - name: Replay and recover the frozen Candidate conversion without skips + run: >- + uv run --locked --extra seekdb python scripts/replay_candidate_migration.py + ${{ matrix.backend }} --output "$RUNNER_TEMP/candidate-replay" + - name: Preserve OceanBase diagnostics + if: always() && matrix.backend == 'oceanbase' + run: | + mkdir -p "$RUNNER_TEMP/candidate-replay" + docker logs upgrade-oceanbase > "$RUNNER_TEMP/candidate-replay/oceanbase.log" 2>&1 || true + docker stop --timeout 30 upgrade-oceanbase || true + - name: Save generated inventories and comparisons + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: candidate-replay-${{ matrix.backend }}-${{ github.sha }} + path: ${{ runner.temp }}/candidate-replay/ + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/capture-release-schema.yml b/.github/workflows/capture-release-schema.yml new file mode 100644 index 0000000000..ddcfda28c7 --- /dev/null +++ b/.github/workflows/capture-release-schema.yml @@ -0,0 +1,63 @@ +name: Capture released Server schema + +on: + pull_request: + paths: + - scripts/capture_release_schema.py + - scripts/upgrade_database_probe.py + - scripts/upgrade_process.py + - tests/release_upgrades/test_full_server.py + - .github/workflows/capture-release-schema.yml + - .github/actions/setup-upgrade-oceanbase/** + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-schema-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + capture: + name: Capture 1.4.0 (${{ matrix.backend }}, ${{ matrix.configuration }}) + runs-on: ubuntu-24.04 + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + backend: [sqlite, seekdb, oceanbase] + configuration: [full-server-no-vector, full-server-code, full-server-vector, full-server-vector-code] + env: + PC_UPGRADE_OCEANBASE_URL: mysql+aoceanbase://root%40test:powercontext-e2e@127.0.0.1:2881/pc_upgrade_test?charset=utf8mb4 + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python + uses: ./.github/actions/setup-python-env + - name: Start isolated OceanBase + if: matrix.backend == 'oceanbase' + uses: ./.github/actions/setup-upgrade-oceanbase + - name: Capture installed release without current runtime initialization + run: >- + .venv/bin/python scripts/upgrade_process.py + --parent-pid $$ --timeout 1800 --shutdown-timeout 60 -- + .venv/bin/python scripts/capture_release_schema.py + ${{ matrix.backend }} --configuration ${{ matrix.configuration }} + --output "$RUNNER_TEMP/release-schema" + - name: Preserve OceanBase diagnostics + if: always() && matrix.backend == 'oceanbase' + run: | + mkdir -p "$RUNNER_TEMP/release-schema" + docker logs upgrade-oceanbase > "$RUNNER_TEMP/release-schema/oceanbase.log" 2>&1 || true + docker stop --timeout 30 upgrade-oceanbase || true + - name: Save source schema and provenance + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: release-1.4.0-${{ matrix.backend }}-${{ matrix.configuration }}-${{ github.sha }} + path: ${{ runner.temp }}/release-schema/ + if-no-files-found: error + retention-days: 30 diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml index 5b4b97f2e8..7894c6b584 100644 --- a/.github/workflows/desktop.yml +++ b/.github/workflows/desktop.yml @@ -134,6 +134,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 @@ -141,6 +142,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 1fecae49cd..ccd0afeb32 100644 --- a/.github/workflows/e2e-harness.yml +++ b/.github/workflows/e2e-harness.yml @@ -68,7 +68,7 @@ jobs: if: ${{ !cancelled() && (needs.changes.result != 'success' || fromJSON(needs.changes.outputs.suites || '{}').database) }} name: Recall effort (OceanBase) runs-on: ubuntu-latest - timeout-minutes: 25 + timeout-minutes: 60 env: OCEANBASE_IMAGE: ghcr.io/oceanbase/oceanbase-ce:4.3.5.6-106000012026040916 POWERCONTEXT_TEST_OCEANBASE_URL: >- @@ -98,10 +98,37 @@ jobs: if docker exec recall-effort-oceanbase \ obclient -h127.0.0.1 -P2881 -uroot@test -ppowercontext-e2e \ -Dpowercontext_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 full Server recovery scenarios. + docker exec recall-effort-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 recall-effort-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/recall-effort-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/recall-effort-datafile.tsv"; then + datafile_ready=true + break + fi + sleep 2 + done + cat "$RUNNER_TEMP/recall-effort-datafile.tsv" + if [ "$datafile_ready" != true ]; then + echo 'OceanBase datafile configuration did not become effective within one minute.' >&2 + exit 1 + fi docker exec recall-effort-oceanbase \ obclient -h127.0.0.1 -P2881 -uroot@test -ppowercontext-e2e \ -Dpowercontext_test \ - -e "SELECT VERSION(); SHOW VARIABLES LIKE 'ob_compatibility_mode';" + -e "SET GLOBAL recyclebin=OFF; SELECT VERSION(); SHOW VARIABLES LIKE 'ob_compatibility_mode'; SHOW GLOBAL VARIABLES LIKE 'recyclebin';" exit 0 fi if [ "$(docker inspect --format '{{.State.Running}}' recall-effort-oceanbase)" != true ]; then @@ -176,11 +203,85 @@ jobs: tests/builtin/persistence/test_database_snapshots.py \ --junitxml=.powercontext/recall-effort-ci/regressions.xml -q -rs + - name: Recover interrupted Server initialization 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 -vv -s -rs \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_mysql_full_server_resumes_interrupted_initialization_before_manifest_seed \ + -o faulthandler_timeout=180 --durations=0 \ + -k oceanbase --junitxml=.powercontext/recall-effort-ci/full-server-initialization.xml + + # Each group keeps a hard deadline. The six independent recovery scenarios + # must not consume a single shared deadline before client-exit recovery runs. + - name: Recover committed Server DDL and domain checkpoints 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 -vv -s -rs \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_mysql_full_server_interruption_preserves_history_and_original_maintenance \ + -o faulthandler_timeout=180 --durations=0 \ + -k oceanbase --junitxml=.powercontext/recall-effort-ci/full-server-checkpoints.xml + + - name: Recover an exited Server migration client 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 -vv -s -rs \ + tests/builtin/persistence/test_mysql_migration_runner.py::test_oceanbase_full_server_client_exit_requires_acknowledgement_and_preserves_recovery \ + -o faulthandler_timeout=180 --durations=0 \ + -k oceanbase --junitxml=.powercontext/recall-effort-ci/full-server-client-exit.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 -vv -s -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=0 \ + -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-initialization.xml"), + Path(".powercontext/recall-effort-ci/full-server-checkpoints.xml"), + Path(".powercontext/recall-effort-ci/full-server-client-exit.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) != 11 or any(case.find(tag) is not None for case in cases for tag in ("skipped", "failure", "error")): + raise SystemExit("All eleven native full Server recovery and maintenance scenarios must pass without skips.") + PY + - name: Collect the database log if: always() run: | mkdir -p .powercontext/recall-effort-ci docker logs recall-effort-oceanbase > .powercontext/recall-effort-ci/oceanbase.log 2>&1 || true + for logfile in observer.log observer.log.wf; do + docker exec recall-effort-oceanbase tail -n 20000 "/root/demo/log/$logfile" \ + > ".powercontext/recall-effort-ci/$logfile" 2>&1 || true + done + df -h / > .powercontext/recall-effort-ci/runner-disk.txt 2>&1 || true + docker exec recall-effort-oceanbase df -h /root/demo \ + > .powercontext/recall-effort-ci/container-disk.txt 2>&1 || true + docker exec recall-effort-oceanbase \ + obclient -h127.0.0.1 -P2881 -uroot@sys \ + -e "SHOW PARAMETERS WHERE name IN ('datafile_size', 'datafile_maxsize', 'datafile_next'); SELECT data_disk_allocated, data_disk_capacity, data_disk_in_use FROM oceanbase.GV\$OB_SERVERS;" \ + > .powercontext/recall-effort-ci/database-disk.txt 2>&1 || true - name: Save validation reports if: always() @@ -203,7 +304,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: 145 env: OCEANBASE_IMAGE: ghcr.io/oceanbase/oceanbase-ce:4.3.5.6-106000012026040916 POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL: >- @@ -291,13 +392,23 @@ 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 concurrent managed startup without skips + # Collection and three isolated cases retain their 180s process deadlines. + timeout-minutes: 15 + run: | + uv run --locked --extra server python scripts/run_atomic_memory_clean_migration.py \ + oceanbase --output "$MIGRATION_REPORTS/startup-snapshot" --tests \ + tests/e2e/test_managed_startup_read_consistency.py + test "$(wc -l < "$MIGRATION_REPORTS/startup-snapshot/nodes.txt")" -eq 3 + - 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 \ @@ -345,7 +456,8 @@ jobs: acceptance: needs: validate runs-on: ubuntu-latest - timeout-minutes: 40 + # OceanBase regressions can take 32 minutes before the separate replay scenarios. + timeout-minutes: ${{ matrix.database == 'oceanbase' && 60 || 40 }} strategy: fail-fast: false matrix: @@ -364,6 +476,7 @@ jobs: version: ${{ env.UV_VERSION }} - name: Verify OceanBase Atomic Memory and HTTP regressions + timeout-minutes: 40 if: matrix.database == 'oceanbase' env: COMPOSE_PROJECT_NAME: powercontext-e2e-oceanbase @@ -448,6 +561,7 @@ jobs: PY - name: Run deterministic scenarios + timeout-minutes: ${{ matrix.database == 'oceanbase' && 15 || 40 }} id: acceptance_scenarios env: POWERCONTEXT_E2E_DATABASE: ${{ matrix.database }} 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 98c8aa284f..f7f1e3b9ff 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -7,6 +7,15 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review] workflow_dispatch: + inputs: + upgrade_selection: + description: Upgrade cases (seekDB Code configurations are temporarily manual) + type: choice + default: automatic + options: + - automatic + - seekdb-code + - full permissions: contents: read @@ -16,9 +25,142 @@ concurrency: cancel-in-progress: true jobs: + seekdb-managed-startup: + name: SeekDB managed startup regression (priority) + timeout-minutes: 20 + runs-on: ubuntu-latest + env: + POWERCONTEXT_TEST_MIGRATION_SEEKDB: "1" + PYTHONUNBUFFERED: "1" + MIGRATION_REPORTS: ${{ github.workspace }}/.powercontext/managed-startup-seekdb + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - name: Set up the environment + uses: ./.github/actions/setup-python-env + with: + python-version: "3.12" + + - name: Test concurrent managed startup without skips + # Collection and three isolated cases retain their 180s process deadlines. + timeout-minutes: 15 + env: + TMPDIR: ${{ runner.temp }}/am-seekdb + run: | + mkdir -p "$TMPDIR" + uv run --locked --extra seekdb --extra server python scripts/run_atomic_memory_clean_migration.py \ + seekdb --output "$MIGRATION_REPORTS" --tests \ + tests/e2e/test_managed_startup_read_consistency.py + test "$(wc -l < "$MIGRATION_REPORTS/nodes.txt")" -eq 3 + + - name: Collect native diagnostics + if: always() + env: + TMPDIR: ${{ runner.temp }}/am-seekdb + run: | + mkdir -p "$MIGRATION_REPORTS" + df -h . /tmp > "$MIGRATION_REPORTS/disk.txt" + if [ -d "$TMPDIR" ]; then + find "$TMPDIR" -type f \( -name '*.log' -o -name '*.log.wf' \) \ + -print -exec tail -c 8388608 {} \; > "$MIGRATION_REPORTS/seekdb-native.log" + fi + + - name: Save managed startup reports + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: managed-startup-seekdb-${{ github.sha }} + path: ${{ env.MIGRATION_REPORTS }}/ + include-hidden-files: true + if-no-files-found: error + retention-days: 7 + changes: uses: ./.github/workflows/ci-changes.yml + upgrade-acceptance: + needs: seekdb-managed-startup + # Collect selected upgrade cases even when the priority regression fails. + if: ${{ !cancelled() }} + uses: ./.github/workflows/upgrade-release.yml + with: + selection: ${{ github.event_name == 'workflow_dispatch' && inputs.upgrade_selection || 'automatic' }} + + upgrade-release-gate: + name: Upgrade release gate + needs: upgrade-acceptance + if: always() + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Require the selected parallel acceptance workflow + env: + UPGRADE_RESULT: ${{ needs.upgrade-acceptance.result }} + run: test "$UPGRADE_RESULT" = success + + 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 +419,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 +440,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" @@ -356,10 +500,10 @@ jobs: if-no-files-found: ignore atomic-memory-clean-seekdb: - needs: changes + needs: [changes, seekdb-managed-startup] if: ${{ !cancelled() && (needs.changes.result != 'success' || fromJSON(needs.changes.outputs.suites || '{}').database) }} name: Atomic Memory clean migration (SeekDB) - timeout-minutes: 50 + timeout-minutes: 55 runs-on: ubuntu-latest env: POWERCONTEXT_TEST_MIGRATION_SEEKDB: "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..9b7dcf34a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,74 +13,10 @@ concurrency: jobs: release-build: - runs-on: ubuntu-24.04 - timeout-minutes: 20 - 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 package version from Release tag - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} - run: | - python - <<'PY' - import os - import re - import subprocess - - release_tag = os.environ["RELEASE_TAG"] - if release_tag.startswith("powercontext-v"): - release_version = release_tag.removeprefix("powercontext-v") - else: - release_version = release_tag.removeprefix("v") - if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:(?:a|b|rc)[0-9]+)?", release_version): - raise SystemExit( - "Release tag must use X.Y.Z with optional aN, bN, or rcN suffix and " - "optional v or powercontext-v prefix (for example, powercontext-v1.1.0)" - ) - - package_version = subprocess.check_output( - ["uvx", "--from", "hatchling", "--with", "hatch-vcs", "hatchling", "version"], - text=True, - ).strip() - if package_version != release_version: - raise SystemExit( - f"Release tag version {release_version!r} does not match VCS version {package_version!r}" - ) - PY - - - name: Set up website tools - uses: jdx/mise-action@9b200bcecbfd6303e69a4a261b2e09e3757440a7 # v5.2.0 - - - name: Cache website dependencies - uses: ./.github/actions/cache-pnpm - with: - working-directory: website - cache-dependency-glob: website/pnpm-lock.yaml - - - name: Verify synchronized release references and release notes - env: - VERSION: ${{ github.event.release.tag_name }} - run: make release-check - - - name: Build release distributions - run: | - uv build --out-dir dist - uvx --from twine twine check dist/* - - - name: Upload release distributions - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 - with: - name: release-dists - path: dist/* - if-no-files-found: error - retention-days: 30 + uses: ./.github/workflows/upgrade-release.yml + with: + release: true + selection: automatic pypi-publish: runs-on: ubuntu-latest diff --git a/.github/workflows/upgrade-release.yml b/.github/workflows/upgrade-release.yml new file mode 100644 index 0000000000..ea873141a3 --- /dev/null +++ b/.github/workflows/upgrade-release.yml @@ -0,0 +1,220 @@ +name: Parallel installed-release upgrade acceptance + +on: + workflow_call: + inputs: + selection: + description: full, automatic, or seekdb-code; manual Code cases do not gate publication + type: string + default: full + release: + description: Enforce release metadata and expose accepted publication artifacts + type: boolean + default: false + +permissions: + contents: read + +jobs: + prepare: + name: Prepare upgrade artifacts and matrix + runs-on: ubuntu-24.04 + timeout-minutes: 30 + outputs: + matrix: ${{ steps.prepare.outputs.matrix }} + 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 package version from Release tag + if: inputs.release + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + python - <<'PY' + import os + import re + import subprocess + + release_tag = os.environ["RELEASE_TAG"] + if release_tag.startswith("powercontext-v"): + release_version = release_tag.removeprefix("powercontext-v") + else: + release_version = release_tag.removeprefix("v") + if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:(?:a|b|rc)[0-9]+)?", release_version): + raise SystemExit( + "Release tag must use X.Y.Z with optional aN, bN, or rcN suffix and " + "optional v or powercontext-v prefix (for example, powercontext-v1.1.0)" + ) + + package_version = subprocess.check_output( + ["uvx", "--from", "hatchling", "--with", "hatch-vcs", "hatchling", "version"], + text=True, + ).strip() + if package_version != release_version: + raise SystemExit( + f"Release tag version {release_version!r} does not match VCS version {package_version!r}" + ) + PY + + - name: Set up website tools + if: inputs.release + uses: jdx/mise-action@9b200bcecbfd6303e69a4a261b2e09e3757440a7 # v5.2.0 + + - name: Cache website dependencies + if: inputs.release + uses: ./.github/actions/cache-pnpm + with: + working-directory: website + cache-dependency-glob: website/pnpm-lock.yaml + + - name: Verify synchronized release references and release notes + if: inputs.release + env: + VERSION: ${{ github.event.release.tag_name }} + run: make release-check + + - 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 release distributions + run: | + uv build --out-dir dist + uvx --from twine twine check dist/* + + - name: Freeze distributions and resolve the source matrix once + id: prepare + env: + RELEASE: ${{ inputs.release }} + SELECTION: ${{ inputs.selection }} + run: | + args=() + if [ "$RELEASE" = true ]; then + args+=(--release --release-event "$GITHUB_EVENT_PATH") + fi + uv run python scripts/parallel_upgrade_release_gate.py prepare \ + --output "$RUNNER_TEMP/upgrade-prepared" --selection "$SELECTION" "${args[@]}" + + - name: Share the exact prepared inputs + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: upgrade-prepared + path: | + ${{ runner.temp }}/upgrade-prepared/inputs/ + ${{ runner.temp }}/upgrade-prepared/plan.json + ${{ runner.temp }}/upgrade-prepared/sdist-build.log + if-no-files-found: error + overwrite: true + retention-days: 30 + + cases: + name: Upgrade ${{ matrix.source_version }} / ${{ matrix.backend }} / ${{ matrix.configuration }} + needs: prepare + runs-on: ubuntu-24.04 + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }} + 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: + persist-credentials: false + - name: Set up the environment + uses: ./.github/actions/setup-python-env + - name: Download the shared prepared inputs + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 + with: + name: upgrade-prepared + path: ${{ runner.temp }}/upgrade-prepared/ + - name: Start the isolated upgrade OceanBase environment + if: matrix.backend == 'oceanbase' + uses: ./.github/actions/setup-upgrade-oceanbase + - name: Run this configuration exactly once + env: + CASE_INDEX: ${{ matrix.index }} + run: >- + uv run python scripts/parallel_upgrade_release_gate.py case + --prepared "$RUNNER_TEMP/upgrade-prepared" + --index "$CASE_INDEX" + --output "$RUNNER_TEMP/upgrade-case" + - name: Preserve OceanBase diagnostics and stop the owned container + if: always() && matrix.backend == 'oceanbase' + run: | + mkdir -p "$RUNNER_TEMP/upgrade-case" + docker logs upgrade-oceanbase > "$RUNNER_TEMP/upgrade-case/oceanbase.log" 2>&1 || true + docker inspect --format '{{json .State}}' upgrade-oceanbase \ + > "$RUNNER_TEMP/upgrade-case/oceanbase-state.json" 2>&1 || true + docker rm --force upgrade-oceanbase + - name: Preserve this configuration's evidence + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: upgrade-case-${{ matrix.index }} + path: ${{ runner.temp }}/upgrade-case/ + if-no-files-found: error + overwrite: true + retention-days: 30 + + collect: + name: Verify the selected upgrade matrix + needs: [prepare, cases] + if: always() && needs.prepare.result == 'success' + runs-on: ubuntu-24.04 + timeout-minutes: 15 + 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: Download the shared prepared inputs + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 + with: + name: upgrade-prepared + path: ${{ runner.temp }}/upgrade-prepared/ + - name: Download disjoint case reports + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 + with: + pattern: upgrade-case-* + path: ${{ runner.temp }}/upgrade-case-reports/ + - name: Require all selected evidence for the exact prepared distributions + run: >- + uv run python scripts/parallel_upgrade_release_gate.py collect + --prepared "$RUNNER_TEMP/upgrade-prepared" + --reports "$RUNNER_TEMP/upgrade-case-reports" + --output "$RUNNER_TEMP/upgrade-release-evidence" + - name: Require successful provisioning, execution, cleanup and evidence upload + env: + CASE_JOBS_RESULT: ${{ needs.cases.result }} + run: test "$CASE_JOBS_RESULT" = success + - name: Preserve the aggregate verdict + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: upgrade-release-evidence + path: ${{ runner.temp }}/upgrade-release-evidence/ + if-no-files-found: error + overwrite: true + retention-days: 30 + - name: Upload accepted release distributions + if: success() && inputs.release + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: release-dists + path: ${{ runner.temp }}/upgrade-release-evidence/validated-dist/* + if-no-files-found: error + overwrite: true + 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/README.md b/README.md index 14f2c2ff22..a34042070a 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ PowerContext keeps durable project context outside any single chat. A first run Agent input → Source evidence → Memory / Topic Memory → PreparedContext → next session or Agent ``` -PowerContext 1.4.0 includes an interactive setup wizard. You need Python 3.11+, Git, and +PowerContext 1.4.1 includes an interactive setup wizard. You need Python 3.11+, Git, and [uv](https://docs.astral.sh/uv/getting-started/installation/); uv can provision Python when needed. To connect an Agent, also install its CLI. In the wizard, choose Basic memory for explicit saves and recall without model APIs; Full memory requires separate Generation and Embedding API credentials. For a local Server without model APIs, see @@ -178,7 +178,7 @@ Choose the path that matches your goal: ### 1. Install and run the wizard ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" mkdir -p powercontext-config && cd powercontext-config powercontext config init --language en --output .env powercontext config validate --env-file .env @@ -207,7 +207,7 @@ Code, Dashboard login, SSH forwarding, HTTPS prerequisites, and observable accep Codex installation is: ```bash -powercontext setup codex --ref powercontext-v1.4.0 +powercontext setup codex --ref powercontext-v1.4.1 powercontext doctor codex ``` diff --git a/README_CN.md b/README_CN.md index 12c36b0d3a..6baede2fd5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -126,7 +126,7 @@ PowerContext 把可长期复用的项目上下文放在单次对话之外。一 Agent 输入 → Source 证据 → Memory / Topic Memory → PreparedContext → 新会话或新 Agent ``` -PowerContext 1.4.0 包含交互式配置向导。需要 Python 3.11+、Git 和 +PowerContext 1.4.1 包含交互式配置向导。需要 Python 3.11+、Git 和 [uv](https://docs.astral.sh/uv/getting-started/installation/);需要时 uv 可以按需提供 Python。 要接入 Agent,还需要安装对应的 CLI。向导中选择“基础记忆”即可在没有模型 API 的情况下显式保存与召回; 选择“完整记忆能力”则需要独立的 Generation、Embedding API 凭据。只想启动不依赖模型 API 的本地 Server 时, @@ -144,7 +144,7 @@ PowerContext 1.4.0 包含交互式配置向导。需要 Python 3.11+、Git 和 ### 1. 安装并运行配置向导 ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" mkdir -p powercontext-config && cd powercontext-config powercontext config init --language zh --output .env powercontext config validate --env-file .env @@ -168,7 +168,7 @@ powercontext capabilities [完整安装流程](https://powercontext.oceanbase.io/zh/docs/get-started/quickstart/)包含 Codex、Claude Code、Dashboard 登录、SSH 隧道、HTTPS 前提及逐项验收。例如,匹配本版本的 Codex 安装命令是: ```bash -powercontext setup codex --ref powercontext-v1.4.0 +powercontext setup codex --ref powercontext-v1.4.1 powercontext doctor codex ``` diff --git a/README_JP.md b/README_JP.md index b838ac3d0e..02b3072eba 100644 --- a/README_JP.md +++ b/README_JP.md @@ -120,7 +120,7 @@ PowerContext は、長く使うプロジェクトのコンテキストを 1 つ Agent の入力 → Source の証拠 → Memory / Topic Memory → PreparedContext → 新しいセッションまたは Agent ``` -PowerContext 1.4.0 には対話式セットアップウィザードが含まれています。Python 3.11+、Git、[uv](https://docs.astral.sh/uv/getting-started/installation/) が必要です。必要に応じて uv が Python を用意できます。Agent に接続するには、その CLI もインストールしてください。ウィザードでは、モデル API なしで明示的な保存と想起を試す場合は Basic memory、自動抽出を有効にする場合は Full memory を選択してください。Full memory には Generation と Embedding API の個別の認証情報が必要です。モデル API なしでローカル Server だけを起動する場合は[インストールと実行](docs/en/docs/get-started/install-and-run.md)を参照してください。macOS と Linux をサポートし、Windows のサポートは `experimental` です。 +PowerContext 1.4.1 には対話式セットアップウィザードが含まれています。Python 3.11+、Git、[uv](https://docs.astral.sh/uv/getting-started/installation/) が必要です。必要に応じて uv が Python を用意できます。Agent に接続するには、その CLI もインストールしてください。ウィザードでは、モデル API なしで明示的な保存と想起を試す場合は Basic memory、自動抽出を有効にする場合は Full memory を選択してください。Full memory には Generation と Embedding API の個別の認証情報が必要です。モデル API なしでローカル Server だけを起動する場合は[インストールと実行](docs/en/docs/get-started/install-and-run.md)を参照してください。macOS と Linux をサポートし、Windows のサポートは `experimental` です。 目的に合う経路を選択してください: @@ -133,7 +133,7 @@ PowerContext 1.4.0 には対話式セットアップウィザードが含まれ ### 1. ウィザードをインストールして実行する ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" mkdir -p powercontext-config && cd powercontext-config powercontext config init --language en --output .env powercontext config validate --env-file .env @@ -157,7 +157,7 @@ powercontext capabilities Scope を使います。その後、対応するプラグインをインストールして新しい Agent セッションを起動します。[完全な手順](https://powercontext.oceanbase.io/en/docs/get-started/quickstart/)では、Codex と Claude Code、Dashboard へのログイン、SSH 転送、HTTPS の前提条件、確認可能な受け入れチェックを扱っています。対応する Codex のインストールは次のとおりです。 ```bash -powercontext setup codex --ref powercontext-v1.4.0 +powercontext setup codex --ref powercontext-v1.4.1 powercontext doctor codex ``` diff --git a/desktop/README.md b/desktop/README.md index c7976388c4..30c82e1a89 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -150,7 +150,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-98275dbb-v1` and check the connection before selecting a Scope; that record's contract digest matches this build's bundled contract, while `sqlite-atomic-1da99747-v1`, `sqlite-atomic-d9a78567-v1`, `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-dd62723d-v1` and check the connection before selecting a Scope; that record's contract digest matches this build's bundled contract, while `sqlite-atomic-98275dbb-v1`, `sqlite-atomic-1da99747-v1`, `sqlite-atomic-8f10c560-v1`, `sqlite-atomic-d9a78567-v1`, `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). ## Build on macOS diff --git a/desktop/README.zh.md b/desktop/README.zh.md index 2bf5a726b7..e916063635 100644 --- a/desktop/README.zh.md +++ b/desktop/README.zh.md @@ -133,7 +133,7 @@ Windows GitHub Actions 会构建未签名安装包,安装到临时中文路径 从早期预览版升级后,请打开「连接」并选中已保存的连接。与当前 API 契约匹配的配置显示在「此 Desktop 构建可用」中;先核对已测试的 Server 构建与实际部署,再明确选择、保存、检查并使用该连接。新连接默认不选择兼容配置。 -历史配置显示为不可选择的记录。已保存的不匹配或不可用配置仍会显示并说明原因,须明确替换,不会静默升级。对于已资格化的当前 Server 源码,明确选择 `sqlite-atomic-98275dbb-v1` 并在选取范围前检查连接;该记录的契约摘要与本构建的打包契约匹配,而 `sqlite-atomic-1da99747-v1`、`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-dd62723d-v1` 并在选取范围前检查连接;该记录的契约摘要与本构建的打包契约匹配,而 `sqlite-atomic-98275dbb-v1`、`sqlite-atomic-1da99747-v1`、`sqlite-atomic-8f10c560-v1`、`sqlite-atomic-d9a78567-v1`、`sqlite-atomic-caf2a35a-v1`、`sqlite-atomic-683e2db0-v1` 与 `sqlite-atomic-cab054db-v1` 只资格化各自对应 Server 源码的部署,并不匹配打包契约。已记录的 Server 源码和 wheel 身份必须与实际部署一致。独立验证的 `ci-fixtures` 构建仅用于临时验收测试。历史资格证据和剩余要求见[验证说明](VALIDATION.md)。 ## 在 macOS 上构建 diff --git a/desktop/VALIDATION.md b/desktop/VALIDATION.md index 94c5a30e0c..d2c2963222 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-98275dbb-v1` below; ordinary builds require explicitly selecting it and checking the connection before business operations become available. The `sqlite-atomic-1da99747-v1`, `sqlite-atomic-d9a78567-v1`, `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-dd62723d-v1` below; ordinary builds require explicitly selecting it and checking the connection before business operations become available. The `sqlite-atomic-98275dbb-v1`, `sqlite-atomic-1da99747-v1`, `sqlite-atomic-8f10c560-v1`, `sqlite-atomic-d9a78567-v1`, `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. 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,6 +30,32 @@ Selecting a compatibility profile does not attest the remote binary identity. Ex ### Current Server protocol qualification +#### Server dd62723d protocol qualification + +`sqlite-atomic-dd62723d-v1` qualifies Server sources at `dd62723dceabae8b41e293531f0080a819f87c68` +for OpenAPI digest `c12901b88b8e958c9cd63469298b9a04b9d5e2b0b4564d83b50f7ae95542519a`. +The wheel is `powercontext-1.4.1.dev293+gdd62723dc-py3-none-any.whl`, +SHA-256 `030c6a4647f0ee517d15877286c99fef87ed6a5fb29ef6b2d606615b66274441`. All 609 packaged runtime files +match the clean source commit byte for byte; the sorted runtime manifest digest is +`75947c5ecf6ce0053a9632155c2063093732ada18128607aba20eca91229aeeb` (one `wheel/path sha256` line per file, with a final newline). + +On 2026-10-26, macOS 15.7.3 ARM64, Python 3.12.13 and Rust 1.95.0 ran the existing +`desktop/tests/real_server.py` harness against this wheel and independent SQLite Servers. +All four modes passed: anonymous loopback, static Bearer, HTTPS with an explicit CA/base path, +and an injected provider with enforced access. The native `server_probe` was rebuilt from +the unmodified product modules and locked dependencies. The ten recorded operations cover +save/search/exact reads, Scope selection and pagination, identity changes and revocation, +committed writes with lost responses without replay, and Server survival after client exit. + +This is protocol qualification of the recorded source, contract and wheel. It does not qualify +Windows/Intel execution, native credential persistence, installed GUI workflows, signing or release readiness. +Historical records keep their original identities. Reproduce with the existing wheel build, +native `server_probe` build, `fixture_qualification.py --server-commit dd62723dceabae8b41e293531f0080a819f87c68`, +and `real_server.py` commands from this clean commit. + +本记录对应上述精确源码、契约和 wheel;四种真实 SQLite/HTTP 连接模式均已通过。 +它不替代 Windows/Intel、安装后 GUI、原生凭据库或正式发布验收。 + #### Server 98275dbb protocol qualification `sqlite-atomic-98275dbb-v1` qualifies Server sources at @@ -85,6 +111,33 @@ profile and a connection check. 与产品 Cargo 锁文件一致。本次未运行 Tauri 图形界面、Windows/macOS 安装包、原生凭据库、 签名、IME 或屏幕阅读器验收。 +#### Server 8f10c560 protocol qualification + +The historical preview 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 1da99747 protocol qualification `sqlite-atomic-1da99747-v1` qualifies Server sources at @@ -252,6 +305,12 @@ procedure or native UI qualification for the current bundled contract. The `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 historical records have native protocol evidence on Windows x64 (`sqlite-atomic-1da99747-v1`) +and macOS ARM64 (`sqlite-atomic-8f10c560-v1`). Both bind the earlier contract digest +`5878999ce9e239d6642e4da858074b281ed465629432c578b3584eec47cbce6c`; their installed GUI and clipboard +workflows require separate acceptance. The current `sqlite-atomic-98275dbb-v1` record establishes +the Linux protocol scope stated above, not those native application workflows. + 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) on a dedicated loopback Server and SQLite workspace. The UI saved its diff --git a/desktop/src-tauri/src/connections/compatibility.json b/desktop/src-tauri/src/connections/compatibility.json index 0ccf064f97..109fcd6456 100644 --- a/desktop/src-tauri/src/connections/compatibility.json +++ b/desktop/src-tauri/src/connections/compatibility.json @@ -171,6 +171,25 @@ ], "evidence": "desktop/VALIDATION.md#server-1da99747-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" + }, { "id": "sqlite-atomic-98275dbb-v1", "serverCommit": "98275dbbb745cea6d905eb6fdd2eac70197d0127", @@ -189,5 +208,24 @@ "search_memory" ], "evidence": "desktop/VALIDATION.md#server-98275dbb-protocol-qualification" + }, + { + "id": "sqlite-atomic-dd62723d-v1", + "serverCommit": "dd62723dceabae8b41e293531f0080a819f87c68", + "contractSha256": "c12901b88b8e958c9cd63469298b9a04b9d5e2b0b4564d83b50f7ae95542519a", + "artifactSha256": "030c6a4647f0ee517d15877286c99fef87ed6a5fb29ef6b2d606615b66274441", + "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-dd62723d-protocol-qualification" } ] diff --git a/desktop/src-tauri/src/transport/operations.json b/desktop/src-tauri/src/transport/operations.json index 7e427799f7..9170a68dba 100644 --- a/desktop/src-tauri/src/transport/operations.json +++ b/desktop/src-tauri/src/transport/operations.json @@ -1,5 +1,5 @@ { - "contractSha256": "3165f114185aa403b563c936d5fbd0829388628f4a37540d9d1519202a07871c", + "contractSha256": "c12901b88b8e958c9cd63469298b9a04b9d5e2b0b4564d83b50f7ae95542519a", "operations": { "get_liveness": { "method": "GET", 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/desktop/ui/src/generated/operations.ts b/desktop/ui/src/generated/operations.ts index 9cab145c5d..9909a8b309 100644 --- a/desktop/ui/src/generated/operations.ts +++ b/desktop/ui/src/generated/operations.ts @@ -15,7 +15,7 @@ */ // Generated from openapi/powercontext.yaml. Do not edit. -export const contractSha256 = "3165f114185aa403b563c936d5fbd0829388628f4a37540d9d1519202a07871c"; +export const contractSha256 = "c12901b88b8e958c9cd63469298b9a04b9d5e2b0b4564d83b50f7ae95542519a"; export const operations = { "get_liveness": { "method": "GET", diff --git a/docs/en/development/candidate-schema-replay.md b/docs/en/development/candidate-schema-replay.md new file mode 100644 index 0000000000..fab307c77d --- /dev/null +++ b/docs/en/development/candidate-schema-replay.md @@ -0,0 +1,34 @@ +# Candidate migration replay prototype + +The development command replays the frozen `s1300` → `s1400` Candidate conversion on real SQLite, embedded seekDB, or OceanBase. It generates full-database schema inventories and ordered changes from database reflection, then compares the result with the existing registration. It does not edit migration resources, change checksums, or install a new registration format. + +Run it from a development checkout: + +```sh +uv run --locked python scripts/replay_candidate_migration.py sqlite --output /tmp/candidate-sqlite +POWERCONTEXT_TEST_MIGRATION_SEEKDB=1 uv run --locked --extra seekdb python scripts/replay_candidate_migration.py seekdb --output /tmp/candidate-seekdb +``` + +OceanBase uses the existing native fixture: set `POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL` to a dedicated `pc_migration_probe_*` management database, then select `oceanbase`. The fixture creates a unique child database and drops only the database it successfully created. No production database is an appropriate target. Output must be a new directory. The command fails if any of its three backend cases fails or skips; missing native services cannot produce a passing result. + +## Evidence and comparison + +Each case records the backend and engine/binding versions, frozen resource hashes, bundle and revision checksums, source release, operation order, full reflected inventories, their hashes, and changes derived by comparing consecutive inventories. Data-copy operations retain their own position even when their schema delta is empty. The generated changes reconstruct the complete reflected schema, including foreign-key rewrites in tables not named by a rename. + +Native replay records each Candidate operation from the frozen `s1400` SQL registration, including the dynamic foreign-key operation. Actual reflection is compared with the corresponding full registered checkpoint using the existing backend normalization rules. Expected checkpoints are never used to construct generated inventories. SQLite executes its frozen Alembic implementation, records Candidate SQL boundaries, and compares the completed `s1400` schema with the frozen SQLite target. SQLite intermediate inventories are transaction-local observations, not independently durable recovery points. + +All cases start from the frozen full Server 1.3.0 baseline and seed two historical Candidate versions, a current head, and a Profile reference. They verify preserved original columns and retained copies, both cross-table foreign-key rewrites, complete readiness, original maintenance evidence, and a no-write repeated apply. These are synthetic fixtures built from captured baseline resources; they are not a substitute for installed-release acceptance with actual historical wheels. + +## Interruption semantics + +The three scenarios are uninterrupted replay, interruption after the first retained-history copy, and interruption after renaming the versions table. The interruption is raised after the actual database operation returns; the runner closes its connection before recovery begins. + +- SQLite must roll back the schema and Candidate rows to the baseline, then complete the same upgrade with the original backup and maintenance window. +- seekDB and OceanBase must retain committed DDL. An uncommitted retained-copy insert must roll back and be safely replayed. Recovery uses the existing runner and original maintenance evidence; only this controlled connection interruption permits explicit OceanBase acknowledgement. +- The seekDB fixture holds a real engine lease across maintenance connections within each scenario. This validates real native SQL and connection-interruption recovery, not engine crashes or restart-lock recovery. Those remain separate acceptance requirements. + +The `Candidate schema replay` workflow executes all three scenarios independently on all three real backends and preserves the generated evidence. A local SQLite pass is not a native-backend result. + +## Use for future migrations + +This prototype is an equivalence experiment outside the production bundle. For a new migration, prefer deriving reviewed registrations from real database replay, with explicit data transformations and recovery boundaries, over hand-copying intermediate table definitions. Production integration still requires a reviewed artifact format, complete source/backend provenance, immutable historical resources, and equivalent installed-release, history, repeat-apply, backup and interruption/recovery acceptance. The prototype does not claim to generate an installable migration or simplify the existing frozen `s1400` resources. diff --git a/docs/en/development/released-schema-capture.md b/docs/en/development/released-schema-capture.md new file mode 100644 index 0000000000..e801279e3a --- /dev/null +++ b/docs/en/development/released-schema-capture.md @@ -0,0 +1,42 @@ +# Released Server schema capture + +`scripts/capture_release_schema.py` collects evidence for registering a supported +source release. It creates a fresh environment from the official PowerContext +1.4.0 wheel, verifies the wheel against PyPI's SHA256, starts that installed +Server, writes disposable Memory and optional Topic/Code fixtures, then stops the +Server before reflecting its database with the installed release's drivers. +It does not invoke the current migration runtime or modify registered resources. + +```sh +uv run python scripts/capture_release_schema.py sqlite \ + --configuration full-server-no-vector --output /tmp/release-140-capture +``` + +The output directory must not exist. Use only disposable databases. OceanBase +uses the local isolated tenant created by `.github/actions/setup-upgrade-oceanbase` +and requires `PC_UPGRADE_OCEANBASE_URL`; the shared fixture restricts its database +names and capture rejects a nonempty database before starting the Server. + +The independent **Capture released Server schema** workflow runs all three real +backends and four configurations. Vector profiles use controlled embeddings with +dimensions 3 and 4; Code profiles also index a temporary repository and record +the separate Code cache hashes. Native backends require Linux CI. The Linux +process owner cleans up descendants, including detached embedded engines. + +Artifacts contain raw schema definitions, data digests, engine and driver +versions, installed package versions, wheel URL/SHA256, configuration metadata, +Server readiness, and logs. `canonical-schema.json` contains the complete logical +inventory and its digest. Native definitions are parsed offline by the current +checkout's reviewed parser, whose source SHA256 is recorded; the source release's +older parser is not used. SQLite retains exact `sqlite_schema` rows excluding +SQLite-owned objects. No current runtime connects to the source database. +`status=captured` is written only after every phase +passes. Missing or incomplete artifacts are not valid baseline evidence. A +capture proves how the released version creates a database under that profile; +it does not prove target-version admission, migration correctness, or support for +every possible historical configuration. Review and register the collected +evidence separately, then run the complete upgrade acceptance matrix. + +For an offline reproduction, `--wheel` and `--pypi-metadata` accept a cached wheel +and saved official PyPI release JSON. Their digest must agree; the manifest marks +metadata supplied from disk so it cannot be mistaken for a live PyPI lookup. diff --git a/docs/en/development/releasing.md b/docs/en/development/releasing.md index 0300298b75..69b815e2a4 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,73 @@ 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` keeps 1.3.0 as its fixed baseline; explicit `cases` are empty. +The gate requires twelve cases: one source × three backends × vectors enabled/disabled × Code enabled/disabled. +The target is always the actual wheel built by CI. Future target releases keep testing 1.3.0; +intervening releases are not added automatically. The registered 1.4.0 bridge remains implemented but is outside +CI upgrade acceptance, so a passing gate does not certify its upgrade path. Source wheel URLs and SHA-256s are resolved +from public PyPI and recorded with the target artifact. + +The source-specific chains are `s1300 → s1400 → s1401 → s1402` for 1.3.0 and +`r1400 → s1400 → s1401 → s1402` for 1.4.0. They share the target contract while preserving their frozen execution +history and journal lineage for subsequent registered upgrades. Application versions do not identify schema +checkpoints. In particular, 1.4.0 already has current Candidate envelopes: its bridge preserves those records and +creates empty retained tables for the common target shape, without inventing old Candidate history. Adding other +sources requires exact captures, a registered bridge and actual acceptance; the two registered paths do not certify +all releases between them or the target. +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 in all twelve declared cases. 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..be7f2f89b3 100644 --- a/docs/en/docs/get-started/install-and-run.md +++ b/docs/en/docs/get-started/install-and-run.md @@ -1,6 +1,6 @@ --- title: Install and run -description: Install PowerContext 1.4.0 and run the local Server. +description: Install PowerContext 1.4.1 and run the local Server. --- # Install and run @@ -24,12 +24,12 @@ Embedded seekDB is unavailable on Windows. ## Choose a version -These instructions use PowerContext 1.4.0. Keep the package and Agent integration on -the same version: package `1.4.0` and Git tag `powercontext-v1.4.0`. +These instructions use PowerContext 1.4.1. Keep the package and Agent integration on +the same version: package `1.4.1` and Git tag `powercontext-v1.4.1`. ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" -powercontext setup codex --ref powercontext-v1.4.0 +uv tool install --force "powercontext[cli,server]==1.4.1" +powercontext setup codex --ref powercontext-v1.4.1 ``` Check the [capability matrix](../integrations/capabilities.md) for host support and maintenance status. @@ -41,13 +41,13 @@ You need Python 3.11 or newer, Git, and [`uv`](https://docs.astral.sh/uv/) on ma PowerContext from PyPI: ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" ``` For a source installation of the same version: ```bash -uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@powercontext-v1.4.0" +uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@powercontext-v1.4.1" ``` For a machine that only connects to a remote Server, the `cli` extra includes `setup`, `doctor`, and remote content @@ -126,7 +126,7 @@ Embedded seekDB is available on Linux and macOS when a compatible `pylibseekdb` support this embedded backend. Install or replace the tool with the optional seekDB extra: ```bash -uv tool install --force "powercontext[cli,server,seekdb]==1.4.0" +uv tool install --force "powercontext[cli,server,seekdb]==1.4.1" ``` When switching from SQLite, remove `POWERCONTEXT_SERVER_DATABASE_URL` from the Server process environment. An explicit @@ -174,47 +174,49 @@ 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 and 1.4.0 require passing full Server acceptance on SQLite, seekDB and OceanBase. These migration paths are 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.1 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.1" ``` -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 An application that imports the async Client SDK should add it to that application's environment: ```bash -uv add "powercontext[client]==1.4.0" +uv add "powercontext[client]==1.4.1" ``` Use `builtin` for in-process Python composition, `server` for the service, `client` for the Python SDK, or `cli` for diff --git a/docs/en/docs/get-started/manual-start.md b/docs/en/docs/get-started/manual-start.md index 37836fa6c0..3c43eb2b70 100644 --- a/docs/en/docs/get-started/manual-start.md +++ b/docs/en/docs/get-started/manual-start.md @@ -49,7 +49,7 @@ source "$HOME/.local/bin/env" Install the PowerContext CLI and local Server: ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" powercontext --version powercontext --help ``` @@ -179,7 +179,7 @@ Use the Python Client to call the Server and create a Scope manually. Keep the S ```bash cd ~/powercontext-manual POWERCONTEXT_CLIENT_SERVER_URL=http://127.0.0.1:18080 \ -uv run --with "powercontext[client]==1.4.0" python - <<'PY' +uv run --with "powercontext[client]==1.4.1" python - <<'PY' import asyncio import os from powercontext.client import PowerContextClient @@ -214,7 +214,7 @@ Install the Codex plugin matching the Server version and point it to the manuall ```bash powercontext setup codex \ - --ref powercontext-v1.4.0 \ + --ref powercontext-v1.4.1 \ --server-url http://127.0.0.1:18080 powercontext doctor codex ``` diff --git a/docs/en/docs/get-started/quickstart.md b/docs/en/docs/get-started/quickstart.md index 2e6c64ce9b..9f2e0e5909 100644 --- a/docs/en/docs/get-started/quickstart.md +++ b/docs/en/docs/get-started/quickstart.md @@ -6,8 +6,8 @@ description: Configure full memory, connect Codex, and verify Source capture, To # Quick Start Start with installation, discuss a project in Codex, watch its input become Source evidence and an evolving topic, -then recover the decisions in a new session. These instructions use PowerContext 1.4.0, -with the Agent plugin from the matching `powercontext-v1.4.0` tag. +then recover the decisions in a new session. These instructions use PowerContext 1.4.1, +with the Agent plugin from the matching `powercontext-v1.4.1` tag. You need macOS or Linux, Python 3.11+, Git, [uv](https://docs.astral.sh/uv/getting-started/installation/), and an installed Codex CLI. Full memory also needs working Generation and Embedding model APIs: @@ -18,7 +18,7 @@ and recall; that does not enable automatic Topic Memory. ## 1. Install and open the wizard ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" mkdir -p ~/powercontext-demo cd ~/powercontext-demo powercontext config init --language en --output .env @@ -96,7 +96,7 @@ Reload the client settings and install the matching plugin: set -a . ./.env set +a -powercontext setup codex --ref powercontext-v1.4.0 +powercontext setup codex --ref powercontext-v1.4.1 powercontext doctor codex codex ``` diff --git a/docs/en/docs/operate/artifact-processing-migration.md b/docs/en/docs/operate/artifact-processing-migration.md index a80bb95686..127f74335b 100644 --- a/docs/en/docs/operate/artifact-processing-migration.md +++ b/docs/en/docs/operate/artifact-processing-migration.md @@ -9,6 +9,16 @@ 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. +A Server database with an identity marker but no registered revision must complete the full upgrade first. +The domain-only procedure below applies only to unmanaged SDK storage without either ownership marker. +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..ef5fb2ee8b 100644 --- a/docs/en/docs/operate/atomic-memory-migration.md +++ b/docs/en/docs/operate/atomic-memory-migration.md @@ -14,6 +14,17 @@ 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 Server identity marker, this compatibility command delegates +`plan`, `apply` and `verify` to `server db-migrate --bundle server`. The shared framework validates registered +1.3.0 and 1.4.0 sources; the identity marker selects this path without accepting unknown layouts or configurations. 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 +242,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..8a85ea15dd 100644 --- a/docs/en/docs/operate/database-migrations.md +++ b/docs/en/docs/operate/database-migrations.md @@ -12,20 +12,58 @@ 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 framework registers two installed-release sources for the PowerContext 1.4.1 target (stored compatibility IDs shown below): + +- **1.3.0:** `s1300 → s1400 → s1401 → s1402`. +- **1.4.0:** `r1400 → s1400 → s1401 → s1402` through a separate frozen source bridge. + +Application release numbers and schema revisions are distinct. The 1.4.0 release is not the `s1400` checkpoint: +its existing Candidate schema is current, but its Topic attempt storage still needs the registered bridge. +Both source paths preserve existing references and history and reach the same managed target. Their original +resource checksums remain independently verifiable, including after a later registered revision is appended. +Other releases and extra database objects are not inferred to be compatible; they need exact source registration +and acceptance before writes. Older Memory collection conversion is outside these paths. + +Both sources register 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 uses three registered tables; +maintenance preserves existing graph rows and creates the registered tables for a new Code-enabled database. +Native source and profile registrations must pass the installed-release gate on their actual backends. + +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. + +For the 1.3.0 path, 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. + +The 1.4.0 path preserves its already-current Candidate heads, versions and payloads without running the 1.3 +Candidate conversion. It creates the same two retained tables empty to share the target structure; those empty +tables do not represent fabricated 1.3 history. Its preflight validates the current Processing declaration, Dream +payload identities and pending intents. It preserves compatible current tasks rather than applying the 1.3-only +Dream drain rule, and fences old worker leases during the explicit maintenance window. + +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 catalog retains twelve combinations of the 1.3.0 source, three backends, vectors enabled or disabled, and Code enabled or disabled. Temporarily, the two embedded seekDB Code configurations are manual-only and waived from publication; automatic CI and releases require the other ten. Main workflow dispatch with `upgrade_selection=seekdb-code` runs the two deferred configurations. Passing the automatic gate does not certify them. The 1.4.0 bridge remains registered but is outside CI upgrade acceptance; passing this gate does not certify that path. Code cases build an index with each installed source release, 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; each configuration needs passing evidence for the actual target artifact before upgrade support can be claimed. + +`--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 +71,152 @@ 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). + +## Migration names and software versions + +The target release is **PowerContext 1.4.1**. Migration names use +`v___`, with a three-digit sequence within the release. +Sequence `000` identifies a registered historical baseline; upgrade steps start at `001`. + +| Migration name | Software release | Step | Compatible stored revision | Meaning | +| --- | --- | --- | --- | --- | +| `v1_3_0_000` | 1.3.0 | Baseline | `s1300` | Released 1.3.0 Server storage | +| `v1_4_0_000` | 1.4.0 | Baseline | `r1400` | Released 1.4.0 Server storage | +| `v1_4_1_001` | 1.4.1 | 1 | `s1400` | Complete Server storage transition | +| `v1_4_1_002` | 1.4.1 | 2 | `s1401` | Offline Memory history archive | +| `v1_4_1_003` | 1.4.1 | 3 | `s1402` | Portable restore receipts and timestamp adaptation | + +Both registered source paths reach `v1_4_1_003`; only 1.3.0 is selected for CI upgrade acceptance: + +```text +1.3.0: v1_3_0_000 -> v1_4_1_001 -> v1_4_1_002 -> v1_4_1_003 +1.4.0: v1_4_0_000 -> v1_4_1_001 -> v1_4_1_002 -> v1_4_1_003 +``` + +`status` and `plan` show `source_migration`, `target_migration` and `pending_migrations` +before the existing machine fields. `apply` and `verify` show `migration`. Each descriptor +contains `name`, `software_version`, `sequence` and the compatible `revision` ID. For example: + +```json +{"revision":"s1402","name":"v1_4_1_003","software_version":"1.4.1","sequence":3} +``` + +The software version describes the registered database state, not the currently running binary +or the source of an interrupted maintenance window. A release without storage changes may reuse +the same database revision. Names do not prove upgrade acceptance; the full release gate still applies. + +The existing `s1300`, `r1400` and `s1400`–`s1402` IDs remain in version tables, plan IDs, +frozen resources and recovery evidence. Their release-based names are display aliases, not +additional executable revisions or accepted replacement IDs for stamping a database. This keeps +existing plans and original backups usable. Partial prototype bundles keep their own opaque IDs. + +For a new Server migration, use the release-based name directly as its Alembic `revision` and +register its schema, domain steps and recovery checkpoints as usual. `down_revision` must reference +the actual preceding ID (`s1402` when appending directly to the current head), even if that ID has +a display alias. The framework follows this registered chain, not lexical name ordering. Already +registered names and release assignments stay immutable; a version bump must not rename history. + +## 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. Both Processing and Atomic Memory projection maintenance +validate the original migration evidence and recorded revision checksums before accepting work. They inspect the +stored embedding profile and reject vector storage before creating a plan, backup or maintenance journal, even +when embedding settings are omitted from the command configuration. Both Processing and Atomic Memory projection maintenance +validate the original migration evidence and recorded revision checksums before accepting work. They inspect the +stored embedding profile and reject vector storage before creating a plan, backup or maintenance journal, even +when embedding settings are omitted from the command configuration. + +## 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 +224,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 +285,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 +432,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 @@ -274,6 +468,20 @@ In this bundle, SQLite revision `p0003` recreates the tag table and retains its deleting the business table, so it does not create the SQLite history copy. Neither path automatically deletes backup recovery points. +Full Server `db-migrate status`, `plan`, `apply`, and `verify` also inspect the selected evidence directory for +unfinished Processing configuration and Atomic Memory projection maintenance on all three backends. They return +`recovery_required` until the original domain operation completes, even if the schema already matches the target. +Resume that operation with its original configuration and recovery evidence; full migration cannot bypass it. + +Local startup probes an existing SQLite/embedded seekDB maintenance lock without creating or changing its file. +An observed active lock blocks new runtimes with `migration_locked`. SQLite SDK and background admission also +check the default adjacent migration evidence directory: unfinished maintenance, changed recorded revision checksums, +and missing required retained tables block startup. A current-layout database without that journal is still accepted +when its schema and domain checks pass; it has no recorded historical retention obligation. If maintenance uses a +custom evidence directory, run explicit verification with that directory before starting services. Remote OceanBase +coordinator locks are not observable from arbitrary service hosts. Deployment orchestration must keep all writers +stopped and prevent restarts throughout maintenance; these checks do not fence existing clients or future maintenance. + ## Stopping, starting, and upgrading services Personal services provide these commands while retaining registration and settings: @@ -291,15 +499,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 +557,9 @@ 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. +For 1.4.0, it validates current task identities and formats and preserves compatible queued tasks. ## Storage layers and recovery boundaries @@ -341,15 +574,54 @@ 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. Both full Server source chains register 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. + +Online readiness reads Atomic Memory authority and projections from the same snapshot without locking business head rows. Offline projection repair retains its locked state comparison and write-side concurrency protection. + +The 1.4.0 bridge is assembled from immutable shared resources and its `server_sources/1.4.0` overlay. Source +selection uses the complete unversioned inventory or the original lineage in the main maintenance evidence; it +neither adds another version table nor rewrites a completed backup's source revision. Repeated maintenance and +later registered upgrades retain the same lineage. The canonical target remains the ordinary runtime admission +contract. New sources need their own exact baseline and bridge; adding a target revision alone does not register +all intervening application releases. 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 +639,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/candidate-schema-replay.md b/docs/zh/development/candidate-schema-replay.md new file mode 100644 index 0000000000..1f4c1da589 --- /dev/null +++ b/docs/zh/development/candidate-schema-replay.md @@ -0,0 +1,34 @@ +# Candidate 迁移重放原型 + +开发命令在真实 SQLite、嵌入式 seekDB 或 OceanBase 上重放冻结的 `s1300` → `s1400` Candidate 转换,从数据库反射生成全库结构和有序变化,再与现有注册内容比较。它不修改生产迁移资源、checksum 或注册格式。 + +在开发仓库运行: + +```sh +uv run --locked python scripts/replay_candidate_migration.py sqlite --output /tmp/candidate-sqlite +POWERCONTEXT_TEST_MIGRATION_SEEKDB=1 uv run --locked --extra seekdb python scripts/replay_candidate_migration.py seekdb --output /tmp/candidate-seekdb +``` + +OceanBase 复用现有原生测试夹具:将 `POWERCONTEXT_TEST_MIGRATION_OCEANBASE_URL` 指向专用的 `pc_migration_probe_*` 管理库,再选择 `oceanbase`。夹具创建唯一子库,只删除自己成功创建的库。不能指向生产环境。输出目录必须尚不存在;任何场景失败或跳过都会使命令失败,缺少原生服务不能算通过。 + +## 生成证据与比较 + +每个场景记录后端及引擎/绑定版本、冻结资源 hash、bundle 与 revision checksum、源版本、操作顺序、完整反射结构及其摘要,并从相邻真实结构生成变化。数据复制操作即使没有结构变化,也保留独立顺序位置。生成的变化必须能够重建全库结构,包括重命名引起的其他表外键变化。 + +原生后端记录 `s1400` 冻结 SQL 中每个 Candidate 操作,包括动态外键操作;使用现有后端规范化规则逐点比较真实结构与完整注册检查点。生成结构不依赖预期检查点。SQLite 执行冻结的 Alembic 实现,记录 Candidate SQL 边界,并在 `s1400` 完成时比较冻结目标结构。SQLite 中间结构是事务内观察结果,不是可以独立恢复的持久化节点。 + +所有场景从冻结的完整 Server 1.3.0 基线开始,写入两个历史 Candidate 版本、当前 head 和 Profile 引用,验证原字段、保留副本、两处跨表外键变化、完整就绪、原维护证据与重复 apply 不写入。这些是基于已捕获基线构建的合成数据,不代替使用真实历史 wheel 的安装包升级验收。 + +## 中断语义 + +三个场景分别为完整重放、首次历史保留复制后中断、版本表重命名后中断。中断发生在真实数据库操作已经返回之后;恢复前由原迁移框架关闭连接。 + +- SQLite 必须把结构和 Candidate 数据回滚到基线,再沿用原备份和维护窗口完成升级。 +- seekDB/OceanBase 必须保留已提交的 DDL;未提交的保留复制记录应回滚并可安全重放。恢复复用现有 runner 和原维护证据;只有这种受控连接中断可以明确确认 OceanBase 前一操作已结束。 +- seekDB 夹具在单个场景的维护连接之间持有真实引擎租约。它验证原生 SQL 和连接中断恢复,不验证引擎崩溃或重启锁恢复;后两者仍需独立验收。 + +`Candidate schema replay` 工作流在三种真实后端分别运行全部三个场景并保存生成证据。本地 SQLite 通过不能代表原生后端通过。 + +## 后续新增迁移 + +该原型是生产 bundle 外的等价性实验。新增迁移宜优先从真实数据库重放生成待审注册,明确数据转换和恢复边界,减少手工复制中间表定义。接入生产前仍需审查产物格式、完整源版本/后端来源证据,保留历史资源不变,并完成等价的安装包升级、历史数据、重复 apply、备份和中断恢复验收。本原型不生成可安装迁移,也不宣称已经简化现有冻结的 `s1400` 资源。 diff --git a/docs/zh/development/released-schema-capture.md b/docs/zh/development/released-schema-capture.md new file mode 100644 index 0000000000..6972904e15 --- /dev/null +++ b/docs/zh/development/released-schema-capture.md @@ -0,0 +1,35 @@ +# 已发布 Server 的结构采集 + +`scripts/capture_release_schema.py` 为注册受支持的源版本采集真实证据。 +工具校验官方 PyPI PowerContext 1.4.0 wheel 的 SHA256,在独立环境安装它, +启动该版本 Server,写入临时 Memory 及可选 Topic/Code 样本,停服后使用 +该版本的数据库驱动读取结构。工具不会运行当前迁移框架或修改注册资源。 + +```sh +uv run python scripts/capture_release_schema.py sqlite \ + --configuration full-server-no-vector --output /tmp/release-140-capture +``` + +输出目录必须不存在。只能使用临时数据库。OceanBase 使用 +`.github/actions/setup-upgrade-oceanbase` 创建的本地隔离租户,并要求配置 +`PC_UPGRADE_OCEANBASE_URL`。共享测试工具限制可用数据库名,采集程序在启动 +Server 前会拒绝非空数据库。 + +独立的 **Capture released Server schema** 工作流运行三个真实后端和四种 +配置。向量配置使用受控的 3 维/4 维嵌入,Code 配置还会索引临时仓库并记录 +独立 Code 缓存的摘要。原生后端需要 Linux CI;已有进程管理工具负责清理 +包括脱离原会话的嵌入式引擎在内的子进程。 + +产物保留原始 DDL、数据摘要、引擎及驱动版本、安装包版本、wheel 来源和摘要、 +配置元数据、Server 就绪结果与日志。`canonical-schema.json` 保存完整逻辑 +结构及摘要。原生 DDL 由当前已审查的解析器离线处理,并记录解析器文件摘要, +不会使用旧版解析器;SQLite 保留排除 SQLite 内部对象后的原始结构行。 +当前运行时不会连接旧库。所有步骤成功才写入 `status=captured`,缺失或未完成 +的产物不能作为注册证据。 + +采集结果仅证明已发布版本在相应配置下实际生成的结构,不能替代目标版本的 +基线识别、迁移和完整升级验收,也不代表所有历史配置均受支持。需要审查并 +注册采集证据后,再执行完整升级矩阵。 + +离线复现可通过 `--wheel` 和 `--pypi-metadata` 指定缓存 wheel 与保存的官方 +PyPI 发布 JSON,两者摘要必须一致。清单会明确标记元数据来自本地文件。 diff --git a/docs/zh/development/releasing.md b/docs/zh/development/releasing.md index f53da6fb39..db8baa0718 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,58 @@ DSH README 中说明某项能力已包含在 `1.1.0` 的文字同样属于历史 ## 发布前验证 +### 数据库升级支持范围 + +`release/upgrade-support.json` 声明目标版本、来源版本选择策略、后端和配置。`automatic_sources` 继续将 1.3.0 +作为固定基线,显式 `cases` 为空。共要求 12 个组合:一个来源 × 三种后端 × 向量开关 × Code 开关。 +目标始终是本次 CI 构建的实际 wheel;后续目标发布版本继续测试 1.3.0,不自动加入中间发行版。 +1.4.0 桥接实现和登记仍保留,但不纳入 CI 升级验收;门禁通过不能证明该路径已通过验收。来源 wheel 的公开 PyPI 地址和 SHA-256 与目标产物一起记录。 + +1.3.0 的来源链为 `s1300 → s1400 → s1401 → s1402`,1.4.0 为 `r1400 → s1400 → s1401 → s1402`。两者共享目标 +契约,分别保留冻结的执行历史及日志来源链,以便后续已登记版本继续升级。应用版本不等于 schema 检查点;1.4.0 +的 Candidate 已是当前格式,桥接原样保留记录,并创建空保留表以统一目标结构,不虚构旧 Candidate 历史。 +登记其他来源需要精确采集、明确桥接和真实验收;这两条已登记路径不能证明其间及目标之前所有发行版均兼容。 +当前 `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 数据库在全部 12 个登记组合中通过公开命令迁移、 +完整 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..255791b52a 100644 --- a/docs/zh/docs/get-started/install-and-run.md +++ b/docs/zh/docs/get-started/install-and-run.md @@ -1,6 +1,6 @@ --- title: 安装和运行 -description: 安装 PowerContext 1.4.0,并运行本地 Server。 +description: 安装 PowerContext 1.4.1,并运行本地 Server。 --- # 安装和运行 @@ -22,12 +22,12 @@ Windows 的 CLI、Server 和个人服务支持为试验性;各 Agent Host 仍 ## 选择版本 -本页使用 PowerContext 1.4.0。包与 Agent 集成保持版本一致: -Python 包版本为 `1.4.0`,对应 Git tag 为 `powercontext-v1.4.0`。 +本页使用 PowerContext 1.4.1。包与 Agent 集成保持版本一致: +Python 包版本为 `1.4.1`,对应 Git tag 为 `powercontext-v1.4.1`。 ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" -powercontext setup codex --ref powercontext-v1.4.0 +uv tool install --force "powercontext[cli,server]==1.4.1" +powercontext setup codex --ref powercontext-v1.4.1 ``` 宿主支持范围和维护状态见[能力矩阵](../integrations/capabilities.md)。 @@ -39,13 +39,13 @@ powercontext setup codex --ref powercontext-v1.4.0 [`uv`](https://docs.astral.sh/uv/),然后从 PyPI 安装 PowerContext: ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" ``` 如需从源码安装同一版本: ```bash -uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@powercontext-v1.4.0" +uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@powercontext-v1.4.1" ``` 只连接远程 Server 的机器安装 `cli` extra 即可使用 `setup`、`doctor` 和远程内容命令。运行本地 Server 或使用 @@ -115,7 +115,7 @@ powercontext server run --env-file /path/to/powercontext.env 后端。安装或替换工具时加入可选的 seekDB extra: ```bash -uv tool install --force "powercontext[cli,server,seekdb]==1.4.0" +uv tool install --force "powercontext[cli,server,seekdb]==1.4.1" ``` 从 SQLite 切换时,需要从 Server 进程环境中删除 `POWERCONTEXT_SERVER_DATABASE_URL`;seekDB 不接受显式的 @@ -160,42 +160,44 @@ 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 和 1.4.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.1 到独立环境以试用新建部署: ```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.1" ``` -使用其他 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 项目安装角色 如果应用需要导入异步 Client SDK,应把它加入该应用自己的环境: ```bash -uv add "powercontext[client]==1.4.0" +uv add "powercontext[client]==1.4.1" ``` 进程内 Python 组合使用 `builtin`,服务使用 `server`,Python SDK 使用 `client`,基于 Server 的命令行使用 diff --git a/docs/zh/docs/get-started/manual-start.md b/docs/zh/docs/get-started/manual-start.md index e9e9ee1109..769851c407 100644 --- a/docs/zh/docs/get-started/manual-start.md +++ b/docs/zh/docs/get-started/manual-start.md @@ -49,7 +49,7 @@ source "$HOME/.local/bin/env" 安装 PowerContext CLI 和本地 Server: ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" powercontext --version powercontext --help ``` @@ -179,7 +179,7 @@ Scope 是项目上下文数据的隔离空间,用于隔离 Source、Memory、T ```bash cd ~/powercontext-manual POWERCONTEXT_CLIENT_SERVER_URL=http://127.0.0.1:18080 \ -uv run --with "powercontext[client]==1.4.0" python - <<'PY' +uv run --with "powercontext[client]==1.4.1" python - <<'PY' import asyncio import os from powercontext.client import PowerContextClient @@ -214,7 +214,7 @@ export POWERCONTEXT_CODEX_SCOPE_ID=替换为Server返回的真实scope_id ```bash powercontext setup codex \ - --ref powercontext-v1.4.0 \ + --ref powercontext-v1.4.1 \ --server-url http://127.0.0.1:18080 powercontext doctor codex ``` diff --git a/docs/zh/docs/get-started/quickstart.md b/docs/zh/docs/get-started/quickstart.md index ca9b19d65b..070ec95785 100644 --- a/docs/zh/docs/get-started/quickstart.md +++ b/docs/zh/docs/get-started/quickstart.md @@ -6,8 +6,8 @@ description: 通过配置向导安装完整记忆能力,接入 Codex,并验 # 快速开始 本页从安装开始,带你完成一次真实的记忆体验:在 Codex 中讨论项目,看到原始输入进入 Source、主题记忆生成并演进, -再在新会话中找回决策。以下命令使用 PowerContext 1.4.0,Agent 插件使用对应的 -`powercontext-v1.4.0` tag。 +再在新会话中找回决策。以下命令使用 PowerContext 1.4.1,Agent 插件使用对应的 +`powercontext-v1.4.1` tag。 需要 macOS 或 Linux、Python 3.11+、Git、[uv](https://docs.astral.sh/uv/getting-started/installation/) 和已安装的 Codex CLI。 完整记忆还需要可用的 Generation 和 Embedding 模型 API;准备好各自的地址、模型名和 API key。 @@ -17,7 +17,7 @@ Codex 或 Claude 的订阅登录不会自动为 PowerContext Server 提供这些 ## 1. 安装并进入配置向导 ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" +uv tool install --force "powercontext[cli,server]==1.4.1" mkdir -p ~/powercontext-demo cd ~/powercontext-demo powercontext config init --language zh --output .env @@ -92,7 +92,7 @@ POWERCONTEXT_CODEX_SCOPE_ID=替换为返回的scope_id set -a . ./.env set +a -powercontext setup codex --ref powercontext-v1.4.0 +powercontext setup codex --ref powercontext-v1.4.1 powercontext doctor codex codex ``` diff --git a/docs/zh/docs/operate/artifact-processing-migration.md b/docs/zh/docs/operate/artifact-processing-migration.md index 821e90dede..282f922482 100644 --- a/docs/zh/docs/operate/artifact-processing-migration.md +++ b/docs/zh/docs/operate/artifact-processing-migration.md @@ -8,6 +8,15 @@ Artifact Processing Supervisor。全新的空数据库自动初始化。已有 ## 查看计划并执行 +含有 `pc_schema_revision` 的受管数据库会转发到登记的 +[Processing 配置维护](database-migrations.md#processing-配置维护)操作,plan/apply/verify 使用统一迁移响应格式。 +自动化 apply 还须提供审阅后的 `--plan-id`、明确的 `--backup` 策略和 `--yes`, +并遵守 `db-migrate` 的备份声明及 OceanBase 协调选项。先完成尚未结束的整库升级。 +已有 Server 身份标记但尚未登记 revision 的数据库必须先完成整库升级。 +以下领域独立流程仅适用于不含这两种归属标记的未受管 SDK 存储。 +与 `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..1dbcc949a1 100644 --- a/docs/zh/docs/operate/atomic-memory-migration.md +++ b/docs/zh/docs/operate/atomic-memory-migration.md @@ -11,6 +11,16 @@ title: 迁移到 Atomic Memory ## 执行顺序 +数据库已有 `pc_schema_revision` 或 Server 身份标记时,这个兼容命令将 `plan`、`apply`、`verify` +转发到 `server db-migrate --bundle server`,由共享框架校验登记的 1.3.0、1.4.0 来源。身份标记仅用于选择维护入口, +不会放行未知结构或配置。命令返回共享计划和结果格式,并使用相同的维护锁、备份策略及恢复证据。 +需要变更时,先审阅计划,再提供对应的 `--plan-id`、显式 `--backup` 策略、`--yes` 和 `--maintenance-confirmed`。 +OceanBase 还要求[数据库迁移](./database-migrations.md)中说明的固定维护主机和证据目录选项。 +已就绪的数据库保持只读,不重复迁移。Candidate 和 Owner 决策文件只适用于未受管的旧 Memory 转换, +在已登记的 Server 路径上会被拒绝;该路径不因此增加对 1.3.0 之前 Memory 布局的支持。 + +下文描述未受管数据库的领域独立转换: + 先使用新版部署的配置查看只读计划: ```shell @@ -203,6 +213,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..667ace4c98 100644 --- a/docs/zh/docs/operate/database-migrations.md +++ b/docs/zh/docs/operate/database-migrations.md @@ -10,20 +10,193 @@ 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`。 +- **1.4.0:**通过独立冻结的来源桥接,执行 `r1400 → s1400 → s1401 → s1402`。 + +应用发布版本与 schema revision 分别编号。1.4.0 发行包不等于 `s1400` 检查点:其 Candidate 结构已经是当前格式, +但 Topic 尝试状态仍需登记的桥接。两条路径保留既有引用和历史,到达相同的受管目标结构;原有资源摘要分别保留, +后续追加已登记 revision 时仍可验证。不能据此推导其他发布版本或额外数据库对象同样兼容;它们必须先有精确的 +来源登记和验收,才能执行写入。更早的 Memory 集合转换不属于这两条路径。 + +两个来源均登记了三种后端的 Code 开关配置,并为单位归一化的 L2 向量登记独立 profile。SQLite、OceanBase 的 +Code 本地缓存独立于 Server 数据库;嵌入式 seekDB Code 使用三张已登记的表。维护保留已有图数据,为新建的 +Code 启用数据库创建登记的表。原生后端的来源与 profile 登记必须通过对应后端的真实安装包升级门禁。 + +只有结构、领域数据、搜索投影和任务校验全部通过,才返回 `state=ready`、`readiness_scope=server`、`server_ready=true`。发布还必须通过真实安装包在三个后端上的完整验收;实现路径存在不等于某次发布已通过验收,支持状态以安装指南和发布门禁为准。 + +升级前必须停止所有写入者并排空 1.3.0 中 queued/running Dream 任务;新版不能执行旧提示词契约。终态任务保留原始 payload,待处理 Processing intent 的计数保留,旧 worker lease 被 fencing。迁移按登记规则扩展处理能力,保留原有自动处理选择,不根据当前配置覆盖旧所有权。 + +1.3.0 路径将 Candidate 旧表保留为 `pc_retained_dream_candidate_heads` 和 `pc_retained_dream_candidate_versions`,用于迁移当次的复制验证。它们与迁移备份都不是线上写入目标;升级后的正常修改不再与旧快照比较。历史 Artifact 新增的来源时间/操作者字段保持未知,既有 Source 引用保持不变。投影重建和数据校验会扫描相关表;请为停机时间、备份与保留副本安排容量。 + +1.4.0 路径原样保留已是当前格式的 Candidate heads、versions 和 payload,不运行 1.3 的 Candidate 转换。 +为统一目标结构,创建相同的两张空保留表;空表不代表虚构的 1.3 历史。写入前检查当前 Processing 声明、Dream +payload 身份及待处理 intent。兼容的当前任务原样保留,不套用仅针对 1.3 Dream 的排空规则;显式维护窗口内使 +旧 worker lease 失效。 + +`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 均拒绝隐式扩展。测试目录保留 1.3.0 来源、三个后端、向量开关及 Code 开关组成的 12 种组合。当前嵌入式 seekDB 的两个 Code 配置暂时仅手动运行,发布豁免这两项;自动 CI 和发布要求其余 10 项通过。在 Main 工作流手动选择 `upgrade_selection=seekdb-code` 可执行这两项。自动门禁通过不能证明这两个配置已通过升级验收。1.4.0 桥接仍保留登记,但不纳入 CI 升级验收;门禁通过不能证明该路径已通过验收。Code 配置用各自实际安装的来源版本创建索引,保留缓存文件与嵌入式图数据,并在升级后查询原指纹和符号。向量配置保留非空的历史 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)。 + +## 迁移编号与软件版本 + +目标发布版本为 **PowerContext 1.4.1**。迁移名称采用 +`v<主版本>_<次版本>_<补丁版本>_<序号>`,序号在同一版本内使用三位数字。 +`000` 表示已登记的历史基线,升级步骤从 `001` 开始。 + +| 迁移名称 | 所属软件版本 | 步骤 | 兼容保留的 revision | 含义 | +| --- | --- | --- | --- | --- | +| `v1_3_0_000` | 1.3.0 | 基线 | `s1300` | 已发布的 1.3.0 Server 存储 | +| `v1_4_0_000` | 1.4.0 | 基线 | `r1400` | 已发布的 1.4.0 Server 存储 | +| `v1_4_1_001` | 1.4.1 | 第一步 | `s1400` | 完整 Server 存储转换 | +| `v1_4_1_002` | 1.4.1 | 第二步 | `s1401` | 离线 Memory 历史归档 | +| `v1_4_1_003` | 1.4.1 | 第三步 | `s1402` | 可移植恢复回执与时间戳适配 | + +两条已登记来源路径都升级到 `v1_4_1_003`;CI 升级验收只选择 1.3.0: + +```text +1.3.0: v1_3_0_000 -> v1_4_1_001 -> v1_4_1_002 -> v1_4_1_003 +1.4.0: v1_4_0_000 -> v1_4_1_001 -> v1_4_1_002 -> v1_4_1_003 +``` + +`status` 和 `plan` 在原有机器字段之前显示 `source_migration`、`target_migration`、 +`pending_migrations`;`apply` 和 `verify` 显示 `migration`。每项包含 `name`、 +`software_version`、`sequence` 和兼容保留的 `revision` ID。例如: + +```json +{"revision":"s1402","name":"v1_4_1_003","software_version":"1.4.1","sequence":3} +``` + +这里的软件版本描述已登记的数据库状态,不代表当前运行的软件包版本,也不代表中断维护窗口最初的来源。 +没有存储变化的软件发布可以复用同一数据库 revision。命名不代表升级已经通过验收,完整发布门禁仍然生效。 + +现有 `s1300`、`r1400`、`s1400`–`s1402` 继续保存在版本表、计划标识、冻结资源和恢复证据中。 +新名称是它们的展示别名,不是额外的可执行 revision,也不能作为替代 ID 手工写入版本表。 +这样可以继续使用已有计划和原始备份。局部原型 bundle 保留自己的独立编号。 + +后续新增 Server 迁移直接使用该命名作为 Alembic `revision`,并正常登记结构、领域步骤和恢复检查点。 +`down_revision` 必须引用前一步的实际 ID;若直接追加在当前 head 之后,仍引用 `s1402`,即使它已有展示别名。 +框架按照登记的链执行,不按名称字典序推算。已登记的名称和所属发布版本保持不变,版本升级不能重命名历史。 + +## 初始化与维护职责 + +下表列出结构变更与投影修复的执行入口。含有 `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 校验;配置已匹配时只读返回。 +Processing 和 Atomic Memory 投影维护均在接受操作前校验原迁移证据和已记录 revision 的校验和。 +两者还会读取数据库保存的 embedding profile;即使命令配置遗漏 embedding,也会在生成计划、备份或维护记录前拒绝向量存储。 +Processing 和 Atomic Memory 投影维护均在接受操作前校验原迁移证据和已记录 revision 的校验和。 +两者还会读取数据库保存的 embedding profile;即使命令配置遗漏 embedding,也会在生成计划、备份或维护记录前拒绝向量存储。 + +## 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 +243,8 @@ powercontext server db-migrate verify --env-file deployment.env 计划绑定目标身份、源/目标 revision、实际结构摘要、冻结脚本摘要、配置摘要、备份策略、服务范围和迁移协调范围。配置与凭据值不输出到诊断中;预期错误返回稳定 JSON 类别和非零退出码。 +配置绑定包含目标部署的规范 Processing 归属模式、能力和自动绑定声明。改变这些声明后必须重新生成计划,不能使用旧计划执行。计划绑定本身不验证或迁移历史 Processing 状态;完整 Server bundle 另由已登记的领域步骤校验持久化声明与待处理 intent。 + 在支持范围内,终端用户可直接执行 `apply`,默认 PC 自动备份。命令展示计划、备份方法、维护影响及停写责任,确认一次后执行,不逐条 SQL 询问: ```bash @@ -165,6 +340,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` 起始版本 | @@ -178,6 +358,17 @@ powercontext server db-migrate verify --env-file oceanbase.env \ 本 bundle 的 SQLite `p0003` 会重建标签表,将旧结构和数据保存在 `pc_retained_p0002_artifact_tags`。seekdb/OceanBase 的 `p0003` 原地修改 CHECK,不替换或删除业务表,因此不创建 SQLite 的历史副本。两条路径均不自动删除备份恢复点。 +完整 Server 的 `db-migrate status`、`plan`、`apply`、`verify` 在三个后端都会检查所选证据目录中的 +Processing 配置维护和 Atomic Memory 投影维护记录。即使数据库结构已符合目标,存在未完成记录时仍返回 +`recovery_required`。应使用原配置和恢复证据继续原维护操作;完整迁移不能绕过这项检查。 + +本地启动只读探测已存在的 SQLite/嵌入式 seekDB 维护锁,不创建或修改锁文件。发现活动锁时, +新运行实例返回 `migration_locked`。SQLite SDK 和后台启动还会检查默认相邻迁移证据目录:未完成的维护、 +已登记 revision 校验和变化、必留历史表缺失都会阻止启动。没有该记录的当前结构数据库,只要结构和领域校验通过, +仍可启动,不额外要求其具备未登记的历史保留对象。使用自定义证据目录时,启动服务前必须指定该目录执行显式验证。 +任意服务主机无法观察远端 OceanBase 协调主机上的锁。部署编排仍须在整个维护期间停止全部写入并防止重启; +这些检查不会隔离已经运行的客户端,也不能阻止未来发起的维护。 + ## 服务停止、启动与升级 本机个人服务提供以下命令,保留注册和配置: @@ -194,9 +385,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 基线,确认升级后仍保持停止并可显式启动;同时验证数据库已就绪时运行中服务的维护恢复。 +每个操作系统须分别执行原生测试。冻结基线夹具与实际历史版本安装并写入数据库后的升级验收是不同的证据。 -`--manage-service` 的范围始终只是已确认的本机服务,不能代替 `--maintenance-confirmed` 对其他写入者和共享库节点的声明。阶段 A 的有限 bundle 未完成完整 Server readiness 验收;需要持久化变更时,`--manage-service` 返回 `service_unsupported`,不能据此自动切换生产服务。 +套件还在独立环境中安装真实 1.3.0 wheel,通过原生管理器启动,并用 HTTP 创建 Scope。运行中和已停止两种服务状态 +均通过公开维护命令升级,验证备份与旧数据保留,并确认注册切换到目标解释器且保持预期运行状态。 +CI 安装时校验来源 wheel 的 SHA-256,并设置 `POWERCONTEXT_NATIVE_UPGRADE_SOURCE_PYTHON`; +本地运行原生测试时也需提供该已安装来源版本的解释器。这些用例覆盖 SQLite 服务切换, +后端崩溃恢复与完整安装版本领域验收仍需分别验证。 + +失败场景覆盖备份存储不可用、目标服务启动时真实的端口冲突,以及替换服务注册文件时注入的 `ENOSPC`。 +启动或切换失败后,保留已停止的服务和恢复记录;数据库验证已经完成时,通过显式启动服务恢复,无需再次执行迁移 apply。 手动管理时,先停旧服务,在已支持的完整迁移成功后确认服务启动定义指向新版,再启动。前台部署则退出旧进程,使用新版环境的现有入口: @@ -210,15 +423,36 @@ 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 中 queued/running 的旧提示词契约任务并保留终态 payload;对 1.4.0 校验当前任务的身份与格式,保留兼容的排队任务。局部 Artifact 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 仅在结构满足后运行。两条完整 Server 来源链均用 `mysql_state_layouts` 登记逐语句的精确结构变化和可恢复前缀。领域步骤附着在共享 revision 上,使用同一连接、锁和恢复点;校验成功后才推进版本。只读 live 校验与迁移当次的保留副本校验分离。向量配置仍需独立登记。 + +在线就绪校验在同一快照中读取 Atomic Memory 权威记录与投影,不锁定业务 head 行。离线投影修复仍保留加锁状态比较和写入并发保护。 -Repository 仅在结构满足后运行。完整业务的全文/向量索引版本管理及受控重建、旧任务 readiness 尚未包含在本验收 bundle 中。 +1.4.0 桥接由不可改写的共享资源和 `server_sources/1.4.0` 来源覆盖层组装。执行器根据完整未登记版本的数据库 +结构,或主维护日志中原有的来源链选择路径;不新增版本表,也不改写已完成备份的来源 revision。重复维护和 +后续已登记版本升级仍使用同一来源链,普通运行时准入继续使用规范目标结构。新增来源需要自己的精确基线及桥接; +仅新增目标 revision 并不会登记中间所有应用发布版本。 单版本表不提供通用 run ID 续跑。中断后先用只读命令检查真实结构和数据;含混状态返回 `recovery_required`,不盲目 stamp、不用部分状态的备份替换原恢复点。库外摘要在 DDL 前记录执行及备份信息;重试保留原维护窗口和恢复点。在恢复条件允许时可显式重新确认 manual/skip 策略,但不绕过结构验证,也不替代必需的进度证据。 @@ -228,4 +462,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/integrations/dify/plugin/powercontext_dify/contract.json b/integrations/dify/plugin/powercontext_dify/contract.json index 662443e44d..2291f8fcfa 100644 --- a/integrations/dify/plugin/powercontext_dify/contract.json +++ b/integrations/dify/plugin/powercontext_dify/contract.json @@ -1,5 +1,5 @@ { - "api_version": "1.4.0", + "api_version": "1.4.1", "operations": { "list_atomic_memories": { "method": "POST", diff --git a/integrations/dsh/plugins/powercontext/README.md b/integrations/dsh/plugins/powercontext/README.md index 1d2dfcdd6d..548c224a05 100644 --- a/integrations/dsh/plugins/powercontext/README.md +++ b/integrations/dsh/plugins/powercontext/README.md @@ -5,8 +5,8 @@ This plugin is a thin DeepSeek Harness integration for a running PowerContext Se Install the released Server and plugin together: ```bash -uv tool install --force "powercontext[cli,server]==1.4.0" -powercontext setup dsh --source oceanbase/powercontext --ref powercontext-v1.4.0 +uv tool install --force "powercontext[cli,server]==1.4.1" +powercontext setup dsh --source oceanbase/powercontext --ref powercontext-v1.4.1 ``` `setup dsh` calls `dsh plugin --profile web add`. The model-facing PowerContext surface is registered through DSH's native `@deepseek-ai/dsh-mcp-client`. diff --git a/openapi/powercontext.yaml b/openapi/powercontext.yaml index 915b40be56..f08902602e 100644 --- a/openapi/powercontext.yaml +++ b/openapi/powercontext.yaml @@ -16,7 +16,7 @@ openapi: 3.0.3 info: title: PowerContext API description: Remote PowerContext transport. Runtime behavior is reported by /v1/capabilities. - version: 1.4.0 + version: 1.4.1 x-powercontext-feature-contracts: access.principal: {major: 1, minor: 0} scope.selection: {major: 1, minor: 0} diff --git a/release/upgrade-support.json b/release/upgrade-support.json new file mode 100644 index 0000000000..c0afba9bf2 --- /dev/null +++ b/release/upgrade-support.json @@ -0,0 +1,76 @@ +{ + "format": 2, + "target_version": "1.4.1", + "mode": "upgrade-required", + "schema_review": { + "persistence_sha256": "9b67d639cf57b3ae7cf37dcd1845f50561af36bb19edd9a7b979408f31e9a6de", + "rationale": "The reusable Server migration framework registers actual PowerContext 1.3.0 through s1300 -> s1400 -> s1401 -> s1402 and actual 1.4.0 through the separate immutable r1400 -> s1400 -> s1401 -> s1402 source bridge. Original 1.3 resources and checksums remain byte-identical. Real 1.4.0 SQLite, embedded seekDB and OceanBase structures were captured from the official wheel in all twelve vector/Code configurations; provenance and exact source layouts are packaged with the bridge. The 1.4 bridge preserves current Candidate, Artifact and task data, adds the missing Topic budget/block structure, revokes old lease fences and joins the existing archive/portable-storage path. Empty retained tables align the final schema without inventing 1.3 Candidate history. Source histories retain their identity through subsequent upgrades and recovery; shared head names never authorize adopting unknown layouts. Vector profiles remain bound to historical model, profile ID, dimension, unit normalization and L2 distance, with no provider calls during maintenance. Complete schema, domain, projection, profile and retained-history checks precede startup and partial maintenance. Local SQLite and embedded seekDB startup reject an observable active maintenance lock through a read-only probe. SQLite runtime admission validates the default adjacent journal, rejecting unfinished full or partial maintenance, changed recorded revision checksums and missing required retained tables. Absent journals do not invent retention obligations; custom evidence directories require explicit operator verification and remote coordinator locks remain subject to deployment orchestration. Native read-only readiness uses a consistent snapshot and does not acquire Atomic Memory head locks; normal business transaction isolation is unchanged. Offline projection repairs retain the original locked state comparison and write-side CAS protection. Target locks, backup choices, original recovery evidence and explicit remote acknowledgments remain enforced. The installed-artifact catalog retains twelve 1.3.0 source/backend/vector/Code combinations. Embedded seekDB full-server-code and full-server-vector-code are temporarily manual-only due to native DDL waits with the available engine. Main and publication require the other ten; no manual result is needed for publication. The two deferred configurations remain unverified and are excluded from generated supported upgrade declarations. Reports retain their identities and explicitly record incomplete full-matrix acceptance. The registered 1.4.0 bridge remains implemented but is outside the CI upgrade acceptance matrix; passing this gate does not certify that path. The gate preserves source-specific Processing counters and isolates OceanBase source databases. Any failed, skipped or missing automatic case rejects publication. The policy remains upgrade-required until the exact target artifact passes the required automatic matrix and the reviewed support declaration is updated. Captured source structure, local tests and checkpoint replay are distinct from installed-release acceptance, native interruption/observer recovery and backup restoration. The Candidate generation prototype observes actual SQLite/seekDB/OceanBase migration checkpoints outside the production bundle; it does not replace frozen registrations or weaken recovery checks. Existing target schema and data formats, including batched persistence interfaces, remain governed by the shared registered path. The synchronized master adds blank-text validation at Source creation and managed worker logging. It changes neither registered table layout nor migration data encodings; existing stored Source values are not rewritten. The source bridges and frozen history remain unchanged. Compatibility commands identify Server-owned storage before opening a writable legacy profile and delegate source admission to the shared framework, including actual 1.4.0 and rejection of unknown layouts. Partial Processing and Atomic Memory projection maintenance verify the original recorded revision checksums; native admission also validates typed journal identity and its completed position against the database revision. Persisted vector profiles reject unsupported partial maintenance before plans, backups or operation evidence, including when the supplied configuration omits embeddings. These are admission-only changes; frozen migration resources, registered layouts, historical checksums and data transformations remain unchanged. PowerContext 1.4.1 assigns release-based presentation names v1_4_1_001 through v1_4_1_003 to the existing shared upgrade steps; the 1.3.0 and 1.4.0 source baselines remain independently registered. Existing revision IDs, bundle and revision checksums, accepted plan identities and journal schemas are unchanged. New Server revisions use software-version-plus-sequence names directly; software releases without storage changes may reuse the same database revision. Naming does not grant acceptance or replace source registration. Full Server status, plan, apply and verify reject unfinished Processing configuration and Atomic Memory projection journals through the same typed admission check used by partial maintenance and SQLite startup. The owning domain runner can still resume its own journal. This only changes admission; frozen revisions, journal formats, database layouts and data transformations remain 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/capture_release_schema.py b/scripts/capture_release_schema.py new file mode 100644 index 0000000000..5c8f5bc9cc --- /dev/null +++ b/scripts/capture_release_schema.py @@ -0,0 +1,298 @@ +# 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. + +"""Capture a real installed PowerContext 1.4.0 Server baseline without migrating it.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import sys +from contextlib import contextmanager +from datetime import UTC, datetime +from inspect import unwrap +from pathlib import Path +from urllib.parse import urlsplit +from urllib.request import urlopen + +ROOT = Path(__file__).resolve().parents[1] +VERSION = "1.4.0" +PROFILES = ("full-server-no-vector", "full-server-code", "full-server-vector", "full-server-vector-code") + + +def save(path, value): + path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def source_wheel(directory, metadata_path=None, wheel_path=None): + url = f"https://pypi.org/pypi/powercontext/{VERSION}/json" + if metadata_path is None: + with urlopen(url, timeout=60) as response: # noqa: S310 -- fixed official PyPI HTTPS URL + metadata = json.load(response) + else: + metadata = json.loads(metadata_path.read_text()) + candidates = [ + entry for entry in metadata["urls"] if entry["filename"] == f"powercontext-{VERSION}-py3-none-any.whl" + ] + if metadata["info"]["version"] != VERSION or len(candidates) != 1: + raise ValueError("Expected the official PowerContext 1.4.0 wheel") # noqa: TRY003 + entry = candidates[0] + if urlsplit(entry["url"]).scheme != "https" or urlsplit(entry["url"]).hostname != "files.pythonhosted.org": + raise ValueError("Expected a wheel hosted by PyPI") # noqa: TRY003 + destination = directory / entry["filename"] + if wheel_path is None: + with urlopen(entry["url"], timeout=120) as response: # noqa: S310 -- validated official PyPI HTTPS host + destination.write_bytes(response.read()) + else: + destination.write_bytes(wheel_path.read_bytes()) + checksum = hashlib.sha256(destination.read_bytes()).hexdigest() + if checksum != entry["digests"]["sha256"]: + raise ValueError("Source wheel does not match the PyPI SHA256") # noqa: TRY003 + return destination, { + "name": "powercontext", + "version": VERSION, + "filename": entry["filename"], + "sha256": checksum, + "url": entry["url"], + "uploaded_at": entry["upload_time_iso_8601"], + "metadata_url": url, + "metadata_origin": "live-pypi" if metadata_path is None else "provided-pypi-json", + } + + +def canonical_schema(schema, backend): + # Parse already-captured text with the reviewed checkout parser. Never use + # the released wheel's older parser or initialize a current database profile. + sys.path.insert(0, str(ROOT / "src")) + from powercontext.builtin.persistence.migrations import mysql_schema + from powercontext.builtin.persistence.migrations.models import digest + + parser = Path(mysql_schema.__file__).resolve() + if not parser.is_relative_to(ROOT / "src"): + raise RuntimeError("Schema canonicalization must use the checked-out parser") # noqa: TRY003 + if backend == "sqlite": + inventory = [row for row in schema["schema_rows"] if not row[1].startswith("sqlite_")] + else: + tables = {} + for name, definition in schema["tables"].items(): + reflected, table = mysql_schema._parse_table(definition, database=schema["database"]) + if reflected != name: + raise ValueError("Reflected table identity differs from raw evidence") # noqa: TRY003 + tables[name] = table + inventory = { + "tables": tables, + "views": sorted(schema["views"]), + "triggers": sorted(schema["trigger_rows"], key=lambda row: row["Trigger"]), + } + return { + "inventory": inventory, + "sha256": digest(inventory), + "canonicalizer": "exact sqlite_schema rows" if backend == "sqlite" else "current mysql_schema._parse_table", + "parser_sha256": None if backend == "sqlite" else hashlib.sha256(parser.read_bytes()).hexdigest(), + } + + +def capture(args): + # This module contains only subprocess/HTTP fixture helpers. Source commands + # use -I and a separate installed-release environment, never checkout runtime. + sys.path.insert(0, str(ROOT)) + sys.path.insert(0, str(ROOT / "scripts")) + from upgrade_release_gate import case_workspace + + from tests.release_upgrades.test_full_server import ( + command, + environment_for, + historical_code, + model_gateway, + post, + server, + snapshot, + ) + + output = args.output.resolve() + output.mkdir(parents=True, exist_ok=False) + manifest = { + "format": "installed-server-schema-capture-v1", + "status": "incomplete", + "captured_at": datetime.now(UTC).isoformat(), + "checkout_commit": os.environ.get("GITHUB_SHA"), + "workflow_run": os.environ.get("GITHUB_RUN_ID"), + "backend": args.backend, + "configuration": args.configuration, + "source_version": VERSION, + "capture_kind": "real installed Server startup and HTTP writes, followed by read-only reflection", + } + save(output / "manifest.json", manifest) + # Keep database sockets short and database files outside uploaded evidence. + with case_workspace(args.backend, output) as work: + environment = { + key: value + for key, value in os.environ.items() + if not key.startswith(("POWERCONTEXT_", "PYTHON", "UV_", "PIP_")) and key != "VIRTUAL_ENV" + } + wheel, provenance = source_wheel(work, args.pypi_metadata, args.wheel) + manifest["source_wheel"] = provenance + save(output / "manifest.json", manifest) + command( + ["uv", "venv", "--python", sys.executable, str(work / "source")], environment, work, output / "venv.log" + ) + python = work / "source" / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + extras = "server,cli,code" + (",seekdb" if args.backend == "seekdb" else "") + command( + [ + "uv", + "pip", + "install", + "--python", + str(python), + "--no-config", + "--default-index", + "https://pypi.org/simple", + f"{wheel}[{extras}]", + ], + environment, + work, + output / "install.log", + ) + installed = json.loads( + command( + [ + str(python), + "-I", + "-c", + "import json,platform,powercontext; from importlib.metadata import distributions; " + "print(json.dumps({'python':platform.python_version(),'module_file':powercontext.__file__," + "'packages':{d.metadata['Name']:d.version for d in distributions()}}))", + ], + environment, + work, + output / "installed.log", + ) + ) + if installed["packages"].get("powercontext") != VERSION or not Path(installed["module_file"]).is_relative_to( + work + ): + raise RuntimeError("The source interpreter did not import the isolated 1.4.0 installation") # noqa: TRY003 + manifest["installed"] = installed + manifest["profile"] = { + "code_enabled": args.configuration.endswith("-code"), + "vector_dimension": {"full-server-vector": 3, "full-server-vector-code": 4}.get(args.configuration, 0), + "embedding_normalization": "unit" + if "-vector" in args.configuration and not args.configuration.endswith("no-vector") + else None, + } + save(output / "manifest.json", manifest) + gateway_context = contextmanager(unwrap(model_gateway)) + with gateway_context() as gateway: + case = { + "source": python, + "source_version": VERSION, + "backend": args.backend, + "configuration": args.configuration, + "work": work, + "report": output, + "model": gateway, + **manifest["profile"], + } + env = environment_for(case) + manifest["embedding_profile"] = { + key.removeprefix("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_").lower(): value + for key, value in env.items() + if key.startswith("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_") and not key.endswith("BASE_URL") + } + save(output / "manifest.json", manifest) + if args.backend == "oceanbase" and snapshot(case, "source", "empty")["tables"]: + raise RuntimeError("Refusing to initialize a nonempty OceanBase database") # noqa: TRY003 + with server(python, env, work, output / "source-server.log") as client: + readiness = client.get("/health/ready") + readiness.raise_for_status() + save(output / "source-readiness.json", readiness.json()) + scope = post( + client, + "/v1/scopes", + { + "title": "Release schema capture", + "summary": "Disposable fixture", + "idempotency_key": "capture-scope", + }, + )["scope_id"] + post( + client, + "/v1/memory/remember", + {"scope_id": scope, "kind": "fact", "text": "Schema capture evidence."}, + ) + if case["vector_dimension"]: + post( + client, + f"/v1/scopes/{scope}/artifacts", + { + "family": "topic-memory", + "content": { + "title": "Capture", + "summary": "Vector fixture", + "detail": "# Capture\nProfile evidence.", + }, + }, + ) + if case["code_enabled"]: + manifest["code"] = historical_code(case, scope) + schema = snapshot(case, "source", "source") + canonical = canonical_schema(schema, args.backend) + save(output / "canonical-schema.json", canonical) + if ( + not {"pc_scopes", "pc_artifacts", "pc_candidate_heads", "pc_candidate_versions"} + <= schema["tables"].keys() + ): + raise RuntimeError("The released Server did not initialize the expected 1.4.0 schema") # noqa: TRY003 + expected_vectors = { + "pc_atomic_memory_current", + "pc_topic_memory_vector_topics", + "pc_topic_memory_vector_chunks", + } + if case["vector_dimension"] and ( + set(schema["vectors"]) != expected_vectors + or not all(item["rows"] for item in schema["vectors"].values()) + ): + raise RuntimeError("The vector profile did not persist its fixture embeddings") # noqa: TRY003 + manifest.update({ + "status": "captured", + "schema_file": "source-schema.json", + "canonical_file": "canonical-schema.json", + "canonical_sha256": canonical["sha256"], + "schema_sha256": hashlib.sha256((output / "source-schema.json").read_bytes()).hexdigest(), + "engine_version": schema["engine_version"], + "driver_version": schema["driver_version"], + "embedding_requests": gateway["embeddings"], + "server_stopped_before_reflection": True, + "target_migration_executed": False, + }) + save(output / "manifest.json", manifest) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("backend", choices=("sqlite", "seekdb", "oceanbase")) + parser.add_argument("--configuration", choices=PROFILES, default=PROFILES[0]) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--wheel", type=Path, help="Optional cached wheel, verified against PyPI metadata") + parser.add_argument( + "--pypi-metadata", type=Path, help="Optional saved official release JSON for offline reproduction" + ) + capture(parser.parse_args()) + + +if __name__ == "__main__": + main() 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/parallel_upgrade_release_gate.py b/scripts/parallel_upgrade_release_gate.py new file mode 100644 index 0000000000..6e6e3eb9ec --- /dev/null +++ b/scripts/parallel_upgrade_release_gate.py @@ -0,0 +1,288 @@ +# 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. + +"""Prepare once, execute disjoint upgrade cases, and collect the complete evidence.""" + +from __future__ import annotations + +import argparse +import json +import os +import shutil +import subprocess +import tarfile +import uuid +import zipfile +from pathlib import Path +from typing import Any +from xml.etree import ElementTree + +import upgrade_release_gate as gate +from run_upgrade_release_gate import stage_distributions +from upgrade_release_matrix import resolve_matrix + + +def write_json(path: Path, value: Any) -> None: + path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +SELECTIONS = ("full", "automatic", "seekdb-code") + + +def selected_indexes(cases: list[dict[str, Any]], selection: str, *, release: bool) -> list[int]: + gate.require(selection in SELECTIONS, "Unknown upgrade selection") + gate.require(not release or selection != "seekdb-code", "Manual-only acceptance cannot authorize publication") + manual = {index for index, case in enumerate(cases) if gate.manual_upgrade_case(case)} + selected = [ + index for index in range(len(cases)) if selection == "full" or (index in manual) == (selection == "seekdb-code") + ] + gate.require(selected, "No cases match the upgrade selection") + return selected + + +def prepare( + root: Path, dist: Path, output: Path, *, release: bool, release_notes: str | None, selection: str = "full" +) -> dict[str, Any]: + """Freeze the distributions and source discovery before scheduling any cases.""" + gate.require(not release or selection != "seekdb-code", "Manual-only acceptance cannot authorize publication") + output.mkdir(parents=True, exist_ok=False) + wheel, files = stage_distributions(dist, output / "inputs", release=release) + policy = gate.validate_target(root, wheel, release=release, release_notes=release_notes) + matrix = resolve_matrix(policy, gate.wheel_version(wheel)) + gate.require(matrix["required_cases"], "No required upgrade acceptance cases") + plan = { + "format": 1, + "execution_id": uuid.uuid4().hex, + "workflow_run_id": os.environ.get("GITHUB_RUN_ID"), + "source_sha": os.environ.get("GITHUB_SHA"), + "policy_sha256": gate.sha256(root / gate.MANIFEST), + "target_version": gate.wheel_version(wheel), + "target_wheel": wheel.name, + "artifacts": {path.name: gate.sha256(path) for path in files}, + "mode": policy["mode"], + "release": release, + "release_notes": release_notes, + "resolved_matrix": matrix, + "selection": selection, + } + selected = selected_indexes(matrix["required_cases"], selection, release=release) + write_json(output / "plan.json", plan) + return { + "include": [ + { + "index": index, + "backend": case["backend"], + "configuration": case["configuration"], + "source_version": case["source_version"], + } + for index, case in enumerate(matrix["required_cases"]) + if index in selected + ] + } + + +def read_plan(root: Path, prepared: Path) -> tuple[dict[str, Any], Path]: + plan = json.loads((prepared / "plan.json").read_text(encoding="utf-8")) + gate.require(plan["format"] == 1, "Unknown parallel upgrade plan format") + gate.require(plan["workflow_run_id"] == os.environ.get("GITHUB_RUN_ID"), "Evidence belongs to another workflow run") + gate.require(plan["source_sha"] == os.environ.get("GITHUB_SHA"), "Evidence belongs to another checkout") + gate.require(plan["policy_sha256"] == gate.sha256(root / gate.MANIFEST), "Upgrade policy changed after preparation") + inputs = prepared / "inputs" + files = list(inputs.iterdir()) + gate.require(all(path.is_file() and not path.is_symlink() for path in files), "Invalid prepared distributions") + gate.require( + {path.name: gate.sha256(path) for path in files} == plan["artifacts"], "Prepared distributions changed" + ) + gate.require(plan["target_wheel"] in plan["artifacts"], "Missing prepared target wheel") + wheel = inputs / plan["target_wheel"] + policy = gate.validate_target(root, wheel, release=plan["release"], release_notes=plan["release_notes"]) + gate.require( + plan["target_version"] == gate.wheel_version(wheel) and plan["mode"] == policy["mode"], "Target changed" + ) + # Validate the frozen matrix against the reviewed policy without rediscovering + # PyPI independently on each runner (which could select different evidence). + declared = list(policy["cases"]) + if automatic := policy["automatic_sources"]: + declared += [{"source_version": automatic["source_version"], **profile} for profile in automatic["profiles"]] + required = plan["resolved_matrix"]["required_cases"] + gate.require( + [{key: value for key, value in case.items() if key != "source_artifact"} for case in required] == declared, + "Prepared matrix differs from reviewed cases", + ) + gate.require(required and plan["resolved_matrix"]["candidate_cases"] == required, "Incomplete prepared matrix") + selected_indexes(required, plan["selection"], release=plan["release"]) + return plan, wheel + + +def execute_case(root: Path, prepared: Path, index: int, output: Path) -> None: + plan, wheel = read_plan(root, prepared) + cases = plan["resolved_matrix"]["required_cases"] + gate.require( + index in selected_indexes(cases, plan["selection"], release=plan["release"]), "Unselected upgrade case index" + ) + output.mkdir(parents=True, exist_ok=False) + evidence: dict[str, Any] = {"plan_sha256": gate.sha256(prepared / "plan.json"), "index": index, "status": "failed"} + try: + evidence["result"] = gate.run_case(root, wheel, cases[index], output / "acceptance") + gate.require(evidence["result"]["status"] == "passed", "Upgrade case failed") + read_plan(root, prepared) + evidence["status"] = "passed" + finally: + # If provisioning failed before this command, the collector instead sees + # a missing result and rejects the matrix; it cannot count diagnostics as a pass. + write_json(output / "result.json", evidence) + + +def collect(root: Path, prepared: Path, reports: Path, output: Path) -> list[Path]: + """Accept exactly one passing result per planned case, then expose the tested bytes.""" + plan, wheel = read_plan(root, prepared) + output.mkdir(parents=True, exist_ok=False) + evidence: dict[str, Any] = { + "policy_sha256": plan["policy_sha256"], + "target_version": plan["target_version"], + "target_wheel_sha256": plan["artifacts"][wheel.name], + "mode": plan["mode"], + "resolved_matrix": plan["resolved_matrix"], + "execution_id": plan["execution_id"], + "selection": plan["selection"], + "complete": False, + "cases": [], + "status": "failed", + } + try: + results = list(reports.glob("*/result.json")) + cases = plan["resolved_matrix"]["required_cases"] + selected = selected_indexes(cases, plan["selection"], release=plan["release"]) + evidence["deferred_cases"] = [case for index, case in enumerate(cases) if index not in selected] + gate.require(len(results) == len(selected), "Missing or extra upgrade case results") + by_index = {} + for path in results: + result = json.loads(path.read_text(encoding="utf-8")) + gate.require(result["plan_sha256"] == gate.sha256(prepared / "plan.json"), "Stale or mixed upgrade results") + index = result["index"] + gate.require(type(index) is int and index in selected, "Unknown upgrade result index") + gate.require(index not in by_index, "Duplicate upgrade result") + gate.require(result["status"] == "passed", f"Upgrade case {index} failed") + case_result = result["result"] + gate.require( + case_result["case"] == cases[index] and case_result["status"] == "passed", "Case identity mismatch" + ) + acceptance = path.parent / "acceptance" + backend = gate.validate_case_output(cases[index], acceptance) + installed = json.loads((acceptance / "installed.json").read_text(encoding="utf-8")) + gate.require( + installed == case_result["installed"] + and installed["source"]["powercontext"] == cases[index]["source_version"] + and installed["target"]["powercontext"] == plan["target_version"], + "Installed versions do not match the planned upgrade", + ) + gate.require(backend == case_result["database"], "Backend evidence changed") + by_index[index] = case_result + evidence["cases"] = [by_index[index] for index in selected] + if plan["release"]: + gate.require( + plan["mode"] == "supported-upgrades", "Update the reviewed support declaration before publication" + ) + # Verify the originals once more before exposing a publication set. + read_plan(root, prepared) + evidence["complete"] = len(selected) == len(cases) + if plan["selection"] == "seekdb-code": + evidence["status"] = "selected-cases-passed" + return [] + validated = output / "validated-dist" + validated.mkdir() + accepted = [] + for name, digest in plan["artifacts"].items(): + destination = validated / name + shutil.copyfile(prepared / "inputs" / name, destination) + gate.require(gate.sha256(destination) == digest, "Validated distribution copy changed") + accepted.append(destination) + write_json( + output / "publication.json", + { + "artifacts": plan["artifacts"], + "complete": evidence["complete"], + "deferred_cases": evidence["deferred_cases"], + }, + ) + evidence["status"] = "passed" if evidence["complete"] else "selected-cases-passed" + return accepted + finally: + write_json(output / "result.json", evidence) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + preparation = commands.add_parser("prepare") + preparation.add_argument("--dist", type=Path, default=Path("dist")) + preparation.add_argument("--output", type=Path, required=True) + preparation.add_argument("--release", action="store_true") + preparation.add_argument("--selection", choices=SELECTIONS, default="full") + preparation.add_argument("--release-event", type=Path) + execution = commands.add_parser("case") + execution.add_argument("--prepared", type=Path, required=True) + execution.add_argument("--index", type=int, required=True) + execution.add_argument("--output", type=Path, required=True) + collection = commands.add_parser("collect") + collection.add_argument("--prepared", type=Path, required=True) + collection.add_argument("--reports", type=Path, required=True) + collection.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + try: + if args.command == "prepare": + notes = None + if args.release_event: + notes = json.loads(args.release_event.read_text(encoding="utf-8"))["release"]["body"] or "" + matrix = prepare( + gate.ROOT, + args.dist.resolve(), + args.output.resolve(), + release=args.release, + release_notes=notes, + selection=args.selection, + ) + print(json.dumps(matrix)) + if github_output := os.environ.get("GITHUB_OUTPUT"): + with Path(github_output).open("a", encoding="utf-8") as stream: + stream.write(f"matrix={json.dumps(matrix)}\n") + elif args.command == "case": + execute_case(gate.ROOT, args.prepared.resolve(), args.index, args.output.resolve()) + else: + accepted = collect(gate.ROOT, args.prepared.resolve(), args.reports.resolve(), args.output.resolve()) + if accepted: + verdict = json.loads((args.output / "result.json").read_text(encoding="utf-8")) + scope = ( + "Complete upgrade matrix" + if verdict["complete"] + else "Required automatic cases (manual cases waived)" + ) + print(f"{scope} passed; {len(accepted)} validated distributions") + else: + print("Selected upgrade cases passed; full acceptance remains incomplete; no publication artifacts") + except ( + gate.GateError, + OSError, + ValueError, + KeyError, + subprocess.SubprocessError, + tarfile.TarError, + zipfile.BadZipFile, + ElementTree.ParseError, + ) as error: + raise SystemExit(f"Parallel upgrade gate failed: {error}") from error # noqa: TRY003 -- operator diagnostic + + +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/replay_candidate_migration.py b/scripts/replay_candidate_migration.py new file mode 100644 index 0000000000..d05d0b342f --- /dev/null +++ b/scripts/replay_candidate_migration.py @@ -0,0 +1,271 @@ +# 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. + +"""Generate Candidate recovery evidence by observing the frozen migration on real databases. + +Development prototype only. This module does not register or replace production resources. +The command uses the repository's isolated real-backend fixtures, never an application database. +""" + +from __future__ import annotations + +import argparse +import copy +import json +import os +import sys +from collections.abc import Iterator +from contextlib import contextmanager +from importlib.metadata import version +from pathlib import Path +from typing import Any +from unittest.mock import patch +from xml.etree import ElementTree + +from sqlalchemy import Connection, Engine, event + +from powercontext.builtin.persistence.migrations.bundle import MigrationBundle, sqlite_inventory +from powercontext.builtin.persistence.migrations.models import digest +from powercontext.builtin.persistence.migrations.mysql_schema import MySQLSchema, mysql_inventory + +_CONTROL = frozenset({"pc_schema_revision"}) + + +class ReplayMismatch(AssertionError): + def __init__(self, operation: str): + super().__init__(f"Generated inventory differs from the frozen checkpoint: {operation}") + + +class ReplayInterrupted(BaseException): + """Stop after a real database operation has returned, before the runner can continue.""" + + +def inventory(connection: Connection) -> Any: + if connection.dialect.name == "sqlite": + return sqlite_inventory(connection, exclude=_CONTROL) + return mysql_inventory(connection, exclude=_CONTROL) + + +def objects(state: Any) -> dict[str, Any]: + if isinstance(state, list): + return {f"{row[0]}:{row[1]}": row for row in state} + return { + **{f"table:{name}": value for name, value in state["tables"].items()}, + "views": state["views"], + "triggers": state["triggers"], + } + + +class CandidateReplay: + """Observe actual full-database inventories; frozen checkpoints are comparison inputs only.""" + + def __init__(self, bundle: MigrationBundle, backend: str, output: Path, *, interrupt: str | None = None): + self.bundle = bundle + self.backend = backend + self.output = output + output.mkdir(parents=True, exist_ok=False) + self.interrupt = interrupt + self.records: list[dict[str, Any]] = [] + self.previous: Any = None + self.recording = False + self.manifest: dict[str, Any] = { + "format": "powercontext.candidate-replay.v1", + "backend": backend, + "source_release": "1.3.0", + "source_kind": "frozen captured full Server baseline, populated synthetic Candidate history", + "source_revision": "s1300", + "conversion_revision": "s1400", + "bundle_sha256": bundle.checksum, + "revision_checksums": bundle.checksums, + "resources": bundle.resources, + "status": "incomplete", + "records": self.records, + } + if backend == "seekdb": + self.manifest["binding_version"] = version("pylibseekdb") + self.save() + + def save(self) -> None: + (self.output / "manifest.json").write_text( + json.dumps(self.manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8" + ) + + def capture( + self, + connection: Connection, + statement: str, + *, + position: int | None = None, + expected: Any = None, + schema: MySQLSchema | None = None, + ) -> Any: + state = inventory(connection) + before = objects(self.previous) if self.previous is not None else {} + after = objects(state) + changed = { + name: after.get(name) + for name in sorted(before.keys() | after.keys()) + if before.get(name) != after.get(name) + } + record: dict[str, Any] = { + "sequence": len(self.records), + "operation": statement, + "registered_position": position, + "schema_sha256": digest(state), + "changes": changed, + } + if expected is not None: + record["registered_schema_sha256"] = digest(expected) + record["matches_registered_checkpoint"] = schema._matches(state, expected) if schema else state == expected + filename = f"{len(self.records):04d}.json" + record["inventory"] = filename + (self.output / filename).write_text(json.dumps(state, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + self.records.append(record) + self.previous = copy.deepcopy(state) + self.save() + if record.get("matches_registered_checkpoint") is False: + raise ReplayMismatch(statement) + return state + + def begin(self, connection: Connection) -> Any: + sql = "SELECT sqlite_version()" if self.backend == "sqlite" else "SELECT VERSION()" + self.manifest["engine_version"] = connection.exec_driver_sql(sql).scalar_one() + return self.capture(connection, "source") + + def interrupt_after(self, statement: str) -> None: + if self.interrupt and statement.startswith(self.interrupt): + self.interrupt = None + raise ReplayInterrupted + + @contextmanager + def native(self, schema: MySQLSchema) -> Iterator[None]: + entry = json.loads((self.bundle.snapshot_directory / "schemas/mysql-s1400.json").read_text())["revisions"][ + "s1400" + ] + operations = [] + for statement in entry["statements"]: + if "candidate" not in statement: + break + operations.append(statement) + self.manifest["registered_operations"] = operations + execute = schema.execute + + def observed(connection, statement, configuration): + if statement not in operations: + execute(connection, statement, configuration) + return + executed = [] + + def capture_sql(_connection, _cursor, sql, _parameters, _context, _executemany): + if sql.lstrip().upper().startswith(("CREATE ", "ALTER ", "DROP ", "INSERT ", "UPDATE ", "DELETE ")): + executed.append(sql) + + event.listen(connection, "after_cursor_execute", capture_sql) + try: + execute(connection, statement, configuration) + finally: + event.remove(connection, "after_cursor_execute", capture_sql) + position = operations.index(statement) + 1 + self.capture( + connection, statement, position=position, expected=schema._prefixes["s1400"][position], schema=schema + ) + self.records[-1]["executed_sql"] = executed + self.save() + self.interrupt_after(statement) + + # Observation only: every original operation executes against the real connection. + with patch.object(schema, "execute", observed): + yield + + @contextmanager + def sqlite(self) -> Iterator[None]: + fingerprints = self.bundle.fingerprints["s1400"] + + def observed(connection, _cursor, statement, _parameters, _context, _executemany): + if self.recording or connection.dialect.name != "sqlite": + return + operation = statement.strip() + candidate = "candidate" in operation and operation.upper().startswith(( + "CREATE ", + "ALTER ", + "DROP ", + "INSERT ", + )) + revision = operation.upper().startswith(("UPDATE PC_SCHEMA_REVISION", "INSERT INTO PC_SCHEMA_REVISION")) + if not candidate and not revision: + return + self.recording = True + try: + if candidate: + self.capture(connection, operation) + self.interrupt_after(operation) + elif connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalar_one() == "s1400": + state = self.capture(connection, "s1400 transaction checkpoint") + business = [ + row + for row in state + if row[1] not in self.bundle.retained_objects and row[2] not in self.bundle.retained_objects + ] + self.records[-1]["matches_registered_checkpoint"] = digest(business) in fingerprints + self.records[-1]["registered_schema_sha256"] = fingerprints + self.save() + if digest(business) not in fingerprints: + raise ReplayMismatch("s1400") + finally: + self.recording = False + + event.listen(Engine, "after_cursor_execute", observed) + try: + yield + finally: + event.remove(Engine, "after_cursor_execute", observed) + + def finish(self, **evidence: Any) -> None: + self.bundle.assert_unchanged() + self.manifest.update(evidence, status="passed") + self.save() + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("backend", choices=("sqlite", "seekdb", "oceanbase")) + parser.add_argument("--output", type=Path, required=True) + arguments = parser.parse_args() + output = arguments.output.resolve() + output.mkdir(parents=True, exist_ok=False) + os.environ["POWERCONTEXT_CANDIDATE_REPLAY_OUTPUT"] = str(output) + import pytest + + root = Path(__file__).resolve().parents[1] + report = output / "junit.xml" + result = pytest.main([ + str(root / "tests/builtin/persistence/test_candidate_schema_replay.py"), + "-k", + arguments.backend, + "-vv", + "-o", + "faulthandler_timeout=180", + f"--junitxml={report}", + ]) + if result: + return int(result) + cases = list(ElementTree.parse(report).iter("testcase")) # noqa: S314 -- locally generated pytest report + if len(cases) != 3 or any(case.find(tag) is not None for case in cases for tag in ("skipped", "failure", "error")): + print("All three real-backend Candidate replay/recovery cases must pass without skips", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) 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..6e85bb0031 --- /dev/null +++ b/scripts/upgrade_database_probe.py @@ -0,0 +1,205 @@ +# 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"), + ) + } + schema_rows = list(connection.execute("SELECT type,name,tbl_name,sql FROM sqlite_schema ORDER BY type,name")) + 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"), + "schema_rows": schema_rows, + **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") + trigger_rows = [ + dict(zip((column[0] for column in cursor.description), row, strict=True)) for row in cursor.fetchall() + ] + triggers = {row["Trigger"]: [row["Event"], row["Table"], row["Statement"]] for row in trigger_rows} + 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, + "trigger_rows": [ + {key: row[key] for key in ("Trigger", "Table", "Timing", "Event", "Statement")} for row in trigger_rows + ], + "database": options["database"], + "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..f6ef42ff5e --- /dev/null +++ b/scripts/upgrade_release_gate.py @@ -0,0 +1,591 @@ +# 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 and 1.4.0 require passing full Server acceptance on SQLite, seekDB and OceanBase. These migration paths are not yet verified; do not upgrade existing databases.", + "zh": "1.3.0 和 1.4.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 manual_upgrade_case(case: dict[str, Any]) -> bool: + # Temporary embedded-engine waiver: keep both Code configurations available + # for manual diagnostics, but do not claim accepted upgrade support for them. + return case["backend"] == "seekdb" and case["configuration"] in {"full-server-code", "full-server-vector-code"} + + +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 not manual_upgrade_case(case) + ) + if automatic := policy["automatic_sources"]: + lines.extend( + f"| {automatic['source_version']} | {policy['target_version']} | {profile['backend']} | {profile['configuration']} |" + for profile in automatic["profiles"] + if not manual_upgrade_case(profile) + ) + 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 = [] + # Preserve the existing revision journal before native logs can use the budget. + paths = [ + path + for name in ("database", "fresh") + for path in sorted((directory / f"{name}.pc-migration-state").glob("*/maintenance.json")) + ] + paths += [path for name in ("database", "fresh") for path in sorted((directory / name).rglob("*.log*"))] + with zipfile.ZipFile(output / "seekdb-native-logs.zip", "w", compression=zipfile.ZIP_DEFLATED) as archive: + for path in paths: + if remaining == 0: + break + if path.is_symlink() or not path.is_file(): + continue + relative = path.relative_to(directory).as_posix() + segments = [] + captured = 0 + with path.open("rb") as source: + size = source.seek(0, os.SEEK_END) + length = min(size, 4 * 1024 * 1024, remaining) + if length < size: + head = length // 2 + ranges = [ + (f"head/{relative}", 0, head), + (f"tail/{relative}", size - (length - head), length - head), + ] + else: + ranges = [(relative, 0, length)] + for entry, offset, count in ranges: + if count == 0 and size: + continue + source.seek(offset) + content = source.read(count) + archive.writestr(entry, content) + remaining -= len(content) + captured += len(content) + segments.append({"path": entry, "offset_bytes": offset, "captured_bytes": len(content)}) + files.append({ + "path": relative, + "size_bytes": size, + "captured_bytes": captured, + "truncated": captured < size, + "segments": segments, + }) + 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", + ) + backend = validate_case_output(case, output) + return {"case": case, "installed": metadata, "database": backend, "status": "passed"} + + +def validate_case_output(case: dict[str, Any], output: Path) -> dict[str, Any]: + validate_junit(output / "junit.xml") + 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 backend + + +def validate_target(root: Path, wheel: Path, *, release: bool, release_notes: str | None) -> dict[str, Any]: + 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", + ) + return policy + + +def gate(root: Path, wheel: Path, output: Path, *, release: bool = False, release_notes: str | None = None) -> None: + policy = validate_target(root, wheel, release=release, release_notes=release_notes) + actual_version = wheel_version(wheel) + # 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 = [] + evidence["deferred_cases"] = [case for case in matrix["required_cases"] if manual_upgrade_case(case)] + evidence["complete"] = not evidence["deferred_cases"] + required = [case for case in matrix["required_cases"] if not manual_upgrade_case(case)] + require(required, "No required automatic upgrade cases") + for index, case in enumerate(required): + 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" if evidence["complete"] else "selected-cases-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/database.py b/src/powercontext/builtin/persistence/database.py index b2e37772d3..d73c1aca91 100644 --- a/src/powercontext/builtin/persistence/database.py +++ b/src/powercontext/builtin/persistence/database.py @@ -61,6 +61,22 @@ async def _expiring(deadline: float) -> AsyncIterator[None]: raise ModelUsageAttemptExpired from error +async def begin_mysql_read_snapshot(connection: AsyncConnection) -> None: + """Start an owned read snapshot without changing the session default. + + The caller owns a fresh connection and must close or roll it back afterward. + Never use this inside a transaction that has already read business data. + """ + try: + await connection.exec_driver_sql("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ") + await connection.exec_driver_sql("START TRANSACTION WITH CONSISTENT SNAPSHOT") + except BaseException: + # A one-shot setting may have applied before START failed or was + # cancelled. Pool rollback alone does not consume that setting. + await connection.invalidate() + raise + + class AsyncDatabase: """Own or attach to one SQLAlchemy async engine. @@ -172,14 +188,7 @@ async def _transaction(self, *, consistent_snapshot: bool = False) -> AsyncItera # The MySQL dialect's begin hook is a no-op. Explicitly start # the owned transaction even when the server session uses autocommit. if consistent_snapshot: - try: - await connection.exec_driver_sql("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ") - await connection.exec_driver_sql("START TRANSACTION WITH CONSISTENT SNAPSHOT") - except BaseException: - # SET may have succeeded before START failed or was - # cancelled. Do not pool its unconsumed next-transaction setting. - await connection.invalidate() - raise + await begin_mysql_read_snapshot(connection) else: await connection.exec_driver_sql("START TRANSACTION") snapshot = nullcontext() diff --git a/src/powercontext/builtin/persistence/dream_schema.py b/src/powercontext/builtin/persistence/dream_schema.py index 41d7345af1..f4ce4e414e 100644 --- a/src/powercontext/builtin/persistence/dream_schema.py +++ b/src/powercontext/builtin/persistence/dream_schema.py @@ -17,10 +17,13 @@ This domain check does not replace RFC 1771's full-database readiness gate. """ +from pathlib import Path + from sqlalchemy import CheckConstraint, Connection, inspect from sqlalchemy.engine.reflection import Inspector from sqlalchemy.ext.asyncio import AsyncEngine +from powercontext.builtin.persistence.database import begin_mysql_read_snapshot from powercontext.builtin.persistence.tables import CANDIDATE_HEADS_TABLE, CANDIDATE_VERSIONS_TABLE @@ -31,26 +34,37 @@ 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": + from powercontext.builtin.persistence.migrations.locking import assert_no_local_migration + + for _, name, filename in await connection.exec_driver_sql("PRAGMA database_list"): + if name == "main" and filename: + assert_no_local_migration(Path(filename)) + # 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") + elif connection.dialect.name == "mysql": + await begin_mysql_read_snapshot(connection) + 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, _startup=True) + 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..e69089287a 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,15 +45,35 @@ class MigrationBundle: partially applied DDL cannot be mistaken for a supported baseline. """ - def __init__(self, directory: Path) -> None: + def __init__( # noqa: C901 -- Keep ordered admission checks with their operation. + self, + directory: Path, + *, + embedding_profile: EmbeddingProfile | None = None, + source_directory: Path | None = None, + ) -> None: self.directory = directory.resolve() + self.source_directory = source_directory.resolve() if source_directory is not None else None + self._original_resources = self._read_resources(self.directory) + self._source_resources = self._read_resources(self.source_directory) if self.source_directory else {} + resources = self._original_resources.copy() + if self.source_directory is not None: + registration = json.loads(self._source_resources["registration.json"]) + removed = registration.get("remove_resources", []) + if ( + not isinstance(removed, list) + or any(not isinstance(name, str) or name not in resources for name in removed) + or len(set(removed)) != len(removed) + ): + raise MigrationError("invalid_bundle", "Source overlay removes an unknown or duplicate resource.") + for name in removed: + del resources[name] + resources.update({ + name: content for name, content in self._source_resources.items() if name != "registration.json" + }) + resources["_source_registration.json"] = self._source_resources["registration.json"] # Execute exactly the reviewed bytes, even if the installed resources # change during a long backup. Alembic reloads scripts on every command. - resources = { - path.relative_to(self.directory).as_posix(): path.read_bytes() - for path in sorted(self.directory.rglob("*")) - if path.is_file() and path.suffix in {".py", ".sql", ".json"} - } self._snapshot = TemporaryDirectory(prefix="pc-migration-bundle-") self.snapshot_directory = Path(self._snapshot.name).resolve() for name, content in resources.items(): @@ -67,6 +92,38 @@ 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.source_revision = manifest.get("source_revision") + self.source_release = manifest.get("source_release") + if self.source_revision is not None and self.source_revision != self.revisions[0]: + raise MigrationError("invalid_bundle", "A registered source must start its linear revision chain.") + 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()} + source_revision = manifest.get("source_revision", "s1300") + manifest["sqlite_baselines"] = {digest(layouts[source_revision]): source_revision} + 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"] = manifest.get("vector_domain_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 +139,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,16 +147,40 @@ 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 assert_unchanged(self) -> None: - resources = { - path.relative_to(self.directory).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() - for path in sorted(self.directory.rglob("*")) + 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) + + @staticmethod + def _read_resources(directory: Path) -> dict[str, bytes]: + return { + path.relative_to(directory).as_posix(): path.read_bytes() + for path in sorted(directory.rglob("*")) if path.is_file() and path.suffix in {".py", ".sql", ".json"} } - if resources != self.resources: + + def assert_unchanged(self) -> None: + if self._read_resources(self.directory) != self._original_resources or ( + self.source_directory is not None and self._read_resources(self.source_directory) != self._source_resources + ): raise MigrationError("plan_changed", "Migration resources changed after the bundle was loaded.") def config(self, connection: Connection) -> Config: diff --git a/src/powercontext/builtin/persistence/migrations/connections.py b/src/powercontext/builtin/persistence/migrations/connections.py index ef85571df8..6f6fa6d579 100644 --- a/src/powercontext/builtin/persistence/migrations/connections.py +++ b/src/powercontext/builtin/persistence/migrations/connections.py @@ -18,21 +18,26 @@ 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 +from powercontext.builtin.persistence.database import begin_mysql_read_snapshot 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.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 +66,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 +78,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 +99,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( @@ -112,9 +121,50 @@ def run( "unsupported_target", "A nonempty seekdb directory must contain recognized native storage." ) initial_inode = _directory_inode(path) if path.exists() else None - return asyncio.run(self._seekdb(operation, self.config, path, identity, initial_inode)) + return asyncio.run( + self._seekdb(operation, self.config, path, identity, initial_inode, writable=writable) + ) 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], @@ -122,6 +172,8 @@ async def _seekdb( path: Path, identity: BackendIdentity, initial_inode: tuple[int, int] | None, + *, + writable: bool, ) -> _T: try: module = seekdb_profile._load_binding() @@ -140,6 +192,8 @@ async def _seekdb( if initial_inode is not None and initial_inode != inode: raise MigrationError("migration_lock_lost", "The seekdb directory changed during engine startup.") async with engine.connect() as connection: + if not writable: + await begin_mysql_read_snapshot(connection) def execute(sync: Connection) -> _T: def verify() -> None: @@ -151,7 +205,8 @@ def verify() -> None: raise MigrationError("target_identity_unavailable", "The seekdb database identity changed.") # MySQL's SQLAlchemy begin hook sends no SQL. Match the # explicit transaction ownership used by AsyncDatabase. - sync.exec_driver_sql("START TRANSACTION") + if writable: + sync.exec_driver_sql("START TRANSACTION") result = operation(sync, identity, verify) verify() return result @@ -185,6 +240,8 @@ async def _oceanbase( ) try: async with engine.connect() as connection: + if not writable: + await begin_mysql_read_snapshot(connection) identity = await connection.run_sync(_oceanbase_identity) if not writable: return await connection.run_sync(lambda sync: operation(sync, identity, lambda: None)) diff --git a/src/powercontext/builtin/persistence/migrations/deployment.py b/src/powercontext/builtin/persistence/migrations/deployment.py index 86977dfaa4..32ab8927ed 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,102 @@ from powercontext.server.settings import ServerSettings +def server_bundle( + *, embedding_profile: EmbeddingProfile | None = None, source_release: Literal["1.4.0"] | None = None +) -> MigrationBundle: + """The shared Server chain, optionally reached through a frozen source bridge.""" + root = Path(__file__).parent + return MigrationBundle( + root / "server_resources", + embedding_profile=embedding_profile, + source_directory=root / "server_sources" / source_release if source_release else None, + ) + + 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 deployment_runner( +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( # 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() + ) + sources = ( + (server_bundle(embedding_profile=profile, source_release="1.4.0"),) + if bundle in {"server", "processing", "atomic-projection"} + else () + ) + 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 +163,28 @@ 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, + source_bundles=sources, + 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, + source_bundles=sources, 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 +195,21 @@ 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, + source_bundles=sources, + 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, + source_bundles=sources, + 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/locking.py b/src/powercontext/builtin/persistence/migrations/locking.py index 1e3f62bb99..e2af2a8e9d 100644 --- a/src/powercontext/builtin/persistence/migrations/locking.py +++ b/src/powercontext/builtin/persistence/migrations/locking.py @@ -16,6 +16,9 @@ from __future__ import annotations +import errno +import os +import stat from collections.abc import Iterator from contextlib import contextmanager, suppress from pathlib import Path @@ -56,3 +59,38 @@ def local_migration_lock(target: Path) -> Iterator[None]: raise else: lock.release() + + +def assert_no_local_migration(target: Path) -> None: + """Probe an existing migration lock without creating or changing its file. + + This observes active maintenance, not future migration attempts or existing + writers. Deployment orchestration must still prevent restarts during maintenance. + """ + target = target.resolve() + path = target.with_name(target.name + ".pc-migration.lock") + try: + descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) + except FileNotFoundError: + return + except OSError as error: + raise MigrationError("migration_lock_unsupported", "Cannot inspect the maintenance lock.") from error + try: + if not stat.S_ISREG(os.fstat(descriptor).st_mode): + raise MigrationError("migration_lock_unsupported", "The maintenance lock is not a regular file.") + if os.name == "posix": + import fcntl + + fcntl.flock(descriptor, fcntl.LOCK_SH | fcntl.LOCK_NB) + elif os.name == "nt": + import msvcrt + + msvcrt.locking(descriptor, msvcrt.LK_NBRLCK, 1) + else: + raise MigrationError("migration_lock_unsupported", "This platform has no supported lock inspection.") + except OSError as error: + if error.errno in {errno.EACCES, errno.EAGAIN, errno.EDEADLK}: + raise MigrationError("migration_locked", "Another process holds the migration lock.") from error + raise MigrationError("migration_lock_unsupported", "Cannot inspect the maintenance lock.") from error + finally: + os.close(descriptor) diff --git a/src/powercontext/builtin/persistence/migrations/maintenance.py b/src/powercontext/builtin/persistence/migrations/maintenance.py new file mode 100644 index 0000000000..eb44bca878 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/maintenance.py @@ -0,0 +1,297 @@ +# 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, sqlite_inventory +from .connections import BackendIdentity, MaintenanceConnections +from .models import MigrationError, MigrationPlan, MigrationResult, digest +from .mysql import MySQLMigrationRunner, _Evidence, _Journal, require_completed_domain_maintenance +from .server_readiness import check_server_schema +from .sources import MigrationSources +from .sqlite import SQLiteMigrationRunner, read_maintenance_evidence, verify_release_checksums, verify_retained_objects +from .vector_profile import read_embedding_profile + + +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, + source_bundles: tuple[MigrationBundle, ...] = (), + ) -> None: + self.connections = connections + self.bundle = bundle + self.sources = MigrationSources(bundle, source_bundles) + 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_inputs = { + "operation": self.operation, + "lifecycle": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "implementation": implementation_checksum, + } + self.checksum = digest({"bundle": bundle.checksum, **self._checksum_inputs}) + + def _select_source(self, evidence) -> None: + self.bundle = self.sources.from_evidence(evidence) + self.bundle.assert_unchanged() + self.checksum = digest({"bundle": self.bundle.checksum, **self._checksum_inputs}) + + 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.") + if read_embedding_profile(connection) is not None: + raise MigrationError( + "unsupported_upgrade_configuration", + "This partial maintenance bundle has no registered vector configuration path.", + ) + self._check_previous_maintenance(connection, 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, connection: Connection, identity: BackendIdentity) -> None: + require_completed_domain_maintenance(self.directory, identity, excluding=self.operation) + main_path = ( + self.directory / "maintenance.json" + if isinstance(self.connections.config, SQLiteConfig) + else self.directory / identity.database_id / "maintenance.json" + ) + if isinstance(self.connections.config, SQLiteConfig): + main = read_maintenance_evidence(main_path, identity.database_id) + if main is not None and main["state"] != "complete": + raise MigrationError("recovery_required", "Complete the original full Server maintenance first.") + self._select_source(main) + if main is not None: + verify_release_checksums(main["release_checksums"], self.bundle.checksums) + verify_retained_objects(sqlite_inventory(connection), self.bundle.head, bundle=self.bundle, evidence=main) + else: + main = _Journal(self.directory, identity).read() + self._select_source(main.model_dump() if main is not None else None) + if main is not None: + runner = MySQLMigrationRunner( + self.connections, + self.bundle, + configuration=self.configuration, + evidence_directory=self.directory, + ) + revisions = connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalars().all() + if len(revisions) != 1: + raise MigrationError("recovery_required", "The full Server migration head is missing or ambiguous.") + runner.verify_completed_evidence(main, revisions[0]) 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..4cb5baf641 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,9 @@ 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 +from .sources import MigrationSources +from .sqlite import verify_release_checksums BackupPolicy = Literal["auto", "manual", "skip"] BackupFactory = Callable[[Connection, BackendIdentity, Path], BackupProvider] @@ -130,6 +137,20 @@ def write(self, evidence: _Evidence) -> None: ) from error +def require_completed_domain_maintenance( + directory: Path, identity: BackendIdentity, *, excluding: str | None = None +) -> None: + """Block admission until recorded domain work completes; its own runner may resume it.""" + for operation in ("processing_configuration", "atomic_projection"): + if operation == excluding: + continue + evidence = _Journal(directory / operation, identity).read() + if evidence is not None and evidence.state != "complete": + raise MigrationError( + "recovery_required", f"Resume the recorded {operation} maintenance before full Server admission." + ) + + class MySQLMigrationRunner: """Use one version table, a pinned backend lock, and persistent external intent. @@ -147,12 +168,17 @@ def __init__( evidence_directory: Path, configuration: Mapping[str, Any] | None = None, backup_factory: BackupFactory | None = None, + source_bundles: tuple[MigrationBundle, ...] = (), ) -> None: self.connections = connections self.bundle = bundle - self.schema = MySQLSchema(bundle) + self.sources = MigrationSources(bundle, source_bundles) 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.configuration_digest = digest(self.configuration) if connections.config.kind == "oceanbase": if ( connections.lock_coordination != "single-host" @@ -163,34 +189,67 @@ 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._load_bundle(bundle) + + def _load_bundle(self, bundle: MigrationBundle) -> None: + self.bundle = bundle + self.schema = MySQLSchema(bundle, embedded_code=self.configuration.get("embedded_code_enabled", False)) + self._history_directory = ( + bundle.snapshot_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 +284,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 +320,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 +397,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 +428,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 +443,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 +489,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 +523,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, @@ -451,19 +534,69 @@ def _inspect( ) -> MigrationPlan: if backup_policy not in {"auto", "manual", "skip"}: raise MigrationError("invalid_backup_policy", "Choose auto, manual or skip.") + if self.bundle.readiness_scope == "server": + require_completed_domain_maintenance(self.evidence_directory, identity) journal = _Journal(self.evidence_directory, identity) evidence = journal.read() 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.sources.revisions) + if connection is not None + else (False, None) + ) + selected = self.sources.select( + evidence=evidence.model_dump() if evidence is not None else None, + revision=current, + initialized=initialized, + inventory=inventory, + embedded_code=self.configuration.get("embedded_code_enabled", False), + ) + selected.assert_unchanged() + if selected is not self.bundle: + self._load_bundle(selected) + if ( + connection is not None + and evidence is not None + and evidence.state == "complete" + and self.bundle.vector_profile is not None + ): + # Revision checksums bind the embedding space as well as resources. + # Diagnose configuration drift first, but leave partial DDL/domain + # recovery to its original evidence: its declaration may be unseeded. + self.bundle.vector_profile.verify_declaration(connection, required=False) 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,29 +625,50 @@ 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 verify_completed_evidence(self, evidence: _Evidence, revision: str) -> None: + """Check shared maintenance history without requiring domain readiness. + + A registered partial operation may repair its own domain, but cannot + replace full migration evidence or bypass released execution checksums. + """ + self._assert_unchanged() + if evidence.state != "complete": + raise MigrationError("recovery_required", "Complete the original full Server maintenance first.") + self._validate_evidence(evidence) + self._validate_position(evidence, revision) + def _validate_evidence(self, evidence: _Evidence | None) -> None: if evidence is None: return - for revision, checksum in evidence.release_checksums.items(): - if self.checksums.get(revision) != checksum: - raise MigrationError("checksum_conflict", "A recorded released migration resource changed.") + verify_release_checksums(evidence.release_checksums, self.checksums) if evidence.state != "complete" and ( evidence.bundle_checksum != self.checksum or evidence.configuration_digest != self.configuration_digest ): @@ -608,7 +762,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/revision_names.py b/src/powercontext/builtin/persistence/migrations/revision_names.py new file mode 100644 index 0000000000..60beec1ec4 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/revision_names.py @@ -0,0 +1,55 @@ +# 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. + +"""Software-release names without rewriting persisted migration identities.""" + +import re +from typing import TypedDict + + +class RevisionName(TypedDict): + revision: str + name: str + software_version: str | None + sequence: int | None + + +# These aliases are presentation metadata. Keep the original revision IDs in +# frozen resources, accepted plans, version tables and recovery evidence. +_LEGACY_NAMES = { + "s1300": "v1_3_0_000", + "r1400": "v1_4_0_000", + "s1400": "v1_4_1_001", + "s1401": "v1_4_1_002", + "s1402": "v1_4_1_003", +} +_NAME = re.compile(r"v(0|[1-9][0-9]*)_(0|[1-9][0-9]*)_(0|[1-9][0-9]*)_([0-9]{3})") + + +def describe_revision(revision: str | None) -> RevisionName | None: + """Describe a database state, not the running binary or its original source. + + Newly registered revisions use their release-based name directly. Unknown + legacy/custom IDs remain opaque; their software version is never guessed. + """ + if revision is None: + return None + name = _LEGACY_NAMES.get(revision, revision) + match = _NAME.fullmatch(name) + return { + "revision": revision, + "name": name, + "software_version": ".".join(match.groups()[:3]) if match else None, + "sequence": int(match[4]) if match else None, + } 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..6771f46845 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_readiness.py @@ -0,0 +1,193 @@ +# 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 pathlib import Path + +from sqlalchemy import inspect +from sqlalchemy.engine import make_url + +from .bundle import sqlite_inventory +from .models import MigrationError, digest + + +def check_server_schema(connection, *, _archive_recovery=False, _startup=False): + bundle = check_server_layout(connection) + if _startup and connection.dialect.name == "sqlite": + _check_sqlite_startup_history(connection, bundle) + 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 _check_sqlite_startup_history(connection, bundle): + """Honor observable local history without requiring an external journal to exist.""" + from .connections import BackendIdentity + from .deployment import server_bundle + from .mysql import require_completed_domain_maintenance + from .sources import MigrationSources + from .sqlite import read_maintenance_evidence, verify_release_checksums, verify_retained_objects + + filename = next( + (filename for _, name, filename in connection.exec_driver_sql("PRAGMA database_list") if name == "main"), + "", + ) + if not filename: + return + target = Path(filename).resolve() + directory = target.with_name(target.name + ".pc-migration-state") + identity = BackendIdentity("sqlite", digest({"kind": "sqlite", "path": str(target)}), target.name) + evidence = read_maintenance_evidence(directory / "maintenance.json", identity.database_id) + if evidence is not None: + if evidence["state"] != "complete": + raise MigrationError("recovery_required", "Complete the recorded full Server maintenance before startup.") + profile = bundle.vector_profile.profile if bundle.vector_profile is not None else None + bundle = MigrationSources( + bundle, (server_bundle(embedding_profile=profile, source_release="1.4.0"),) + ).from_evidence(evidence) + verify_release_checksums(evidence["release_checksums"], bundle.checksums) + verify_retained_objects(sqlite_inventory(connection), bundle.head, bundle=bundle, evidence=evidence) + require_completed_domain_maintenance(directory, identity) + + +def prepare_server_storage(settings, *, injected_embedding=False, embedding_profile=None): # noqa: C901 -- Keep startup admission and bootstrap decisions together. + from .deployment import configured_embedding_profile, deployment_runner + + database = settings.database + if database.kind == "sqlite" and database.is_in_memory: + return + if database.kind == "sqlite": + from .locking import assert_no_local_migration + + url = make_url(database.url) + if url.database and not url.query: + assert_no_local_migration(Path(url.database).absolute()) + 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/server_sources/1.4.0/manifest.json b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/manifest.json new file mode 100644 index 0000000000..5c06c6a39b --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/manifest.json @@ -0,0 +1,213 @@ +{ + "revision_resources": { + "s1400": [ + "schemas/sqlite.json", + "dream/schema.json", + "release1400_operations.py", + "schemas/mysql-s1400.json" + ], + "s1401": [ + "schemas/sqlite-s1401.json", + "schemas/mysql-s1401.json" + ], + "s1402": [ + "schemas/sqlite-s1402.json", + "schemas/mysql-s1402.json" + ], + "r1400": [ + "schemas/sqlite.json", + "release1400_operations.py", + "schemas/mysql-r1400.json", + "source-provenance.json" + ] + }, + "sqlite_baselines": { + "374ad3217e4dc286c3942708da52cc8262e30581e2b836bedd0c4e693f1b75bc": "r1400" + }, + "sqlite_fingerprints": { + "s1400": [ + "9064ecb0c6233884e64312a1a9a48a6c52b7c379724fa83f9b77b90ed1ee0381" + ], + "s1401": [ + "8752beed9acfb66e03d4360f8652661107b58067ae7a9c461f1035345edf4f27" + ], + "s1402": [ + "795b6fd01188c15d16625c59506aadc42134dc8293ffdd4a1588a04f8f66e877" + ], + "r1400": [ + "374ad3217e4dc286c3942708da52cc8262e30581e2b836bedd0c4e693f1b75bc" + ] + }, + "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": "release1400_domain.py", + "resources": [ + "server_domain.py", + "server_vector_domain.py", + "release1400_domain.py", + "release1400_vector_domain.py", + "dream/upgrade.py", + "dream/schema.json", + "dream/manifest.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-r1400.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" + }, + "source_release": "1.4.0", + "source_revision": "r1400", + "vector_domain_module": "release1400_vector_domain.py" +} diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/mysql/versions/r1400.py b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/mysql/versions/r1400.py new file mode 100644 index 0000000000..8c52d2c021 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/mysql/versions/r1400.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 = "r1400" +down_revision = None + + +def upgrade() -> None: + context.config.attributes["execute_revision"](revision) diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/mysql/versions/s1400.py b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/mysql/versions/s1400.py new file mode 100644 index 0000000000..8f21b57571 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/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 = "r1400" + + +def upgrade() -> None: + context.config.attributes["execute_revision"](revision) diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/profiles/seekdb-code.json b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/profiles/seekdb-code.json new file mode 100644 index 0000000000..3f9a339f10 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/profiles/seekdb-code.json @@ -0,0 +1,507 @@ +{ + "format": "powercontext.mysql-schema-extension.v1", + "source_release": "1.3.0", + "source_revision": "r1400", + "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_sources/1.4.0/registration.json b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/registration.json new file mode 100644 index 0000000000..c7c0d6d90f --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/registration.json @@ -0,0 +1,7 @@ +{ + "remove_resources": [ + "versions/s1300_server_baseline.py", + "mysql/versions/s1300.py", + "schemas/mysql-s1300.json" + ] +} diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_domain.py b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_domain.py new file mode 100644 index 0000000000..d19ccd5024 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_domain.py @@ -0,0 +1,83 @@ +# 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. + +"""Preserve current 1.4 tasks and authority while joining the shared Server chain.""" + +import hashlib +import runpy +from pathlib import Path + +from sqlalchemy import inspect + +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.pinned import finish, pinned + +shared = runpy.run_path(str(Path(__file__).parent / "server_domain.py")) + + +def preflight(connection, *, configuration): + shared["preflight"](connection, configuration=configuration) + validate_source_data(connection, configuration=configuration) + + +def validate_source_data(connection, *, configuration): + names = set(inspect(connection).get_table_names()) + if not names or names == {"pc_schema_revision"}: + return + frozen = shared["dream"]() + try: + manifest = frozen["_processing_manifest"](connection, frozen["_schema"](), upgraded=True) + except frozen["DreamUpgradeError"] as error: + raise MigrationError(error.reason, "The released Processing configuration is not recognized.") from error + target = configuration["processing_manifest"] + if any(manifest[key] != target[key] for key in ("bindings", "mode", "capabilities")): + raise MigrationError( + "incompatible_processing_configuration", "Processing ownership differs from the source declaration." + ) + shared["_validate_intent_bindings"](connection) + # Current tasks are decoded by their supported repository contract. The + # frozen 1.3 task recognizer rejects newer operations and is not applicable. + 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)) + + +def apply(connection, *, configuration): + shared["apply"](connection, configuration=configuration) + + +def verify(connection, *, configuration): + # No legacy Candidate conversion occurred on this source path. + validate(connection, configuration=configuration) + + +def validate(connection, *, configuration): + shared["validate"](connection, configuration=configuration) diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_operations.py b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_operations.py new file mode 100644 index 0000000000..30046105c8 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_operations.py @@ -0,0 +1,32 @@ +# 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 released 1.4 Processing declaration on the pinned connection.""" + +import json + +from sqlalchemy import text + +from powercontext.builtin.persistence.migrations.models import MigrationError + + +def execute(connection, statement, configuration): + if statement != "@initialize_processing": + raise MigrationError("invalid_bundle", "Unknown released-source initialization operation.") + 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(configuration["processing_manifest"], sort_keys=True, separators=(",", ":"))}, + ) diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_vector_domain.py b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_vector_domain.py new file mode 100644 index 0000000000..2a87dc16cd --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/release1400_vector_domain.py @@ -0,0 +1,39 @@ +# 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. + +"""Join the shared vector profile without converting already-current Candidates.""" + +import runpy +from pathlib import Path + +root = Path(__file__).parent +shared = runpy.run_path(str(root / "server_vector_domain.py")) +source = runpy.run_path(str(root / "release1400_domain.py")) + + +def preflight(connection, *, configuration): + shared["preflight"](connection, configuration=configuration) + source["validate_source_data"](connection, configuration=configuration) + + +def apply(connection, *, configuration): + shared["apply"](connection, configuration=configuration) + + +def verify(connection, *, configuration): + validate(connection, configuration=configuration) + + +def validate(connection, *, configuration): + shared["validate"](connection, configuration=configuration) diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/schemas/mysql-r1400.json b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/schemas/mysql-r1400.json new file mode 100644 index 0000000000..b3e0d6847a --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/schemas/mysql-r1400.json @@ -0,0 +1,12098 @@ +{ + "format": "powercontext.mysql-state-layouts.v1", + "revisions": { + "r1400": { + "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 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_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 `created_at` datetime DEFAULT NULL,\n `created_by` varchar(2048) DEFAULT NULL,\n `restored_from_revision` int(11) DEFAULT NULL,\n `rollback_reason` varchar(1024) DEFAULT 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_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_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 `consecutive_dream_attempts` 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_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_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 CONSTRAINT `pc_scopes_OBFK_1792942763272555` 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_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_operation` (`scope_id`, `operation_id`, `created_at`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_decision_observations_scope_created` (`scope_id`, `created_at`, `observation_id`) 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_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_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_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_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_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 CONSTRAINT `pc_artifact_handles_OBFK_1792942764247536` 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_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_1792942764264492` 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 CONSTRAINT `pc_artifact_lineage_artifacts_OBFK_1792942764327696` 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 CONSTRAINT `pc_artifact_lineage_artifacts_OBFK_1792942764323467` 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 CONSTRAINT `pc_artifact_publications_OBFK_1792942764363019` 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 CONSTRAINT `pc_artifact_publications_OBFK_1792942764353101` 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_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 CONSTRAINT `pc_topic_memory_revision_publications_OBFK_1792942765350359` 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_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 CONSTRAINT `pc_artifact_lineage_sources_OBFK_1792942764296771` FOREIGN KEY (`scope_id`, `source_type`, `source_id`) REFERENCES `pc_sources`(`scope_id`, `source_type`, `source_id`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `pc_artifact_lineage_sources_OBFK_1792942764293392` 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_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 CONSTRAINT `pc_candidate_versions_OBFK_1792942765299932` 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 `pc_candidate_versions_OBFK_1792942765290525` FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n CONSTRAINT `ck_pc_candidate_versions_version_positive` CHECK ((`version` > 0)),\n 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))))\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 CONSTRAINT `pc_context_bootstrap_receipts_OBFK_1792942765781175` 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_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 `proposal_fingerprint` varchar(71) DEFAULT 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 CONSTRAINT `pc_dream_runs_OBFK_1792942765623127` 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_runs_proposal_fingerprint` (`scope_id`, `principal_key`, `proposal_fingerprint`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_dream_list` (`scope_id`, `accepted_at`, `run_id`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_dream_dispatch` (`scope_id`, `operation`, `status`, `request_generation`) 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_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 CONSTRAINT `pc_recurrence_match_OBFK_1792942765403587` 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 CONSTRAINT `pc_recurrence_observation_OBFK_1792942765451906` 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_revision` (`scope_id`, `family`, `artifact_id`, `revision`) 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 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 CONSTRAINT `pc_scope_bindings_OBFK_1792942764219182` 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 CONSTRAINT `pc_scope_context_references_OBFK_1792942764126042` FOREIGN KEY (`referenced_scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `pc_scope_context_references_OBFK_1792942764123304` 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 CONSTRAINT `pc_scope_creation_requests_OBFK_1792942764171624` 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 CONSTRAINT `pc_scope_external_references_OBFK_1792942764144862` 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 CONSTRAINT `pc_scope_settings_OBFK_1792942764194937` 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_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 CONSTRAINT `pc_memory_entry_heads_OBFK_1792942765379233` 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_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 CONSTRAINT `pc_artifact_tags_OBFK_1792942765993012` 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_family_key` (`scope_id`, `family`, `tag_key_hash`, `target_type`, `artifact_id`, `target_id`) BLOCK_SIZE 16384 LOCAL,\n KEY `ix_pc_artifact_tags_key` (`scope_id`, `tag_key_hash`, `family`, `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 CONSTRAINT `pc_topic_memory_active_chunks_OBFK_1792942765964247` 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 CONSTRAINT `pc_topic_memory_active_topics_OBFK_1792942765940683` 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_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 `candidate_kind` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'artifact',\n `result_payload` mediumblob DEFAULT 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 CONSTRAINT `pc_candidate_heads_OBFK_1792942765873054` 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 CONSTRAINT `pc_candidate_heads_OBFK_1792942765869498` FOREIGN KEY (`scope_id`, `candidate_id`, `version`) REFERENCES `pc_candidate_versions`(`scope_id`, `candidate_id`, `version`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n CONSTRAINT `pc_candidate_heads_OBFK_1792942765869483` FOREIGN KEY (`scope_id`) REFERENCES `pc_scopes`(`scope_id`) ON UPDATE RESTRICT ON DELETE CASCADE ,\n CONSTRAINT `ck_pc_candidate_kind` CHECK ((`candidate_kind` in ('artifact','tag'))),\n CONSTRAINT `ck_pc_candidate_heads_status` CHECK ((`status` in ('pending','approved','rejected'))),\n 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))))\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 CONSTRAINT `pc_profile_policies_OBFK_1792942766090659` FOREIGN KEY (`scope_id`, `pending_candidate_id`) REFERENCES `pc_candidate_heads`(`scope_id`, `candidate_id`) ON UPDATE RESTRICT ON DELETE RESTRICT ,\n CONSTRAINT `pc_profile_policies_OBFK_1792942766078846` 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", + "@release1400_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_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_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_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": [] + }, + "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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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": [] + }, + "proposal_fingerprint": { + "type": [ + "varchar", + [ + "71" + ] + ], + "nullable": true, + "default": null, + "charset": "utf8mb4", + "collation": null, + "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_runs_proposal_fingerprint": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "principal_key", + null, + "asc" + ], + [ + "proposal_fingerprint", + 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_dispatch": { + "columns": [ + [ + "scope_id", + null, + "asc" + ], + [ + "operation", + null, + "asc" + ], + [ + "status", + null, + "asc" + ], + [ + "request_generation", + null, + "asc" + ] + ], + "unique": false + } + } + } + }, + { + "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_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_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_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_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": [] + }, + "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": [] + }, + "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": "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_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": {} + } + }, + {} + ] + } + }, + "optional_table_options": { + "pc_access_audit": { + "auto_increment_mode": [ + "order" + ] + } + } +} diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/schemas/mysql-s1400.json b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/schemas/mysql-s1400.json new file mode 100644 index 0000000000..dac9b4f79a --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/schemas/mysql-s1400.json @@ -0,0 +1,3297 @@ +{ + "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)", + "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)", + "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_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_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_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_sources/1.4.0/schemas/sqlite.json b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/schemas/sqlite.json new file mode 100644 index 0000000000..d4c27f7a48 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/schemas/sqlite.json @@ -0,0 +1,1281 @@ +{ + "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_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_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 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_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)", + "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\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)", + "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\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)", + "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_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)", + "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)", + "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_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)", + "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\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)", + "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_lexical_projection_versions (\n\tprojection VARCHAR(128) NOT NULL, \n\tanalyzer_version VARCHAR(64) NOT NULL, \n\tPRIMARY KEY (projection)\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_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_context_bootstrap_receipts_created_at ON pc_context_bootstrap_receipts (created_at)", + "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_dream_runs_proposal_fingerprint ON pc_dream_runs (scope_id, principal_key, proposal_fingerprint)", + "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_sources/1.4.0/source-provenance.json b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/source-provenance.json new file mode 100644 index 0000000000..0016967db4 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/source-provenance.json @@ -0,0 +1,273 @@ +{ + "format": "powercontext.released-source-provenance.v1", + "source_release": "1.4.0", + "source_revision": "r1400", + "source_wheel": { + "name": "powercontext", + "version": "1.4.0", + "filename": "powercontext-1.4.0-py3-none-any.whl", + "sha256": "f700209c85470ddb3825c6ab9d51d747c75c69401697f22999ed08e1a3bbc3ec", + "url": "https://files.pythonhosted.org/packages/c6/b6/5ec591d57fd87c1176908d9b80661bca7a1623e7cf27bd507a285eba627b/powercontext-1.4.0-py3-none-any.whl", + "uploaded_at": "2026-10-19T05:59:50.214919Z", + "metadata_url": "https://pypi.org/pypi/powercontext/1.4.0/json", + "metadata_origin": "live-pypi" + }, + "workflow_run": "https://github.com/oceanbase/powercontext/actions/runs/40207916624", + "head_commit": "6d055f03a0d99b2843bbaf8f6ecf506451a0b0e3", + "tested_merge_commit": "4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "capture_method": "Independently installed official wheel; real Server startup and writes; Server stopped before reflection; no target migration executed.", + "captures": [ + { + "backend": "oceanbase", + "configuration": "full-server-code", + "captured_at": "2026-10-25T15:39:57.113515+00:00", + "schema_sha256": "abeb9100f85118c9ac36478db23e40627d8cef1ef392b4a21111944e02e738ba", + "canonical_sha256": "630b72acf4f665f3f920878e12ccca651a7a42b2091531d0d4e225154b843cc8", + "engine_version": "5.7.25-OceanBase_CE-v4.3.5.6", + "driver_version": "1.2.0", + "profile": { + "code_enabled": true, + "vector_dimension": 0, + "embedding_normalization": null + }, + "embedding_profile": {}, + "artifact_name": "release-1.4.0-oceanbase-full-server-code-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": "4d770a089e4f270e813702f43a8a5cbe76144ba3f8d673c3aa454e845b7b0e00", + "embedded_binding_version": null + }, + { + "backend": "oceanbase", + "configuration": "full-server-no-vector", + "captured_at": "2026-10-25T15:39:57.593867+00:00", + "schema_sha256": "0467fd04d2e192a210684325f9639989dacda0deec3fec8df80ac75895644807", + "canonical_sha256": "630b72acf4f665f3f920878e12ccca651a7a42b2091531d0d4e225154b843cc8", + "engine_version": "5.7.25-OceanBase_CE-v4.3.5.6", + "driver_version": "1.2.0", + "profile": { + "code_enabled": false, + "vector_dimension": 0, + "embedding_normalization": null + }, + "embedding_profile": {}, + "artifact_name": "release-1.4.0-oceanbase-full-server-no-vector-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": "4d770a089e4f270e813702f43a8a5cbe76144ba3f8d673c3aa454e845b7b0e00", + "embedded_binding_version": null + }, + { + "backend": "oceanbase", + "configuration": "full-server-vector", + "captured_at": "2026-10-25T15:39:53.611582+00:00", + "schema_sha256": "8bf6ba6c0c77f0b85c0feb4fddebad601bead1fbb4a1794b7bb80b88bee835c0", + "canonical_sha256": "9cbe42c3f0a24c747a56629a8c2cca740002c3f8d42820a386fba68b6c308724", + "engine_version": "5.7.25-OceanBase_CE-v4.3.5.6", + "driver_version": "1.2.0", + "profile": { + "code_enabled": false, + "vector_dimension": 3, + "embedding_normalization": "unit" + }, + "embedding_profile": { + "model": "openai:upgrade-vector-3", + "profile_id": "upgrade-vector-3-unit", + "dimension": "3", + "normalization": "unit" + }, + "artifact_name": "release-1.4.0-oceanbase-full-server-vector-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": "4d770a089e4f270e813702f43a8a5cbe76144ba3f8d673c3aa454e845b7b0e00", + "embedded_binding_version": null + }, + { + "backend": "oceanbase", + "configuration": "full-server-vector-code", + "captured_at": "2026-10-25T15:39:52.700905+00:00", + "schema_sha256": "7e9589e01fbcd6e69691a67bd8844f0945956d9084c5ad899088b90f8a5a2e30", + "canonical_sha256": "febced49a80ce87cd03b586816488bcedd108afef682aaba77c0d68a6f5e5df7", + "engine_version": "5.7.25-OceanBase_CE-v4.3.5.6", + "driver_version": "1.2.0", + "profile": { + "code_enabled": true, + "vector_dimension": 4, + "embedding_normalization": "unit" + }, + "embedding_profile": { + "model": "openai:upgrade-vector-4", + "profile_id": "upgrade-vector-4-unit", + "dimension": "4", + "normalization": "unit" + }, + "artifact_name": "release-1.4.0-oceanbase-full-server-vector-code-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": "4d770a089e4f270e813702f43a8a5cbe76144ba3f8d673c3aa454e845b7b0e00", + "embedded_binding_version": null + }, + { + "backend": "seekdb", + "configuration": "full-server-code", + "captured_at": "2026-10-25T15:38:59.097834+00:00", + "schema_sha256": "ff7781f40dfd0746cd99175b2698840e8652193532b75fff4bde7e1c5188d11d", + "canonical_sha256": "250eeaea4bdc5b224b794aaa27b9dbee9dcddaac719065897c23764a2069ed28", + "engine_version": "5.7.25-OceanBase seekdb-v1.4.0.0", + "driver_version": "1.2.0", + "profile": { + "code_enabled": true, + "vector_dimension": 0, + "embedding_normalization": null + }, + "embedding_profile": {}, + "artifact_name": "release-1.4.0-seekdb-full-server-code-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": "4d770a089e4f270e813702f43a8a5cbe76144ba3f8d673c3aa454e845b7b0e00", + "embedded_binding_version": "1.4.0.post1" + }, + { + "backend": "seekdb", + "configuration": "full-server-no-vector", + "captured_at": "2026-10-25T15:39:03.075746+00:00", + "schema_sha256": "a16258f7ccbf7b60d5f0c22296f3399190d97f5963e03ddf4a2c1413095f59e4", + "canonical_sha256": "5a6d885d2cbbb3c41e420f6ea5277722d568992a8ef5c89b521975118e15be95", + "engine_version": "5.7.25-OceanBase seekdb-v1.4.0.0", + "driver_version": "1.2.0", + "profile": { + "code_enabled": false, + "vector_dimension": 0, + "embedding_normalization": null + }, + "embedding_profile": {}, + "artifact_name": "release-1.4.0-seekdb-full-server-no-vector-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": "4d770a089e4f270e813702f43a8a5cbe76144ba3f8d673c3aa454e845b7b0e00", + "embedded_binding_version": "1.4.0.post1" + }, + { + "backend": "seekdb", + "configuration": "full-server-vector", + "captured_at": "2026-10-25T15:39:03.449384+00:00", + "schema_sha256": "dd30076cea6912a71583ea593c2cf56ebf131f858f0b73f42ecb48a4abd15bbb", + "canonical_sha256": "9d335410d4bc9c26d27171cb88f9e5320fa934db488706ff986acf6cb82096e7", + "engine_version": "5.7.25-OceanBase seekdb-v1.4.0.0", + "driver_version": "1.2.0", + "profile": { + "code_enabled": false, + "vector_dimension": 3, + "embedding_normalization": "unit" + }, + "embedding_profile": { + "model": "openai:upgrade-vector-3", + "profile_id": "upgrade-vector-3-unit", + "dimension": "3", + "normalization": "unit" + }, + "artifact_name": "release-1.4.0-seekdb-full-server-vector-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": "4d770a089e4f270e813702f43a8a5cbe76144ba3f8d673c3aa454e845b7b0e00", + "embedded_binding_version": "1.4.0.post1" + }, + { + "backend": "seekdb", + "configuration": "full-server-vector-code", + "captured_at": "2026-10-25T15:39:00.112440+00:00", + "schema_sha256": "ce1e892d319dc0f01df44114addeb2a40e5d091481f57ed1198ae1dc2cd1fd6c", + "canonical_sha256": "d8e7cb7d00874694f5d817f0f707910fe42d37db006faa8c3d46caaaf5c6e33a", + "engine_version": "5.7.25-OceanBase seekdb-v1.4.0.0", + "driver_version": "1.2.0", + "profile": { + "code_enabled": true, + "vector_dimension": 4, + "embedding_normalization": "unit" + }, + "embedding_profile": { + "model": "openai:upgrade-vector-4", + "profile_id": "upgrade-vector-4-unit", + "dimension": "4", + "normalization": "unit" + }, + "artifact_name": "release-1.4.0-seekdb-full-server-vector-code-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": "4d770a089e4f270e813702f43a8a5cbe76144ba3f8d673c3aa454e845b7b0e00", + "embedded_binding_version": "1.4.0.post1" + }, + { + "backend": "sqlite", + "configuration": "full-server-code", + "captured_at": "2026-10-25T15:38:59.263298+00:00", + "schema_sha256": "8c0513052c80b0bdc0a0051d3016b496df60fdeb292874f100b949022892978a", + "canonical_sha256": "374ad3217e4dc286c3942708da52cc8262e30581e2b836bedd0c4e693f1b75bc", + "engine_version": "3.45.1", + "driver_version": "0.22.1", + "profile": { + "code_enabled": true, + "vector_dimension": 0, + "embedding_normalization": null + }, + "embedding_profile": {}, + "artifact_name": "release-1.4.0-sqlite-full-server-code-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": null, + "embedded_binding_version": null + }, + { + "backend": "sqlite", + "configuration": "full-server-no-vector", + "captured_at": "2026-10-25T15:38:59.909168+00:00", + "schema_sha256": "313dc3bf280d5d28f4692a35434e19750e1d70c3e373777e4cacaee2332630e2", + "canonical_sha256": "374ad3217e4dc286c3942708da52cc8262e30581e2b836bedd0c4e693f1b75bc", + "engine_version": "3.45.1", + "driver_version": "0.22.1", + "profile": { + "code_enabled": false, + "vector_dimension": 0, + "embedding_normalization": null + }, + "embedding_profile": {}, + "artifact_name": "release-1.4.0-sqlite-full-server-no-vector-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": null, + "embedded_binding_version": null + }, + { + "backend": "sqlite", + "configuration": "full-server-vector", + "captured_at": "2026-10-25T15:39:48.283922+00:00", + "schema_sha256": "ad11f9117a10549a8ea8866c06457cd6adbc20b3b326a0365beeaf06c0c42a69", + "canonical_sha256": "ca12a9f4af49fc3da90c8ebacc4d1cba4a6853e9bc201d7b4bdaa0d672b771d3", + "engine_version": "3.45.1", + "driver_version": "0.22.1", + "profile": { + "code_enabled": false, + "vector_dimension": 3, + "embedding_normalization": "unit" + }, + "embedding_profile": { + "model": "openai:upgrade-vector-3", + "profile_id": "upgrade-vector-3-unit", + "dimension": "3", + "normalization": "unit" + }, + "artifact_name": "release-1.4.0-sqlite-full-server-vector-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": null, + "embedded_binding_version": null + }, + { + "backend": "sqlite", + "configuration": "full-server-vector-code", + "captured_at": "2026-10-25T15:38:59.726121+00:00", + "schema_sha256": "5a5f2c51eb7b08fb0ecdf5ad1d64d1fc05476968a5ccc710b2c780cdcefa9fd5", + "canonical_sha256": "1896624bd1abb545a5dbdf51058d036b921f948590fab5e5f59bbf38952c323f", + "engine_version": "3.45.1", + "driver_version": "0.22.1", + "profile": { + "code_enabled": true, + "vector_dimension": 4, + "embedding_normalization": "unit" + }, + "embedding_profile": { + "model": "openai:upgrade-vector-4", + "profile_id": "upgrade-vector-4-unit", + "dimension": "4", + "normalization": "unit" + }, + "artifact_name": "release-1.4.0-sqlite-full-server-vector-code-4632f4f5c4f83b4f1040b6261b480b4da6b76168", + "parser_sha256": null, + "embedded_binding_version": null + } + ], + "registration_notes": [ + "SQLite source rows are frozen from the released no-vector schema; vector rows exactly match the existing frozen profile.", + "Native initialization statements come from real released seekDB reflection; local database qualifiers and AUTO_INCREMENT runtime counters are removed without changing logical schema.", + "OceanBase differs only by the registered optional access-audit auto_increment_mode=order.", + "The bridge preserves current Candidate rows and creates empty retained tables solely to join the shared schema; no old Candidate history is invented." + ] +} diff --git a/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/versions/r1400_release_baseline.py b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/versions/r1400_release_baseline.py new file mode 100644 index 0000000000..f58e7c33e3 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/versions/r1400_release_baseline.py @@ -0,0 +1,38 @@ +# 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. + +"""Freeze the complete released Server 1.4.0 SQLite baseline.""" + +import json +from pathlib import Path + +from alembic import context, op +from sqlalchemy import text + +revision = "r1400" +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 = context.config.attributes["configuration"]["processing_manifest"] + 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_sources/1.4.0/versions/s1400_full_server.py b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/versions/s1400_full_server.py new file mode 100644 index 0000000000..1d9c5bf501 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/server_sources/1.4.0/versions/s1400_full_server.py @@ -0,0 +1,65 @@ +# 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. + +"""Bridge released Server 1.4.0 to the canonical shared schema checkpoint.""" + +import json +from pathlib import Path + +from alembic import op +from sqlalchemy import inspect + +revision = "s1400" +down_revision = "r1400" + + +def upgrade(): + connection = op.get_bind() + root = Path(__file__).parents[1] + target = json.loads((root / "schemas/sqlite.json").read_text())["target"] + # The source already has the current Candidate envelope. Copy every source + # column; only the new nullable Topic attempt fields remain unknown. + 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_release1400_" + 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 table and inspected column identifiers. + connection.exec_driver_sql(f"DROP TABLE {name}") + connection.exec_driver_sql(f"ALTER TABLE {temporary} RENAME TO {name}") + for kind, name, _owner, ddl in target: + if kind == "table" and name == "pc_topic_memory_work_blocks": + connection.exec_driver_sql(ddl) + for kind, _name, owner, ddl in target: + if kind == "index" and owner in rebuild | {"pc_topic_memory_work_blocks"}: + connection.exec_driver_sql(ddl) + # Keep the shared schema shape without inventing 1.3 Candidate history. + # The released 1.4 source has no old Candidate envelopes to archive. + dream = json.loads((root / "dream/schema.json").read_text()) + for entry in dream["tables"].values(): + connection.exec_driver_sql(entry["retained"]["sqlite"]) + 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' WHERE holder_id <> 'migration-s1400' OR lease_expires_at IS NULL OR lease_expires_at <> '1970-01-01 00:00:00.000000'" + ) diff --git a/src/powercontext/builtin/persistence/migrations/sources.py b/src/powercontext/builtin/persistence/migrations/sources.py new file mode 100644 index 0000000000..32d042f247 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/sources.py @@ -0,0 +1,93 @@ +# 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. + +"""Choose an immutable source history using the pinned inventory and main intent.""" + +from collections.abc import Mapping +from typing import Any + +from .bundle import MigrationBundle +from .models import MigrationError, digest +from .mysql_schema import MySQLSchema + + +class MigrationSources: + def __init__(self, default: MigrationBundle, sources: tuple[MigrationBundle, ...]) -> None: + self.default = default + self.sources = sources + revisions = [source.source_revision for source in sources] + if None in revisions or len(set(revisions)) != len(revisions): + raise MigrationError("invalid_bundle", "Source histories need distinct registered source revisions.") + self.revisions = tuple( + dict.fromkeys(revision for bundle in (default, *sources) for revision in bundle.revisions) + ) + + def from_evidence(self, evidence: Mapping[str, Any] | None) -> MigrationBundle: + if evidence is None: + return self.default + # source_revision identifies this maintenance window's recovery point. + # Later windows start at a shared revision; the completed release history + # still identifies the original source chain. Selection does not validate + # those checksums: the runner must check every recorded revision. + recorded = evidence.get("release_checksums", {}) + if not isinstance(recorded, Mapping): + raise MigrationError("recovery_required", "The recorded migration history is invalid.") + matches = [ + source + for source in self.sources + if source.source_revision == evidence.get("source_revision") or source.source_revision in recorded + ] + if len(matches) > 1: + raise MigrationError("recovery_required", "The recorded history selects multiple source chains.") + return matches[0] if matches else self.default + + def select( + self, + *, + evidence: Mapping[str, Any] | None, + revision: str | None, + initialized: bool, + inventory: list[list[Any]] | dict[str, Any], + embedded_code: bool = False, + ) -> MigrationBundle: + if evidence is not None: + return self.from_evidence(evidence) + for source in self.sources: + if initialized and revision == source.source_revision: + # Selection does not authorize recovery without its original journal. + return source + if initialized or not self.sources: + return self.default + matches = [ + bundle + for bundle in (self.default, *self.sources) + if self._matches(bundle, inventory, embedded_code=embedded_code) + ] + if len(matches) > 1: + raise MigrationError("invalid_bundle", "The source layout matches multiple registered histories.") + return matches[0] if matches else self.default + + @staticmethod + def _matches(bundle, inventory, *, embedded_code: bool) -> bool: + if isinstance(inventory, list): + excluded = {"pc_schema_revision", *bundle.retained_objects} + fingerprint = digest([row for row in inventory if row[1] not in excluded and row[2] not in excluded]) + return fingerprint in bundle.baselines + schema = MySQLSchema(bundle, embedded_code=embedded_code) + try: + return schema.identify(inventory) in bundle.baselines.values() + except MigrationError as error: + if error.code != "incompatible_schema": + raise + return False diff --git a/src/powercontext/builtin/persistence/migrations/sqlite.py b/src/powercontext/builtin/persistence/migrations/sqlite.py index 2664f2f440..04bae947a3 100644 --- a/src/powercontext/builtin/persistence/migrations/sqlite.py +++ b/src/powercontext/builtin/persistence/migrations/sqlite.py @@ -39,6 +39,8 @@ 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 +from .sources import MigrationSources _VERSION_DDL = ( "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) NOT NULL, " @@ -89,6 +91,50 @@ def consistent_recovery_point(self) -> Self: return self +def read_maintenance_evidence(path: Path, database_id: str) -> dict[str, Any] | None: + """Read the original journal and require its recorded database identity.""" + if not path.exists(): + return None + try: + value = _MaintenanceEvidence.model_validate_json(path.read_text(encoding="utf-8")) + except (OSError, ValueError) as error: + raise MigrationError("recovery_required", "The external maintenance evidence cannot be read safely.") from error + if value.database_id != database_id: + raise MigrationError("recovery_required", "The external maintenance evidence belongs to another target.") + return value.model_dump(mode="json") + + +def verify_release_checksums(recorded: Mapping[str, str], checksums: Mapping[str, str]) -> None: + """Require every recorded revision to retain its released resources.""" + for revision, checksum in recorded.items(): + if checksums.get(revision) != checksum: + raise MigrationError("checksum_conflict", "A recorded released migration resource changed.") + + +def verify_retained_objects( + inventory: list[list[Any]], + revision: str | None, + *, + bundle: MigrationBundle, + evidence: Mapping[str, Any] | None, +) -> None: + """Check historical retention without requiring unrelated domain readiness.""" + for name in bundle.retained_objects: + objects = [row for row in inventory if row[1] == name or row[2] == name] + if objects and bundle.retained_revisions[name] in bundle.pending(revision): + raise MigrationError("recovery_required", "A retained object exists before its introducing revision.") + if objects and digest(objects) not in bundle.retained_fingerprints.get(name, []): + raise MigrationError("incompatible_schema", "A retained historical object has an unregistered shape.") + required = set(evidence["retained_objects"]) if evidence else set() + if evidence: + # A revision can commit before its retained inventory is recorded. + # Adoption at the current revision did not execute that revision. + committed = set(bundle.pending(evidence["source_revision"])) - set(bundle.pending(revision)) + required.update(name for name, introduced in bundle.retained_revisions.items() if introduced in committed) + if any(not any(row[1] == name for row in inventory) for name in required): + raise MigrationError("recovery_required", "A required retained table was removed without a cleanup revision.") + + class SQLiteMigrationRunner: """Keep business writers stopped throughout this synchronous executor. @@ -104,10 +150,13 @@ def __init__( configuration: Mapping[str, Any] | None = None, backup_provider: BackupProvider | None = None, evidence_directory: Path | None = None, + source_bundles: tuple[MigrationBundle, ...] = (), ) -> None: self.database = database.expanduser().resolve() self.bundle = bundle - self.configuration_digest = digest(configuration or {}) + self.sources = MigrationSources(bundle, source_bundles) + 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" @@ -121,6 +170,13 @@ def plan( ) -> MigrationPlan: """Inspect without creating a database, parent, lock or external evidence.""" self.bundle.assert_unchanged() + if self.bundle.readiness_scope == "server": + from .connections import BackendIdentity + from .mysql import require_completed_domain_maintenance + + require_completed_domain_maintenance( + self.evidence_path.parent, BackendIdentity("sqlite", self._database_id(), self.database.name) + ) if backup_policy not in {"auto", "manual", "skip"}: raise MigrationError("invalid_backup_policy", "Choose auto, manual or skip.") if not self.database.exists(): @@ -144,7 +200,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 +222,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 +237,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 +248,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 +265,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 +306,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: @@ -268,14 +341,22 @@ def _inspect(self, connection: Connection, **options: Any) -> MigrationPlan: initialized = bool(controls) if initialized and controls != [["table", "pc_schema_revision", "pc_schema_revision", _VERSION_DDL]]: raise MigrationError("recovery_required", "The Alembic version table has an unknown shape.") + revisions = ( + list(connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalars()) + if initialized + else [] + ) + if len(revisions) > 1: + raise MigrationError("incompatible_schema", "Multiple revision heads are unsupported.") + revision = revisions[0] if revisions else None + self.bundle = self.sources.select( + evidence=self._evidence(), revision=revision, initialized=initialized, inventory=inventory + ) + self.bundle.assert_unchanged() fingerprint = digest( sqlite_inventory(connection, exclude=_CONTROL_NAMES | frozenset(self.bundle.retained_objects)) ) if initialized: - revisions = list(connection.exec_driver_sql("SELECT version_num FROM pc_schema_revision").scalars()) - if len(revisions) > 1: - raise MigrationError("incompatible_schema", "Multiple revision heads are unsupported.") - revision = revisions[0] if revisions else None self._verify_schema(connection, revision) if self.bundle.pending(revision) and self._evidence() is None: raise MigrationError( @@ -285,11 +366,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,15 +393,14 @@ 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: state = "uninitialized" evidence = self._evidence() if evidence: - for recorded, checksum in evidence.get("release_checksums", {}).items(): - if self.bundle.checksums.get(recorded) != checksum: - raise MigrationError("checksum_conflict", "A recorded released migration resource changed.") + verify_release_checksums(evidence["release_checksums"], self.bundle.checksums) if evidence["state"] != "complete": if ( evidence["bundle_checksum"] != self.bundle.checksum @@ -335,11 +424,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, @@ -455,17 +547,7 @@ def _pristine_preparation(evidence: dict[str, Any], revision: str | None, finger ) def _evidence(self) -> dict[str, Any] | None: - if not self.evidence_path.exists(): - return None - try: - value = _MaintenanceEvidence.model_validate_json(self.evidence_path.read_text(encoding="utf-8")) - except (OSError, ValueError) as error: - raise MigrationError( - "recovery_required", "The external maintenance evidence cannot be read safely." - ) from error - if value.database_id != self._database_id(): - raise MigrationError("recovery_required", "The external maintenance evidence belongs to another target.") - return value.model_dump(mode="json") + return read_maintenance_evidence(self.evidence_path, self._database_id()) def _write_evidence(self, evidence: dict[str, Any]) -> None: directory = self.evidence_path.parent @@ -501,6 +583,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,38 +595,26 @@ 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: - objects = [row for row in inventory if row[1] == name or row[2] == name] - if objects and self.bundle.retained_revisions[name] in self.bundle.pending(revision): - raise MigrationError("recovery_required", "A retained object exists before its introducing revision.") - if objects and digest(objects) not in self.bundle.retained_fingerprints.get(name, []): - raise MigrationError("incompatible_schema", "A retained historical object has an unregistered shape.") - evidence = self._evidence() - required = set(evidence["retained_objects"]) if evidence else set() - if evidence: - # A revision can commit before its retained inventory is recorded. - # Adoption at the current revision did not execute that revision. - committed = set(self.bundle.pending(evidence["source_revision"])) - set(self.bundle.pending(revision)) - required.update( - name for name, introduced in self.bundle.retained_revisions.items() if introduced in committed - ) - if any(not any(row[1] == name for row in inventory) for name in required): - raise MigrationError( - "recovery_required", "A required retained table was removed without a cleanup revision." - ) + verify_retained_objects(inventory, revision, bundle=self.bundle, evidence=self._evidence()) def _verify_integrity(self, connection: Connection) -> None: if any( 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..3b00999970 --- /dev/null +++ b/src/powercontext/builtin/persistence/migrations/vector_profile.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. + +"""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) + manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8")) + self.source_revision = manifest.get("source_revision", "s1300") + 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 = { + self.source_revision: 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 = {self.source_revision: "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..4ffa376e8d 100644 --- a/src/powercontext/builtin/persistence/seekdb/profile.py +++ b/src/powercontext/builtin/persistence/seekdb/profile.py @@ -109,10 +109,14 @@ async def open( config: SeekDBConfig, *, tables: tuple[Table, ...], + _archive_recovery: bool = False, ) -> AsyncIterator[SeekDBProfile]: """Start seekdb locally and connect through its async Unix socket.""" + from powercontext.builtin.persistence.migrations.locking import assert_no_local_migration + path = config.path.expanduser().resolve() + assert_no_local_migration(path) path.parent.mkdir(parents=True, exist_ok=True) module = _load_binding() instance = await _open_instance(module, path) @@ -121,7 +125,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 7e7c93a536..c3d39537fb 100644 --- a/src/powercontext/builtin/persistence/sqlite/atomic_memory_index.py +++ b/src/powercontext/builtin/persistence/sqlite/atomic_memory_index.py @@ -283,6 +283,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 466cf4b919..2a1ac85f0c 100644 --- a/src/powercontext/builtin/persistence/topic_memory.py +++ b/src/powercontext/builtin/persistence/topic_memory.py @@ -98,7 +98,9 @@ def __init__( raise InvalidRepositoryArgumentError("index", "Topic Memory indexes must implement replace_many") 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 @@ -129,11 +131,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..309a716921 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 @@ -116,11 +117,17 @@ async def _identity_batch( async def _record( - connection: AsyncConnection, artifacts: ArtifactRepository, scope_id: str, artifact_id: str + connection: AsyncConnection, + artifacts: ArtifactRepository, + scope_id: str, + artifact_id: str, + *, + for_update: bool, ) -> AtomicMemoryRecord: state_repository = AtomicMemoryStateRepository() state = await state_repository.get(connection, scope_id, artifact_id) - await state_repository.require_summary(connection, scope_id, artifact_id, state) + if for_update: + await state_repository.require_summary(connection, scope_id, artifact_id, state) artifact = await artifacts.latest(connection, scope_id, AtomicMemory.family, artifact_id) if not isinstance(artifact, AtomicMemory): raise AtomicMemoryIndexError("authority-content", "Atomic Memory has an unsupported authoritative content type") @@ -178,7 +185,11 @@ async def _projection_differences( async def verify_atomic_memory_current( connection: AsyncConnection, index: AtomicMemoryIndex, /, *, batch_size: int = 100 ) -> AtomicMemoryProjectionRebuildReport: - """Check every current identity against authority, including post-migration creations.""" + """Check current identities without taking business row locks. + + The caller owns a consistent read snapshot or an exclusive maintenance + transaction, including for identities created after migration. + """ await _require_authority(connection) artifacts = ArtifactRepository((AtomicMemory,)) @@ -189,7 +200,7 @@ async def verify_atomic_memory_current( active = 0 while batch := await _identity_batch(connection, after, batch_size): for scope_id, artifact_id in batch: - record = await _record(connection, artifacts, scope_id, artifact_id) + record = await _record(connection, artifacts, scope_id, artifact_id, for_update=False) after = scope_id, artifact_id if record.state.state is not AtomicMemoryStateValue.ACTIVE: continue @@ -236,6 +247,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, for_update=True) + 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, @@ -279,7 +321,7 @@ async def rebuild_atomic_memory_projection( break for scope_id, artifact_id in batch: async with database.transaction() as connection: - before = await _record(connection, artifacts, scope_id, artifact_id) + before = await _record(connection, artifacts, scope_id, artifact_id, for_update=True) prepared = ( await publisher.prepare(before.artifact.content) if before.state.state is AtomicMemoryStateValue.ACTIVE @@ -287,7 +329,7 @@ async def rebuild_atomic_memory_projection( ) async with database.transaction() as connection: await artifacts.lock_heads(connection, scope_id, (before.ref,)) - current = await _record(connection, artifacts, scope_id, artifact_id) + current = await _record(connection, artifacts, scope_id, artifact_id, for_update=True) if current.as_read() != before.as_read(): raise AtomicMemoryIndexError( "rebuild-conflict", "Atomic Memory authority changed during projection preparation" @@ -325,4 +367,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 a95239d3a1..331aff3f74 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 @@ -1111,7 +1111,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, @@ -1154,36 +1154,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, @@ -1234,34 +1252,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, @@ -1316,7 +1342,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) @@ -1326,9 +1352,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/http/_generated/operations.py b/src/powercontext/http/_generated/operations.py index 9f817649fc..b96333617a 100644 --- a/src/powercontext/http/_generated/operations.py +++ b/src/powercontext/http/_generated/operations.py @@ -209,7 +209,7 @@ OPENAPI_VERSION = "3.0.3" API_TITLE = "PowerContext API" API_DESCRIPTION = "Remote PowerContext transport. Runtime behavior is reported by /v1/capabilities." -API_VERSION = "1.4.0" +API_VERSION = "1.4.1" FEATURE_CONTRACTS: dict[str, dict[str, JsonValue]] = { "access.principal": {"version": {"major": 1, "minor": 0}, "operations": ["get_access_principal"]}, "scope.selection": { diff --git a/src/powercontext/http/_generated/schema.py b/src/powercontext/http/_generated/schema.py index ccc01c5acd..3f4c08e56b 100644 --- a/src/powercontext/http/_generated/schema.py +++ b/src/powercontext/http/_generated/schema.py @@ -7,7 +7,7 @@ "info": { "title": "PowerContext API", "description": "Remote PowerContext transport. Runtime behavior is reported by /v1/capabilities.", - "version": "1.4.0", + "version": "1.4.1", }, "paths": { "/v1/atomic-memory/list": { diff --git a/src/powercontext/server/cli.py b/src/powercontext/server/cli.py index 9741636e48..8de06de50c 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,116 @@ 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.server.identity import SERVER_IDENTITY_TABLE + + def owned(connection): + if connection is None: + return False + tables = inspect(connection).get_table_names() + if "pc_schema_revision" in tables: + return True + # Ownership is independent of release and optional configuration. The + # shared runner must validate the complete source before any writes; + # an unknown or misconfigured Server must not fall back to SDK upkeep. + server_owned = SERVER_IDENTITY_TABLE.name in tables + if server_owned and not allow_historical: + raise database_migration.MigrationError( + "migration_required", + "Complete the registered full Server upgrade before partial maintenance.", + ) + return server_owned + + 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)) + + async def inspect_sqlite(): + try: + async with ( + SQLiteProfile.open_readonly(database) as profile, + profile.database.transaction() as connection, + ): + return await connection.run_sync(owned) + 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 +332,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 +362,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 +430,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: @@ -540,6 +750,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..bd33f9fd1b 100644 --- a/src/powercontext/server/database_migration.py +++ b/src/powercontext/server/database_migration.py @@ -28,17 +28,20 @@ from powercontext.builtin.persistence.migrations import MigrationError, MigrationPlan, MigrationResult from powercontext.builtin.persistence.migrations.deployment import MigrationRunner, deployment_runner +from powercontext.builtin.persistence.migrations.revision_names import describe_revision from powercontext.server.configuration import ( ServerConfigurationError, 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 +57,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 +86,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 +97,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 +121,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 +176,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 +188,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 +246,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) @@ -233,19 +272,25 @@ 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}, - indent=indent, - ) - ) + payload: dict[str, object] = {} + if value.readiness_scope == "server": + if isinstance(value, MigrationPlan): + payload.update( + source_migration=describe_revision(value.source_revision), + target_migration=describe_revision(value.target_revision), + pending_migrations=[describe_revision(revision) for revision in value.revisions], + ) + else: + payload["migration"] = describe_revision(value.revision) + payload.update(value.model_dump(mode="json")) + payload["server_ready"] = value.readiness_scope == "server" and value.state == "ready" + typer.echo(json.dumps(payload, indent=indent)) 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 +316,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 +373,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 66192aa85d..7a81c1f39e 100644 --- a/src/powercontext/server/factory.py +++ b/src/powercontext/server/factory.py @@ -183,6 +183,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_candidate_schema_replay.py b/tests/builtin/persistence/test_candidate_schema_replay.py new file mode 100644 index 0000000000..db67650e2d --- /dev/null +++ b/tests/builtin/persistence/test_candidate_schema_replay.py @@ -0,0 +1,270 @@ +# 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. + +"""Real schema replay; each native case owns a dedicated scratch database. + +The shared fixtures keep a seekDB engine lease during one scenario. Connection +interruption is exercised here; native engine crash acceptance remains separate. +""" + +from __future__ import annotations + +import json +import os +from pathlib import Path + +import pytest +from replay_candidate_migration import CandidateReplay, ReplayInterrupted, inventory, objects +from sqlalchemy import create_engine, inspect, text + +from powercontext.builtin.persistence.migrations.models import digest +from tests.builtin.persistence import test_full_server_migrations as sqlite_fixtures +from tests.builtin.persistence import test_mysql_migration_runner as native_fixtures + +sqlite_source = sqlite_fixtures.source +backend = native_fixtures.backend + +_INTERRUPTIONS = { + "complete": None, + "copy": "INSERT", + "rename": "ALTER TABLE pc_artifact_candidate_versions RENAME TO pc_candidate_versions", +} + + +def _directory(tmp_path, backend, scenario): + base = Path(os.environ.get("POWERCONTEXT_CANDIDATE_REPLAY_OUTPUT", str(tmp_path / "replay"))) + return base / f"{backend}-{scenario}" + + +def _seed(connection, scope): + for number in (1, 2): + connection.execute( + text( + "INSERT INTO pc_artifact_candidate_versions " + "(scope_id,candidate_id,version,family,proposal,source_refs,artifact_refs) " + "VALUES (:scope,'candidate',:version,'custom-family',:proposal,:refs,:refs)" + ), + {"scope": scope, "version": number, "proposal": f'{{"history":{number}}}'.encode(), "refs": b"[]"}, + ) + connection.execute( + text( + "INSERT INTO pc_artifact_candidate_heads (scope_id,candidate_id,family,version,status) " + "VALUES (:scope,'candidate','custom-family',2,'pending')" + ), + {"scope": scope}, + ) + connection.execute( + text( + "INSERT INTO pc_profile_policies " + "(scope_id,generation_enabled,activation_mode,pending_candidate_id,version,updated_at) " + "VALUES (:scope,0,'review_required','candidate',1,'2026-01-01 00:00:00')" + ), + {"scope": scope}, + ) + connection.commit() + + +def _history(connection, bundle, *, upgraded=False): + entries = json.loads((bundle.snapshot_directory / "dream/schema.json").read_text())["tables"] + rows = {} + for name, entry in entries.items(): + table = entry["target_name"] if upgraded else entry["source_name"] + columns = ", ".join(entry["source_columns"]) + order = "scope_id, candidate_id" + (", version" if name.endswith("versions") else "") + values = [ + tuple(row) + for row in connection.exec_driver_sql( + f"SELECT {columns} FROM {table} ORDER BY {order}" # noqa: S608 -- frozen identifiers + ) + ] + rows[name] = values + if upgraded: + retained = entry["retained_name"] + assert values == [ + tuple(row) + for row in connection.exec_driver_sql( + f"SELECT {columns} FROM {retained} ORDER BY {order}" # noqa: S608 -- frozen identifiers + ) + ] + return rows + + +def _history_digest(rows): + return digest({ + name: [[value.hex() if isinstance(value, bytes) else value for value in row] for row in values] + for name, values in rows.items() + }) + + +def _foreign_keys(connection): + assert any( + key["referred_table"] == "pc_candidate_versions" + for key in inspect(connection).get_foreign_keys("pc_candidate_heads") + ) + assert any( + key["referred_table"] == "pc_candidate_heads" + for key in inspect(connection).get_foreign_keys("pc_profile_policies") + ) + + +def _cross_table_effects(replay): + version_rename = next( + record + for record in replay.records + if record["operation"].startswith("ALTER TABLE pc_artifact_candidate_versions RENAME") + ) + assert "table:pc_artifact_candidate_heads" in version_rename["changes"] + head_rename = next( + record + for record in replay.records + if record["operation"].startswith("ALTER TABLE pc_artifact_candidate_heads RENAME") + ) + assert "table:pc_profile_policies" in head_rename["changes"] + # The generated changes must reconstruct each full inventory, including + # objects not named by the SQL. Never infer a delta from the ALTER target. + reconstructed = {} + for record in replay.records: + for name, value in record["changes"].items(): + if value is None: + reconstructed.pop(name) + else: + reconstructed[name] = value + state = json.loads((replay.output / record["inventory"]).read_text()) + assert reconstructed == objects(state) + assert digest(state) == record["schema_sha256"] + + +@pytest.mark.parametrize("scenario", _INTERRUPTIONS) +def test_candidate_replay_sqlite(sqlite_source, tmp_path, scenario): + runner = sqlite_source + replay = CandidateReplay( + runner.bundle, "sqlite", _directory(tmp_path, "sqlite", scenario), interrupt=_INTERRUPTIONS[scenario] + ) + interrupted = None + engine = create_engine(f"sqlite:///{runner.database}") + try: + with engine.connect() as connection: + _seed(connection, "scope") + original = _history(connection, runner.bundle) + baseline = replay.begin(connection) + if scenario != "complete": + with replay.sqlite(), pytest.raises(ReplayInterrupted): + sqlite_fixtures.apply(runner) + interrupted = json.loads(runner.evidence_path.read_text()) + with engine.connect() as connection: + assert replay.capture(connection, "after rollback") == baseline + assert _history(connection, runner.bundle) == original + assert runner.plan().state == "recovery_required" + with replay.sqlite(): + result = sqlite_fixtures.apply(runner) + if scenario != "complete": + assert interrupted is not None + completed = json.loads(runner.evidence_path.read_text()) + assert completed["maintenance_window_id"] == interrupted["maintenance_window_id"] + assert result.backup_ref == interrupted["backup"]["location"] + assert runner.verify().state == "ready" + with engine.connect() as connection: + assert _history(connection, runner.bundle, upgraded=True) == original + _foreign_keys(connection) + final = inventory(connection) + assert not sqlite_fixtures.apply(runner).changed + with engine.connect() as connection: + assert inventory(connection) == final + assert _history(connection, runner.bundle, upgraded=True) == original + _cross_table_effects(replay) + assert any(record.get("matches_registered_checkpoint") for record in replay.records) + replay.finish( + history_sha256=_history_digest(original), + repeat_apply_changed=False, + interruption=scenario, + interruption_semantics="transactional rollback", + final_schema_sha256=digest(final), + ) + finally: + engine.dispose() + + +@pytest.mark.parametrize("scenario", _INTERRUPTIONS) +def test_candidate_replay_native(backend, tmp_path, scenario): + native_fixtures._load_full_server_baseline(backend) + runner = native_fixtures._full_server_runner(backend) + replay = CandidateReplay( + runner.bundle, + backend.config.kind, + _directory(tmp_path, backend.config.kind, scenario), + interrupt=_INTERRUPTIONS[scenario], + ) + + def seed(connection, _identity, _guard): + _seed(connection, "historical") + return _history(connection, runner.bundle), replay.begin(connection) + + original, baseline = backend.connections().run(seed, writable=True) + interrupted = None + journal = None + if scenario != "complete": + with replay.native(runner.schema), pytest.raises(ReplayInterrupted): + native_fixtures._apply(runner, policy="manual") + (journal,) = backend.evidence_directory.rglob("maintenance.json") + interrupted = json.loads(journal.read_text()) + assert interrupted["pending_revision"] == "s1400" + + def inspect_interruption(connection, _identity, _guard): + state = replay.capture(connection, "after connection rollback") + assert state != baseline # MySQL DDL survives rollback/disconnection. + if scenario == "copy": + # The copy DML rolled back, but its preceding CREATE committed. + assert ( + connection.exec_driver_sql("SELECT COUNT(*) FROM pc_retained_dream_candidate_versions").scalar_one() + == 0 + ) + return state + + backend.connections().run(inspect_interruption) + recovery = native_fixtures._full_server_runner(backend).plan(backup_policy="manual") + assert recovery.state == "recovery_required" + token = (recovery.coordination or {}).get("pending_run_id") + # The injected interruption follows a returned statement and a closed + # owned connection. Only that controlled case permits acknowledgement. + runner = native_fixtures._full_server_runner(backend, acknowledge_previous_run=token) + with replay.native(runner.schema): + native_fixtures._apply(runner, policy="manual") + assert runner.verify().state == "ready" + if scenario != "complete": + assert journal is not None and interrupted is not None + completed = json.loads(journal.read_text()) + assert completed["maintenance_window_id"] == interrupted["maintenance_window_id"] + assert completed["manual_ref"] == interrupted["manual_ref"] + + def final(connection, _identity, _guard): + assert _history(connection, runner.bundle, upgraded=True) == original + _foreign_keys(connection) + return inventory(connection) + + state = backend.connections().run(final) + # The recovery acknowledgement was consumed; a fresh command has no stale token. + assert not native_fixtures._apply(native_fixtures._full_server_runner(backend), policy="manual").changed + assert backend.connections().run(final) == state + _cross_table_effects(replay) + positions = { + record["registered_position"] for record in replay.records if record["registered_position"] is not None + } + assert positions == set(range(1, len(replay.manifest["registered_operations"]) + 1)) + replay.finish( + history_sha256=_history_digest(original), + repeat_apply_changed=False, + interruption=scenario, + interruption_semantics="committed DDL, transactional data copy", + final_schema_sha256=digest(state), + ) diff --git a/tests/builtin/persistence/test_database_migration_backends.py b/tests/builtin/persistence/test_database_migration_backends.py index 43a004d85d..6c6ebcc5a5 100644 --- a/tests/builtin/persistence/test_database_migration_backends.py +++ b/tests/builtin/persistence/test_database_migration_backends.py @@ -21,7 +21,6 @@ from __future__ import annotations -import asyncio import importlib.metadata import json import os @@ -35,9 +34,8 @@ from powercontext.builtin.persistence.migrations import MigrationBundle from powercontext.builtin.persistence.migrations.connections import MaintenanceConnections -from powercontext.builtin.persistence.migrations.locking import local_migration_lock from powercontext.builtin.persistence.oceanbase import OceanBaseConfig -from powercontext.builtin.persistence.seekdb import SeekDBConfig, SeekDBProfile +from powercontext.builtin.persistence.seekdb import SeekDBConfig FIXTURE = Path(__file__).resolve().parents[2] / "fixtures/database_migrations" @@ -90,15 +88,15 @@ def exercise_revisions(connection: Connection, bundle: MigrationBundle) -> None: @pytest.mark.skipif(os.environ.get("POWERCONTEXT_TEST_MIGRATION_SEEKDB") != "1", reason="real seekDB probe not enabled") def test_real_seekdb_revision_operations(short_tmp_path: Path) -> None: - async def scenario() -> None: - path = short_tmp_path / "seekdb" - # The OS lock precedes engine startup and outlives engine shutdown. - with local_migration_lock(path): - async with SeekDBProfile.open(SeekDBConfig(path=path), tables=()) as profile: - async with profile.database.engine.connect() as connection: - await connection.run_sync(lambda sync: exercise_revisions(sync, MigrationBundle(FIXTURE))) - - asyncio.run(scenario()) + adapter = MaintenanceConnections(SeekDBConfig(path=short_tmp_path / "seekdb")) + + def exercise(connection, _identity, verify): + assert connection is not None + verify() + exercise_revisions(connection, MigrationBundle(FIXTURE)) + verify() + + adapter.run(exercise, writable=True) @pytest.mark.skipif( diff --git a/tests/builtin/persistence/test_database_migration_connections.py b/tests/builtin/persistence/test_database_migration_connections.py index 05bc749720..da78fb6a38 100644 --- a/tests/builtin/persistence/test_database_migration_connections.py +++ b/tests/builtin/persistence/test_database_migration_connections.py @@ -16,7 +16,9 @@ from __future__ import annotations +import asyncio import os +import sqlite3 import subprocess import sys import time @@ -32,6 +34,7 @@ from sqlalchemy.engine import make_url from sqlalchemy.ext.asyncio import create_async_engine +from powercontext.builtin.persistence import dream_schema from powercontext.builtin.persistence.migrations import connections from powercontext.builtin.persistence.migrations.connections import BackendIdentity, MaintenanceConnections from powercontext.builtin.persistence.migrations.locking import local_migration_lock @@ -408,3 +411,93 @@ def reacquire( return target assert adapter.run(reacquire, writable=True) == identity + + +def _statement_snapshot_adapter(target): + engine = create_async_engine(f"sqlite+aiosqlite:///{target}") + + class NativeReadConnection: + dialect = SimpleNamespace(name="mysql") + + def __init__(self): + self.connection = engine.connect() + + async def __aenter__(self): + await self.connection.__aenter__() + return self + + async def __aexit__(self, *args): + return await self.connection.__aexit__(*args) + + async def exec_driver_sql(self, statement): + translated = { + "START TRANSACTION WITH CONSISTENT SNAPSHOT": "BEGIN", + "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ": "SELECT 1", + "START TRANSACTION": "SELECT 1", + }.get(statement, statement) + return await self.connection.exec_driver_sql(translated) + + async def run_sync(self, operation, *args, **kwargs): + return await self.connection.run_sync(operation, *args, **kwargs) + + async def invalidate(self): + await self.connection.invalidate() + + @event.listens_for(engine.sync_engine, "connect") + def database_name(connection, _record): + connection.create_function("DATABASE", 0, lambda: "test") + + @event.listens_for(engine.sync_engine, "before_cursor_execute", retval=True) + def ordinary_transaction(_connection, _cursor, statement, parameters, _context, _many): + # An ordinary READ COMMITTED transaction does not pin a snapshot. + return ("SELECT 1" if statement == "START TRANSACTION" else statement), parameters + + return SimpleNamespace(connect=NativeReadConnection, dispose=engine.dispose) + + +@pytest.mark.parametrize("entry", ["profile", "oceanbase", "seekdb"]) +def test_native_read_admission_keeps_one_snapshot(tmp_path, monkeypatch, entry): + # A real SQLite WAL file models MySQL READ COMMITTED's statement snapshots. + # Only the connection/protocol boundary is substituted; the native E2E + # suite proves these semantics on OceanBase and embedded seekDB themselves. + target = tmp_path / "read-consistency.db" + with sqlite3.connect(target) as writer: + writer.execute("PRAGMA journal_mode=WAL") + writer.execute("CREATE TABLE snapshot_values (value INTEGER NOT NULL)") + writer.execute("INSERT INTO snapshot_values VALUES (1)") + adapter = _statement_snapshot_adapter(target) + + def read_twice(connection, *_args, **_kwargs): + first = connection.exec_driver_sql("SELECT value FROM snapshot_values").scalar_one() + with sqlite3.connect(target) as writer: + writer.execute("UPDATE snapshot_values SET value=2") + second = connection.exec_driver_sql("SELECT value FROM snapshot_values").scalar_one() + assert (first, second) == (1, 1) + + if entry == "profile": + monkeypatch.setattr(dream_schema, "check_dream_schema", read_twice) + try: + asyncio.run(dream_schema.assert_dream_schema_ready(adapter)) + finally: + asyncio.run(adapter.dispose()) + elif entry == "oceanbase": + monkeypatch.setattr(connections, "create_async_engine", lambda *_args, **_kwargs: adapter) + monkeypatch.setattr( + connections, "_oceanbase_identity", lambda _connection: BackendIdentity("oceanbase", "isolated", "test") + ) + config = OceanBaseConfig.model_validate({"url": "mysql+aoceanbase://test@localhost:2881/test?charset=utf8mb4"}) + MaintenanceConnections(config).run(read_twice) + else: + path = tmp_path / "seekdb" + path.mkdir() + (path / "fixture").touch() + + async def open_instance(_path): + return SimpleNamespace(connection_options=lambda: {}, close=lambda: None) + + monkeypatch.setattr(connections, "_seekdb_storage_exists", lambda _path: True) + monkeypatch.setattr(connections.seekdb_profile, "_load_binding", lambda: SimpleNamespace(aopen=open_instance)) + monkeypatch.setattr(connections.seekdb_profile, "_create_engine", lambda *_args: adapter) + MaintenanceConnections(SeekDBConfig(path=path)).run(read_twice) + with sqlite3.connect(target) as writer: + assert writer.execute("SELECT value FROM snapshot_values").fetchone() == (2,) 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_migration_locking.py b/tests/builtin/persistence/test_migration_locking.py index 1da244e2d1..3df7c56f1c 100644 --- a/tests/builtin/persistence/test_migration_locking.py +++ b/tests/builtin/persistence/test_migration_locking.py @@ -108,3 +108,22 @@ def release(lock, force: bool = False) -> None: assert error.value is failure with local_migration_lock(target): pass + + +def test_startup_lock_probe_does_not_create_or_modify_files(tmp_path): + from powercontext.builtin.persistence.migrations.locking import assert_no_local_migration + + target = tmp_path / "absent" / "database" + assert_no_local_migration(target) + assert not list(tmp_path.iterdir()) + target = tmp_path / "database" + lock_path = target.with_name(target.name + ".pc-migration.lock") + with local_migration_lock(target): + original = lock_path.read_bytes() + before = lock_path.stat() + with pytest.raises(MigrationError, match="migration_locked"): + assert_no_local_migration(target) + assert lock_path.read_bytes() == original + assert lock_path.stat().st_mtime_ns == before.st_mtime_ns + assert_no_local_migration(target) + assert not target.exists() diff --git a/tests/builtin/persistence/test_migration_sources.py b/tests/builtin/persistence/test_migration_sources.py new file mode 100644 index 0000000000..719120cddd --- /dev/null +++ b/tests/builtin/persistence/test_migration_sources.py @@ -0,0 +1,301 @@ +# 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. + +"""Source-specific upgrades retain their immutable lineage across repeated runs.""" + +import json +import shutil +import sqlite3 +from contextlib import closing + +import pytest +from alembic import command +from sqlalchemy import create_engine + +from powercontext.builtin.persistence.migrations.bundle import MigrationBundle, sqlite_inventory +from powercontext.builtin.persistence.migrations.connections import MaintenanceConnections +from powercontext.builtin.persistence.migrations.deployment import server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError, digest +from powercontext.builtin.persistence.migrations.mysql import MySQLMigrationRunner +from powercontext.builtin.persistence.migrations.sources import MigrationSources +from powercontext.builtin.persistence.migrations.sqlite import SQLiteMigrationRunner +from powercontext.builtin.persistence.seekdb import SeekDBConfig + + +def _fingerprint(*statements): + engine = create_engine("sqlite://") + try: + with engine.connect() as connection: + for statement in statements: + connection.exec_driver_sql(statement) + return digest(sqlite_inventory(connection)) + finally: + engine.dispose() + + +def _revision(directory, revision, parent, *statements): + path = directory / "versions" / (revision + ".py") + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + "from alembic import op\n" + f"revision = {revision!r}\ndown_revision = {parent!r}\n" + "def upgrade():\n" + "".join(f" op.get_bind().exec_driver_sql({statement!r})\n" for statement in statements) + ) + + +@pytest.fixture +def histories(tmp_path): + base = tmp_path / "base" + overlay = tmp_path / "source" + base.mkdir() + overlay.mkdir() + builtin = server_bundle() + (base / "env.py").write_bytes((builtin.snapshot_directory / "env.py").read_bytes()) + initial = "CREATE TABLE records (value TEXT)" + intermediate = "ALTER TABLE records ADD COLUMN marker TEXT" + complete = "ALTER TABLE records ADD COLUMN target TEXT" + _revision(base, "initial", None, initial) + _revision(base, "shared", "initial", intermediate, complete) + _revision(base / "mysql", "initial", None, initial) + _revision(base / "mysql", "shared", "initial", intermediate, complete) + (base / "mysql/env.py").write_bytes((base / "env.py").read_bytes()) + mysql_layouts = { + "format": "powercontext.mysql-layouts.v1", + "revisions": {"initial": [initial], "shared": [intermediate, complete]}, + "checkpoints": {}, + } + (base / "mysql.json").write_text(json.dumps(mysql_layouts)) + source_ddl = "CREATE TABLE records (value TEXT, marker TEXT)" + source_fingerprint = _fingerprint(source_ddl) + final_fingerprint = _fingerprint(initial, intermediate, complete) + manifest = { + "revision_resources": {"initial": ["mysql.json"], "shared": ["mysql.json"]}, + "mysql_layouts": ["mysql.json"], + "readiness_scope": "server", + "sqlite_baselines": {_fingerprint(initial): "initial"}, + "sqlite_fingerprints": {"initial": [_fingerprint(initial)], "shared": [final_fingerprint]}, + "affected_objects": ["records"], + } + (base / "manifest.json").write_text(json.dumps(manifest)) + canonical = MigrationBundle(base) + (overlay / "registration.json").write_text( + json.dumps({"remove_resources": ["versions/initial.py", "mysql/versions/initial.py"]}) + ) + _revision(overlay, "r1400", None, source_ddl) + _revision(overlay, "shared", "r1400", complete) + _revision(overlay / "mysql", "r1400", None, source_ddl) + _revision(overlay / "mysql", "shared", "r1400", complete) + mysql_layouts["revisions"] = {"r1400": [source_ddl], "shared": [complete]} + (overlay / "mysql.json").write_text(json.dumps(mysql_layouts)) + manifest.update( + source_release="1.4.0", + source_revision="r1400", + revision_resources={"r1400": ["mysql.json"], "shared": ["mysql.json"]}, + sqlite_baselines={source_fingerprint: "r1400"}, + sqlite_fingerprints={"r1400": [source_fingerprint], "shared": [final_fingerprint]}, + ) + (overlay / "manifest.json").write_text(json.dumps(manifest)) + source = MigrationBundle(base, source_directory=overlay) + database = tmp_path / "source.db" + with closing(sqlite3.connect(database)) as connection, connection: + connection.execute(source_ddl) + connection.execute("INSERT INTO records VALUES ('kept', 'original')") + return canonical, source, database + + +def _runner(histories): + base, source, database = histories + return SQLiteMigrationRunner(database, base, source_bundles=(source,)) + + +def test_source_overlay_preserves_canonical_history(histories): + base, source, _database = histories + reloaded = MigrationBundle(base.directory) + assert (reloaded.checksum, reloaded.checksums) == (base.checksum, base.checksums) + assert source.revisions == ("r1400", "shared") + assert source.checksums["shared"] != base.checksums["shared"] + assert not (source.snapshot_directory / "versions/initial.py").exists() + assert (source.snapshot_directory / "env.py").read_bytes() == (base.directory / "env.py").read_bytes() + + +@pytest.mark.parametrize("location", ["base", "overlay", "registration"]) +def test_source_overlay_rejects_changed_original_inputs(histories, location): + base, source, _database = histories + path = { + "base": base.directory / "versions/initial.py", + "overlay": source.source_directory / "versions/r1400.py", + "registration": source.source_directory / "registration.json", + }[location] + path.write_text(path.read_text() + "\n") + with pytest.raises(MigrationError, match="plan_changed"): + source.assert_unchanged() + + +def test_source_upgrade_and_reopened_runner_preserve_history(histories): + runner = _runner(histories) + plan = runner.plan(backup_policy="skip") + assert plan.source_revision == "r1400" and plan.revisions == ("shared",) + result = runner.apply( + plan_id=plan.plan_id, + accepted=True, + maintenance_confirmed=True, + backup_policy="skip", + accept_no_backup=True, + ) + assert result.changed + with closing(sqlite3.connect(runner.database)) as connection: + assert connection.execute("SELECT * FROM records").fetchall() == [("kept", "original", None)] + reopened = _runner(histories) + assert reopened.verify().revision == "shared" + assert reopened.plan().bundle_checksum == histories[1].checksum + assert not reopened.apply().changed + + +def test_versioned_source_without_journal_is_rejected_before_write(histories): + _base, source, database = histories + engine = create_engine(f"sqlite:///{database}") + try: + with engine.begin() as connection: + command.stamp(source.config(connection), "r1400") + finally: + engine.dispose() + before = database.read_bytes() + with pytest.raises(MigrationError, match="recovery_required"): + _runner(histories).plan() + assert database.read_bytes() == before + + +def test_unknown_source_object_is_not_adopted(histories): + database = histories[2] + with closing(sqlite3.connect(database)) as connection, connection: + connection.execute("CREATE TABLE unknown (value TEXT)") + before = database.read_bytes() + with pytest.raises(MigrationError, match="unknown_baseline"): + _runner(histories).plan() + assert database.read_bytes() == before + + +def test_native_executor_loads_only_the_assembled_source_history(histories, tmp_path): + _base, source, _database = histories + runner = MySQLMigrationRunner( + MaintenanceConnections(SeekDBConfig(path=tmp_path / "seekdb")), + source, + evidence_directory=tmp_path / "evidence", + ) + assert tuple(runner.checksums) == ("r1400", "shared") + # Construction validates both Alembic chains without opening a native target. + assert not (tmp_path / "seekdb").exists() + + +def _append_revision(histories): + base, source, database = histories + addition = "ALTER TABLE records ADD COLUMN next_target TEXT" + final = _fingerprint( + "CREATE TABLE records (value TEXT)", + "ALTER TABLE records ADD COLUMN marker TEXT", + "ALTER TABLE records ADD COLUMN target TEXT", + addition, + ) + for directory in (base.directory, source.source_directory): + _revision(directory, "later", "shared", addition) + _revision(directory / "mysql", "later", "shared", addition) + (directory / "later.json").write_text( + json.dumps({ + "format": "powercontext.mysql-layouts.v1", + "revisions": {"later": [addition]}, + "checkpoints": {}, + }) + ) + path = directory / "manifest.json" + manifest = json.loads(path.read_text()) + manifest["sqlite_fingerprints"]["later"] = [final] + manifest["revision_resources"]["later"] = ["later.json"] + manifest["mysql_layouts"].append("later.json") + path.write_text(json.dumps(manifest)) + return ( + MigrationBundle(base.directory), + MigrationBundle(base.directory, source_directory=source.source_directory), + database, + ) + + +@pytest.mark.parametrize("interrupt", [False, True]) +def test_source_lineage_survives_later_upgrade_and_recovery(histories, monkeypatch, interrupt): + first = _runner(histories) + plan = first.plan(backup_policy="skip") + first.apply( + plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True, backup_policy="skip", accept_no_backup=True + ) + extended = _append_revision(histories) + runner = _runner(extended) + plan = runner.plan(backup_policy="skip") + assert plan.source_revision == "shared" and plan.revisions == ("later",) + options = { + "plan_id": plan.plan_id, + "accepted": True, + "maintenance_confirmed": True, + "backup_policy": "skip", + "accept_no_backup": True, + } + if interrupt: + original = runner._write_evidence + + def stop_before_completion(evidence): + if evidence["state"] == "complete": + raise RuntimeError("Interrupted after revision commit") # noqa: TRY003 -- injected process interruption + original(evidence) + + with monkeypatch.context() as patch: + patch.setattr(runner, "_write_evidence", stop_before_completion) + with pytest.raises(RuntimeError, match="Interrupted after revision commit"): + runner.apply(**options) + else: + runner.apply(**options) + evidence = json.loads(runner.evidence_path.read_text()) + assert evidence["source_revision"] == "shared", "The current backup origin must not become the historical source" + assert "r1400" in evidence["release_checksums"] + reopened = _runner(extended) + if interrupt: + plan = reopened.plan(backup_policy="skip") + assert plan.state == "recovery_required" + reopened.apply(**{**options, "plan_id": plan.plan_id}) + assert reopened.verify().revision == "later" + assert reopened.plan().bundle_checksum == extended[1].checksum + assert not reopened.apply().changed + with closing(sqlite3.connect(reopened.database)) as connection: + assert connection.execute("SELECT * FROM records").fetchall() == [("kept", "original", None, None)] + evidence = json.loads(reopened.evidence_path.read_text()) + evidence["release_checksums"]["r1400"] = "changed" + reopened.evidence_path.write_text(json.dumps(evidence)) + before = reopened.database.read_bytes() + with pytest.raises(MigrationError, match="checksum_conflict"): + _runner(extended).plan() + assert reopened.database.read_bytes() == before + + +@pytest.mark.parametrize("origin", ["r1400", "shared"]) +def test_conflicting_source_lineages_are_rejected(histories, tmp_path, origin): + base, source, _database = histories + other_directory = tmp_path / "other-source" + shutil.copytree(source.source_directory, other_directory) + for path in other_directory.rglob("*"): + if path.is_file(): + path.write_text(path.read_text().replace("r1400", "r1500").replace("1.4.0", "1.5.0")) + other = MigrationBundle(base.directory, source_directory=other_directory) + sources = MigrationSources(base, (source, other)) + checksums = {"r1500": other.checksums["r1500"]} + if origin == "shared": + checksums["r1400"] = source.checksums["r1400"] + with pytest.raises(MigrationError, match="recovery_required"): + sources.from_evidence({"source_revision": origin, "release_checksums": checksums}) 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..9113bf677b 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,671 @@ 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()) + declaration = connection.exec_driver_sql("SELECT embedding_profile FROM pc_server_storage_profile").scalar_one() + assert json.loads(declaration) == profile.model_dump(mode="json") + return inventory, declaration + + before = backend.connections().run(verify) + evidence = {path: path.read_bytes() for path in backend.evidence_directory.rglob("maintenance.json")} + assert evidence + changed_settings = settings.model_copy( + update={"inference": settings.inference.model_copy(update={"embedding_profile_id": "different-space"})} + ) + changed = deployment_runner( + changed_settings, + evidence_dir=backend.evidence_directory, + lock_coordination="single-host" if isinstance(backend.config, OceanBaseConfig) else None, + ) + for operation in (changed.plan, changed.verify, changed.apply): + with pytest.raises(MigrationError) as error: + operation() + assert error.value.code == "embedding_profile_mismatch" + assert backend.connections().run(verify) == before + assert {path: path.read_bytes() for path in backend.evidence_directory.rglob("maintenance.json")} == evidence + assert runner.verify().state == "ready" + + +@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"}') + + (main_path,) = backend.evidence_directory.glob("*/maintenance.json") + evidence = json.loads(main_path.read_text()) + evidence["release_checksums"]["s1402"] = "0" * 64 + main_path.write_text(json.dumps(evidence)) + original_evidence = {path: path.read_bytes() for path in backend.evidence_directory.rglob("maintenance.json")} + for inspect in (runner.plan, runner.verify, runner.apply): + with pytest.raises(MigrationError, match="checksum_conflict"): + inspect() + assert backend.connections().run(inspect_history) == ("s1402", "dedicated", b'{"history":"unchanged"}') + assert { + path: path.read_bytes() for path in backend.evidence_directory.rglob("maintenance.json") + } == original_evidence + + +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_mysql_vector_admission.py b/tests/builtin/persistence/test_mysql_vector_admission.py new file mode 100644 index 0000000000..14a92a7721 --- /dev/null +++ b/tests/builtin/persistence/test_mysql_vector_admission.py @@ -0,0 +1,163 @@ +# 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. + +"""Public MySQL admission with modeled reflection and real vector declarations. + +SQLite supplies the stored data; native backend acceptance has separate tests. +""" + +import copy +import shutil +from types import SimpleNamespace + +import pytest +from sqlalchemy import create_engine + +from powercontext.builtin.artifacts.memory import EmbeddingProfile +from powercontext.builtin.persistence.migrations import mysql as migration +from powercontext.builtin.persistence.migrations import mysql_schema +from powercontext.builtin.persistence.migrations.bundle import MigrationBundle +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.migrations.sqlite import SQLiteMigrationRunner +from powercontext.builtin.persistence.migrations.vector_profile import load_sqlite_vector_extension +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.fixture +def managed(tmp_path, monkeypatch): + profile = EmbeddingProfile(profile_id="stored-space", model="fixture", dimension=3) + bundle = server_bundle(embedding_profile=profile) + configuration = { + "embedding_enabled": True, + "embedding_profile": profile.model_dump(mode="json"), + "processing_manifest": canonical_processing_manifest(BuiltinConfig()), + } + database = tmp_path / "database.sqlite3" + sqlite = SQLiteMigrationRunner(database, bundle, configuration=configuration) + sqlite.apply(plan_id=sqlite.plan().plan_id, accepted=True, maintenance_confirmed=True) + directory = tmp_path / "mysql-evidence" + connections = MaintenanceConnections(SeekDBConfig(path=tmp_path / "native")) + identity = BackendIdentity("seekdb", "fixture-vectors", "test") + + def runner(selected=bundle, configured=configuration): + return migration.MySQLMigrationRunner( + connections, selected, evidence_directory=directory, configuration=configured + ) + + original = runner() + inventory = copy.deepcopy(original.schema._schemas[bundle.head]) + control = _parse_table( + "CREATE TABLE pc_schema_revision (version_num VARCHAR(32) NOT NULL, PRIMARY KEY (version_num))" + ) + inventory["tables"][control[0]] = control[1] + monkeypatch.setattr(migration, "mysql_inventory", lambda _connection: copy.deepcopy(inventory)) + monkeypatch.setattr(mysql_schema, "mysql_inventory", lambda _connection: copy.deepcopy(inventory)) + + def run(operation, *, writable=False): + assert not writable, "Admission must fail before requesting a write connection" + engine = create_engine(f"sqlite:///{database}") + try: + with engine.connect() as connection: + load_sqlite_vector_extension(connection) + connection.exec_driver_sql("PRAGMA query_only=ON") + return operation(connection, identity, lambda: None) + finally: + engine.dispose() + + monkeypatch.setattr(connections, "run", run) + monkeypatch.setattr(connections, "coordination", lambda _identity: {}) + journal = migration._Journal(directory, identity) + journal.write( + migration._Evidence( + state="complete", + database_id=identity.database_id, + source_revision=None, + source_fingerprint=original.schema.fingerprint({"tables": {}, "views": [], "triggers": []}), + bundle_checksum=original.checksum, + configuration_digest=original.configuration_digest, + last_revision=bundle.head, + maintenance_window_id="vector-upgrade", + backup_policy="auto", + backup_state="not_required", + release_checksums=original.checksums, + ) + ) + assert original.verify().state == "ready" + return SimpleNamespace( + database=database, + profile=profile, + bundle=bundle, + configuration=configuration, + runner=runner, + journal=journal, + inventory=inventory, + control=control, + ) + + +@pytest.mark.parametrize("change", ["profile", "resource"]) +def test_vector_configuration_drift_is_distinct_from_changed_migration_resources(managed, tmp_path, change): + if change == "profile": + profile = managed.profile.model_copy(update={"profile_id": "different-space"}) + bundle = server_bundle(embedding_profile=profile) + configuration = {**managed.configuration, "embedding_profile": profile.model_dump(mode="json")} + expected = "embedding_profile_mismatch" + else: + copied = tmp_path / "changed-bundle" + shutil.copytree(managed.bundle.directory, copied) + resource = copied / "versions/s1402_portable_storage.py" + resource.write_text(resource.read_text() + "\n# Changed historical migration resource\n") + bundle = MigrationBundle(copied, embedding_profile=managed.profile) + configuration = managed.configuration + expected = "checksum_conflict" + replacement = managed.runner(bundle, configuration) + before = managed.database.read_bytes(), managed.journal.path.read_bytes() + for operation in (replacement.plan, replacement.verify, replacement.apply): + with pytest.raises(MigrationError) as error: + operation() + assert error.value.code == expected + assert (managed.database.read_bytes(), managed.journal.path.read_bytes()) == before + + +def test_vector_recovery_allows_declaration_table_before_domain_seed(managed): + original = managed.runner() + engine = create_engine(f"sqlite:///{managed.database}") + try: + with engine.begin() as connection: + connection.exec_driver_sql("DELETE FROM pc_server_storage_profile") + connection.exec_driver_sql("UPDATE pc_schema_revision SET version_num='s1300'") + finally: + engine.dispose() + # Model the s1400 DDL prefix after the profile table was created, before + # its domain transaction stored the declaration or advanced the revision. + managed.inventory.clear() + managed.inventory.update(copy.deepcopy(original.schema._schemas["s1400"])) + managed.inventory["tables"][managed.control[0]] = managed.control[1] + evidence = managed.journal.read() + assert evidence is not None + evidence.state = "active" + evidence.last_revision = "s1300" + evidence.pending_revision = "s1400" + evidence.release_checksums = {"s1300": original.checksums["s1300"]} + managed.journal.write(evidence) + before = managed.database.read_bytes(), managed.journal.path.read_bytes() + plan = original.plan() + assert plan.state == "recovery_required" + assert plan.source_revision == "s1300" and plan.revisions[0] == "s1400" + assert (managed.database.read_bytes(), managed.journal.path.read_bytes()) == before 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_partial_maintenance_profile_admission.py b/tests/builtin/persistence/test_partial_maintenance_profile_admission.py new file mode 100644 index 0000000000..d7d88c8097 --- /dev/null +++ b/tests/builtin/persistence/test_partial_maintenance_profile_admission.py @@ -0,0 +1,76 @@ +# 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. + +"""Unsupported partial vector maintenance must fail before any durable work.""" + +import pytest +from fastapi.testclient import TestClient + +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.server.factory import create_server_app +from tests.builtin.persistence.test_release140_server_migrations import ControlledEmbedding, _apply, _source + + +def _evidence(runner): + directory = runner.evidence_path.parent + return {path.relative_to(directory): path.read_bytes() for path in directory.rglob("*") if path.is_file()} + + +@pytest.mark.parametrize("release", ["1.3.0", "1.4.0"]) +@pytest.mark.parametrize("bundle", ["atomic-projection", "processing"]) +def test_missing_configuration_cannot_admit_partial_vector_maintenance(tmp_path, release, bundle): + settings, profile, full = _source(tmp_path, release=release, vectors=True) + _apply(full) + with TestClient( + create_server_app( + settings=settings, + embedding_model=ControlledEmbedding(profile), + scheduler_path=tmp_path / "scheduler", + ) + ) as client: + response = client.post( + "/v1/memory/remember", + json={"scope_id": "scope", "kind": "fact", "text": "Preserve this healthy vector projection."}, + ) + assert response.status_code == 200, response.text + assert full.verify().state == "ready" + before = full.database.read_bytes(), _evidence(full) + + # The caller omits embedding configuration. Admission must inspect the + # stored declaration, not treat this database as a no-vector deployment. + partial = deployment_runner(settings, bundle=bundle) + for operation in ( + partial.plan, + partial.verify, + lambda: partial.apply( + plan_id="unsupported-plan", + accepted=True, + maintenance_confirmed=True, + backup_policy="skip", + accept_no_backup=True, + ), + ): + with pytest.raises(MigrationError) as error: + operation() + assert error.value.code == "unsupported_upgrade_configuration" + assert (full.database.read_bytes(), _evidence(full)) == before + + # A rejected command must not leave an active operation that prevents the + # next supported full maintenance command from reading the original state. + reopened = deployment_runner(settings, embedding_profile=profile) + assert reopened.plan().state == "ready" + assert reopened.verify().state == "ready" + assert not _apply(reopened).changed + assert (full.database.read_bytes(), _evidence(full)) == before 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_release140_server_migrations.py b/tests/builtin/persistence/test_release140_server_migrations.py new file mode 100644 index 0000000000..699e054f8f --- /dev/null +++ b/tests/builtin/persistence/test_release140_server_migrations.py @@ -0,0 +1,245 @@ +# 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 the registered 1.4 SQLite source; installed-wheel/native gates remain separate.""" + +import json +import sqlite3 +from contextlib import closing +from datetime import UTC, datetime + +import pytest +import sqlite_vec +from alembic import command +from fastapi.testclient import TestClient +from sqlalchemy import create_engine + +from powercontext.artifacts import ArtifactRef +from powercontext.builtin.artifacts.experience import ExperienceContent +from powercontext.builtin.artifacts.memory import EmbeddingProfile +from powercontext.builtin.code import CodeConfig +from powercontext.builtin.dream.models import CreateDreamRunRequest, DreamRecord, DreamRun +from powercontext.builtin.inference.models import EmbeddingResult +from powercontext.builtin.persistence.candidates import CandidateRepository +from powercontext.builtin.persistence.dream import DreamRepository +from powercontext.builtin.persistence.migrations.deployment import deployment_runner, server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.pinned import finish, pinned +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.server.factory import create_server_app +from powercontext.server.settings import McpConfig, ServerSettings + + +class ControlledEmbedding: + def __init__(self, profile): + self.profile = profile + + async def embed(self, texts, /): + vector = (1.0,) + (0.0,) * (self.profile.dimension - 1) + return EmbeddingResult(vectors=tuple(vector for _ in texts)) + + +def _source(tmp_path, *, release="1.4.0", vectors=False, code=False): + database = tmp_path / "source.db" + embedding = EmbeddingProfile(profile_id="source140", model="fixture", dimension=3) if vectors else None + settings = ServerSettings( + database=SQLiteConfig(url=f"sqlite+aiosqlite:///{database}"), + code=CodeConfig(enabled=code, cache_dir=tmp_path / "code"), + mcp=McpConfig(enabled=False), + ) + runner = deployment_runner(settings, embedding_profile=embedding) + source = server_bundle(embedding_profile=embedding, source_release="1.4.0" if release == "1.4.0" else None) + revision = "r1400" if release == "1.4.0" else "s1300" + + def connect(): + connection = sqlite3.connect(database) + if vectors: + connection.enable_load_extension(True) + try: + sqlite_vec.load(connection) + finally: + connection.enable_load_extension(False) + return connection + + engine = create_engine("sqlite://", creator=connect) + try: + with engine.connect() as connection: + connection.exec_driver_sql("PRAGMA foreign_keys=OFF") + connection.exec_driver_sql("BEGIN IMMEDIATE") + config = source.config(connection) + config.attributes["configuration"] = runner.configuration + command.upgrade(config, revision) + if source.vector_profile is not None: + source.vector_profile.sqlite_upgrade(connection, revision) + 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','Source','History','scope','source','history',1)" + ) + if release == "1.4.0": + proposal = ExperienceContent( + situation="Existing", action="Preserve", outcome="Readable", lesson="History" + ) + connection.exec_driver_sql( + "INSERT INTO pc_artifacts (scope_id,family,artifact_id,revision,content) VALUES ('scope','experience','evidence',1,?)", + (proposal.model_dump_json().encode(),), + ) + repository = CandidateRepository({"experience": ExperienceContent}) + options = { + "sources": (), + "artifacts": (ArtifactRef(family="experience", artifact_id="evidence", revision=1),), + "target": None, + "reason": "Original", + } + finish(repository.create(pinned(connection), "scope", "candidate", "experience", proposal, **options)) + finish(repository.revise(pinned(connection), "scope", "candidate", 1, proposal, **options)) + finish(repository.reject(pinned(connection), "scope", "candidate", 2, "Historical decision")) + connection.commit() + finally: + engine.dispose() + return settings, embedding, runner + + +def _apply(runner): + plan = runner.plan(backup_policy="skip") + return runner.apply( + plan_id=plan.plan_id, + accepted=True, + maintenance_confirmed=True, + backup_policy="skip", + accept_no_backup=True, + ) + + +def _candidate_rows(database): + with closing(sqlite3.connect(database)) as connection: + return { + name: connection.execute(f"SELECT * FROM {name} ORDER BY 1,2,3").fetchall() # noqa: S608 -- Two fixed fixture tables. + for name in ("pc_candidate_heads", "pc_candidate_versions") + } + + +@pytest.mark.parametrize(("release", "expected"), [("1.3.0", "s1300"), ("1.4.0", "r1400")]) +def test_registered_sources_choose_distinct_lineages(tmp_path, release, expected): + _settings, _embedding, runner = _source(tmp_path, release=release) + plan = runner.plan() + assert plan.source_revision == expected + assert plan.revisions == ("s1400", "s1401", "s1402") + + +@pytest.mark.parametrize(("vectors", "code"), [(False, False), (True, False), (False, True), (True, True)]) +def test_release140_four_sqlite_configurations_preserve_candidates_and_serve(tmp_path, vectors, code): + settings, embedding, runner = _source(tmp_path, vectors=vectors, code=code) + before = _candidate_rows(runner.database) + assert len(before["pc_candidate_versions"]) == 2 + _apply(runner) + assert _candidate_rows(runner.database) == before + reopened = deployment_runner(settings, embedding_profile=embedding) + assert reopened.verify().state == "ready" + assert not _apply(reopened).changed + with closing(sqlite3.connect(runner.database)) as connection: + for name in ("pc_retained_dream_candidate_heads", "pc_retained_dream_candidate_versions"): + assert connection.execute(f"SELECT COUNT(*) FROM {name}").fetchone() == (0,) # noqa: S608 -- Fixed retained tables. + app = create_server_app( + settings=settings, + embedding_model=ControlledEmbedding(embedding) if embedding is not None else None, + scheduler_path=tmp_path / "scheduler", + ) + with TestClient(app) as client: + assert client.get("/health/ready").status_code == 200 + response = client.post( + "/v1/memory/remember", json={"scope_id": "scope", "kind": "fact", "text": "New data after the 1.4 bridge."} + ) + assert response.status_code == 200 + assert _candidate_rows(runner.database) == before + + +def test_release140_extra_object_blocks_before_any_write(tmp_path): + _settings, _embedding, runner = _source(tmp_path) + with closing(sqlite3.connect(runner.database)) as connection, connection: + connection.execute("CREATE TABLE unexpected (value TEXT)") + before = runner.database.read_bytes() + with pytest.raises(MigrationError, match="unknown_baseline"): + runner.plan() + assert runner.database.read_bytes() == before + assert not runner.evidence_path.exists() + + +@pytest.mark.parametrize("operation", ["server", "processing", "atomic-projection"]) +def test_release140_missing_retained_history_blocks_full_and_partial_verification(tmp_path, operation): + settings, _embedding, runner = _source(tmp_path) + _apply(runner) + with closing(sqlite3.connect(runner.database)) as connection, connection: + connection.execute("DROP TABLE pc_retained_dream_candidate_versions") + before = runner.database.read_bytes() + with pytest.raises(MigrationError, match="recovery_required"): + deployment_runner(settings, bundle=operation).verify() + assert runner.database.read_bytes() == before + + +def test_release140_invalid_processing_manifest_blocks_before_any_write(tmp_path): + _settings, _embedding, runner = _source(tmp_path) + with closing(sqlite3.connect(runner.database)) as connection, connection: + manifest = json.loads( + connection.execute("SELECT config_manifest FROM pc_artifact_processing_schema").fetchone()[0] + ) + manifest["bindings"].pop("handoff.dream.v1") + connection.execute("UPDATE pc_artifact_processing_schema SET config_manifest=?", (json.dumps(manifest),)) + before = runner.database.read_bytes() + with pytest.raises(MigrationError, match="unknown_processing_manifest"): + runner.plan() + assert runner.database.read_bytes() == before + assert not runner.evidence_path.exists() + + +@pytest.mark.parametrize("corrupt_identity", [False, True]) +def test_release140_current_dream_tasks_keep_their_format_or_fail_before_writes(tmp_path, corrupt_identity): + _settings, _embedding, runner = _source(tmp_path) + record = DreamRecord( + run=DreamRun( + scope_id="scope", + run_id="current-task", + operation="refine_experience", + status="queued", + accepted_at=datetime(2026, 1, 1, tzinfo=UTC), + ), + request=CreateDreamRunRequest( + operation="refine_experience", + artifacts=(ArtifactRef(family="experience", artifact_id="evidence", revision=1),), + idempotency_key="current-task", + ), + principal_id="source-owner", + ) + engine = create_engine(f"sqlite:///{runner.database}") + try: + with engine.begin() as connection: + finish(DreamRepository().create(pinned(connection), record)) + if corrupt_identity: + connection.exec_driver_sql("UPDATE pc_dream_runs SET principal_key='mismatched'") + before_payload = connection.exec_driver_sql("SELECT payload FROM pc_dream_runs").scalar_one() + finally: + engine.dispose() + if corrupt_identity: + before = runner.database.read_bytes() + with pytest.raises(MigrationError, match="unsupported_task_format"): + runner.plan() + assert runner.database.read_bytes() == before + assert not runner.evidence_path.exists() + else: + _apply(runner) + assert runner.verify().state == "ready" + with closing(sqlite3.connect(runner.database)) as connection: + assert connection.execute("SELECT status,payload FROM pc_dream_runs").fetchone() == ( + "queued", + before_payload, + ) 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 a2746b528f..f5113891cb 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") @@ -73,17 +74,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 394babe37b..1aa7eafa5b 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_read_consistency.py b/tests/e2e/test_managed_startup_read_consistency.py new file mode 100644 index 0000000000..36ab2f514d --- /dev/null +++ b/tests/e2e/test_managed_startup_read_consistency.py @@ -0,0 +1,183 @@ +# 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. + +"""Concurrent publication remains valid across actual backend startup boundaries.""" + +import asyncio +from contextlib import contextmanager +from contextvars import ContextVar + +import httpx +import pytest +from fastapi.testclient import TestClient +from sqlalchemy import event +from sqlalchemy.dialects import mysql +from sqlalchemy.engine import Engine +from sqlalchemy.ext.asyncio import create_async_engine + +from powercontext.builtin.persistence.migrations import connections +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.builtin.persistence.oceanbase import profile as oceanbase_profile +from powercontext.builtin.persistence.seekdb import profile as seekdb_profile +from powercontext.builtin.runtime import BuiltinConfig, atomic_memory_rebuild, open_builtin_contexts +from powercontext.builtin.runtime.config import RuntimeConfig +from powercontext.server import cli +from powercontext.server.factory import create_server_app +from powercontext.server.settings import AccessControlConfig, BearerAuthConfig, McpConfig, ServerSettings +from powercontext.server.tracing import ServerTracing +from tests.builtin.persistence.test_server_vector_migrations import ControlledEmbedding, profile +from tests.e2e.server_storage import prepare_server_database + + +# Only select the supported session isolation; all SQL, publication and +# validation use the real selected backend. Native cases require the opt-in +# fixtures and run without skips in the isolated CI backend harness. +def _read_committed_engine(*args, **kwargs): + engine = create_async_engine(*args, **kwargs) + if engine.dialect.name == "mysql": + + @event.listens_for(engine.sync_engine, "connect") + def configure(dbapi_connection, _record): + cursor = dbapi_connection.cursor() + try: + cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED") + finally: + cursor.close() + + return engine + + +@contextmanager +def _nonlocking_reads(): + checking_record = ContextVar("checking_record", default=False) + + def reject_locking_read(_connection, statement, _multiparams, _params, _options): + if checking_record.get(): + # SQLite omits locking clauses. Check the native SQL contract + # as well so local runs catch locks before native concurrency CI. + sql = str(statement.compile(dialect=mysql.dialect())).upper() + assert "FOR UPDATE" not in sql and "LOCK IN SHARE MODE" not in sql + + token = checking_record.set(True) + event.listen(Engine, "before_execute", reject_locking_read) + try: + yield + finally: + event.remove(Engine, "before_execute", reject_locking_read) + checking_record.reset(token) + + +@pytest.mark.parametrize("entry,vector", [("server", False), ("sdk", True), ("background", False)]) +def test_concurrent_publication_does_not_reject_managed_startup(migration_backend, monkeypatch, entry, vector): + for module in (connections, oceanbase_profile, seekdb_profile): + monkeypatch.setattr(module, "create_async_engine", _read_committed_engine) + embedding = ControlledEmbedding(profile(3)) if vector else None + settings = ServerSettings( + database=migration_backend.config, + runtime=RuntimeConfig(artifact_processing_families=()), + mcp=McpConfig(enabled=False), + access=AccessControlConfig(mode="disabled"), + auth=BearerAuthConfig(enabled=False), + ) + evidence = migration_backend.directory / "migration-evidence" + prepare_server_database( + settings, evidence_dir=evidence, embedding_profile=None if embedding is None else embedding.profile + ) + app = create_server_app( + settings=settings, scheduler_path=migration_backend.directory / "scheduler.db", embedding_model=embedding + ) + with TestClient(app) as client: + created = client.post( + "/v1/scopes", + json={"title": "Concurrent startup", "summary": "Read consistency", "idempotency_key": "snapshot"}, + ) + assert created.status_code == 201, created.text + scope = created.json()["scope_id"] + remembered = client.post( + "/v1/memory/remember", json={"scope_id": scope, "kind": "fact", "text": "Before concurrent revision."} + ) + assert remembered.status_code == 200, remembered.text + artifact = remembered.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 + with _nonlocking_reads(): + record = await original(*args, **kwargs) + if not updated: + updated = True + connection = args[0] + if connection.dialect.name == "mysql": + # A one-transaction snapshot must leave ordinary business + # transactions at the configured session default. + isolation = (await connection.exec_driver_sql("SELECT @@session.tx_isolation")).scalar_one() + assert str(isolation).replace("-", " ").upper() == "READ COMMITTED" + 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 + + async def start_sdk(): + async with open_builtin_contexts( + BuiltinConfig(database=settings.database, runtime=settings.runtime), embedding_model=embedding + ) as contexts: + if settings.database.kind != "sqlite": + async with contexts.database.transaction() as connection: + isolation = (await connection.exec_driver_sql("SELECT @@session.tx_isolation")).scalar_one() + assert str(isolation).replace("-", " ").upper() == "READ COMMITTED" + + async def serve_metrics(application, _settings, stopped): + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=application), base_url="http://testserver" + ) as transport: + response = await transport.get("/metrics") + assert response.status_code == 200, response.text + assert 'powercontext_server_readiness_state{state="ready"} 1.0' in response.text + stopped.set() + + monkeypatch.setattr(atomic_memory_rebuild, "_record", interleaved_record) + try: + if entry == "sdk": + asyncio.run(start_sdk()) + elif entry == "background": + # Exercise the full background runtime and readiness; replace + # only the listener lifetime so this test does not need signals. + monkeypatch.setattr(cli, "serve_background_metrics", serve_metrics) + asyncio.run(cli._run_background_async(settings, ServerTracing.context_only(role="background"))) + else: + with TestClient( + create_server_app( + settings=settings, + embedding_model=embedding, + scheduler_path=migration_backend.directory / "second-scheduler.db", + ) + ) as second: + assert second.get("/health/ready").status_code == 200 + finally: + monkeypatch.setattr(atomic_memory_rebuild, "_record", original) + assert updated + assert client.get(path).json()["content"]["text"] == "After concurrent revision." + runner = deployment_runner( + settings, + evidence_dir=evidence, + lock_coordination="single-host" if settings.database.kind == "oceanbase" else None, + embedding_profile=None if embedding is None else embedding.profile, + ) + assert runner.verify().state == "ready" 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 1ae4195321..fbe0af7e9f 100644 --- a/tests/e2e/test_topic_memory_generic_api.py +++ b/tests/e2e/test_topic_memory_generic_api.py @@ -751,23 +751,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 947ccaf344..c62800850a 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..87bae12cf0 100644 --- a/tests/native/test_personal_service_lifecycle.py +++ b/tests/native/test_personal_service_lifecycle.py @@ -14,29 +14,43 @@ 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 ExitStack, closing, contextmanager, suppress from importlib.metadata import version from pathlib import Path +from socketserver import BaseRequestHandler, TCPServer +from threading import Thread 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 +149,354 @@ 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 ExitStack() as resources, 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. + resources.enter_context(_occupy_service_port(port)) + 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 + + +@contextmanager +def _occupy_service_port(port: int) -> Iterator[None]: + # Accept and close probe connections so repeated liveness checks cannot fill + # the backlog and turn a live port conflict into a connection timeout. + with TCPServer(("127.0.0.1", port), BaseRequestHandler, bind_and_activate=False) as listener: + # Reuse the stopped server's port while its connections remain in TIME_WAIT. + listener.allow_reuse_address = sys.platform != "win32" + listener.server_bind() + listener.server_activate() + thread = Thread(target=listener.serve_forever, daemon=True) + thread.start() + try: + yield + finally: + listener.shutdown() + thread.join() + + +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..6197291be2 --- /dev/null +++ b/tests/release_upgrades/README.md @@ -0,0 +1,208 @@ +# 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 automatic case +to run and pass. The two embedded seekDB Code configurations are temporarily manual and do not block publication. 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. + +## Reviewed sources and current target + +The declared target release is **1.4.1**. Public migration output maps the two source baselines to +`v1_3_0_000` / `v1_4_0_000` and the shared steps to `v1_4_1_001`–`v1_4_1_003`. The suite checks +these names while retaining the original revision IDs in execution and recovery evidence. + +The `automatic_sources` policy uses `strategy: "fixed-version"` and `source_version: "1.3.0"`. +Explicit `cases` are empty. The catalog retains twelve configurations of the 1.3.0 source across SQLite, +embedded seekDB, and OceanBase. The target is the **actual wheel built by the shared preparation job**, not the latest published +PyPI package. Later target releases keep the same 1.3.0 baseline; intermediate releases are not added automatically. +The framework and fixture retain the independently registered 1.4.0 adapter, but CI does not execute its +installed-release upgrade path. Passing this gate does not establish 1.4.0 upgrade acceptance. This matrix does not establish support for every intermediate release's database or +data shapes; add reviewed explicit `cases` and source adapters 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. + +The selected source uses all twelve profiles in `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 CI 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. Reviewed adapters select the source Candidate API and table +names. The retained, unselected 1.4.0 adapter creates a pending intent with `consecutive_dream_attempts=1`; target readback must preserve +every field supplied by the source SDK before idempotent acknowledgment. The 1.3.0 fixture retains its original +pending-intent contract. 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 the installed +source release, 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. +OceanBase reserves separate upgraded and fresh databases for every source/configuration pair. The default +connection URLs remain `pc_upgrade_test` and `pc_upgrade_fresh`; the suite selects the corresponding configuration +and, for 1.4.0, the `pc_upgrade_140` prefix before either Server is started. Every selected database must be empty. +PR and publication CI use the same reusable workflow, `upgrade-release.yml`: + +1. **Prepare once:** build the wheel and source archive, rebuild the source archive to verify matching contents, + validate release metadata, and resolve the source catalog into one frozen twelve-case plan. +2. **Run independent cases:** each selected matrix job executes exactly one source/backend/configuration tuple. + Every job downloads the same prepared artifacts and verifies their hashes. Each OceanBase job owns a separate + container; SQLite and embedded seekDB run in their own runners. `fail-fast: false` allows other cases to finish + after a failure. There is no second serial execution of the matrix. +3. **Collect once:** require exactly one passing result for every planned case, revalidate JUnit phases, installed + versions and backend evidence, and reject duplicate, missing, skipped, failed or mixed-plan reports. The plan + binds the workflow run, checkout, reviewed policy and distribution hashes. A new preparation gets a new identity; + rerunning only failed cases can reuse the unchanged prepared plan and the other cases' results from that run. +4. **Gate publication:** require all case jobs, including cleanup and artifact upload, to succeed. Only the accepted automatic + distribution snapshot can reach PyPI or GitHub Release assets. `upgrade-required` still blocks publication. + +Temporarily, Main and Release automatically require 10 configurations. Embedded seekDB `full-server-code` +and `full-server-vector-code` run only on manual request while the available engine exhibits DDL waits. +In Actions → Main → Run workflow, select the branch and set `upgrade_selection` to `seekdb-code` to run +those two upgrade configurations alone, or `full` to run all 12. Other Main CI suites still run normally. +For a PR branch, the equivalent command is: + +```sh +gh workflow run master.yml --repo oceanbase/powercontext --ref codex/issue-2154-full-server-upgrade -f upgrade_selection=seekdb-code +``` + +`automatic` runs the remaining 10. Reports list deferred cases and mark full acceptance incomplete; a passing +automatic run can expose the tested distributions for publication without evidence from the manual cases. +A manual-only run produces no publication artifacts and cannot authorize release. Missing, skipped or failed +automatic cases still block publication, and the reviewed support declaration remains mandatory. Passing +automatic CI does not certify either seekDB Code configuration; they are excluded from generated supported +upgrade claims. After validating a fixed seekDB release, remove the temporary `manual_upgrade_case` waiver +and restore Main and Release to `full` selection. + +The fixed required check remains **Upgrade release gate**; on Main it covers only the selected configurations. It fails if the shared workflow fails or is cancelled. +Each case job has a sixty-minute budget including provisioning; existing thirty-minute command deadlines and +owned-descendant cleanup remain unchanged. Wall time depends on the slowest case and available runner capacity, +not the sum of twelve cases. This increases concurrent runner use and does not promise a fixed speedup. + +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 new directories. Parallel collection accepts only reports bound to +that preparation plan; unrelated historical results cannot satisfy the gate. The local serial runner does not +import 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. Independent CI jobs attempt every required combination even after another case fails; the local serial runner +continues through the same matrix. Both fail overall and preserve each case's logs and result. Empty matrices and missing suites are errors. + +## Publication artifacts + +Preparation validates every distribution input once before parallel execution. 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. + +Main first runs the previously failing embedded seekDB concurrent-startup regression in +`SeekDB managed startup regression (priority)`. Its three isolated Server/SDK scenarios run +once, before the installed-release workflow and the remaining seekDB clean-migration job. +Those jobs still run after a failed priority check unless the workflow is cancelled; the +selected installed-release cases remain disjoint and parallel. Other CI suites can run concurrently. diff --git a/tests/release_upgrades/processing_fixture.py b/tests/release_upgrades/processing_fixture.py new file mode 100644 index 0000000000..446cf45c73 --- /dev/null +++ b/tests/release_upgrades/processing_fixture.py @@ -0,0 +1,70 @@ +# 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 importlib.metadata import version +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] + source_version = sys.argv[4] + assert source_version in {"1.3.0", "1.4.0"}, "Unreviewed source processing contract" + if operation == "seed": + assert version("powercontext") == source_version, "Seed must use the exact installed source SDK" + 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) + if source_version == "1.4.0": + # Preserve a nondefault field absent from the 1.3.0 task contract. + await repository.acknowledge(connection, scope, binding, row.requested_generation, dream=True) + row = await repository.request(connection, scope, binding) + assert row.consecutive_dream_attempts == 1 + 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..da39ecdfe5 --- /dev/null +++ b/tests/release_upgrades/test_full_server.py @@ -0,0 +1,1136 @@ +# 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 release -> 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 typing import TypedDict +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." + + +class SourceAdapter(TypedDict): + candidate_prefix: str + candidate_tables: set[str] + revision: str + database_prefix: str + + +SOURCE_ADAPTERS: dict[str, SourceAdapter] = { + "1.3.0": { + "candidate_prefix": "/v1/artifact-candidates", + "candidate_tables": {"pc_artifact_candidate_heads", "pc_artifact_candidate_versions"}, + "revision": "s1300", + "database_prefix": "pc_upgrade", + }, + "1.4.0": { + "candidate_prefix": "/v1/candidates", + "candidate_tables": {"pc_candidate_heads", "pc_candidate_versions"}, + "revision": "r1400", + "database_prefix": "pc_upgrade_140", + }, +} + + +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"} + suffix = { + "full-server-no-vector": "", + "full-server-code": "_code", + "full-server-vector": "_vector", + "full-server-vector-code": "_vector_code", + }[case["configuration"]] + prefix = SOURCE_ADAPTERS[case["source_version"]]["database_prefix"] + profile_path = f"/{prefix}{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 source/profile databases may be used" + ) + url = parsed.geturl() + 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), + case["source_version"], + ], + 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. + source_version = os.environ["PC_UPGRADE_SOURCE_VERSION"] + assert source_version in SOURCE_ADAPTERS, "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_version": source_version, + "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=SOURCE_ADAPTERS[case["source_version"]]["candidate_prefix"]) + 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 SOURCE_ADAPTERS[case["source_version"]]["candidate_tables"] | {"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 + + status = 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" + expected_source_name = "v" + case["source_version"].replace(".", "_") + "_000" + assert status["source_migration"] == plan["source_migration"] + assert plan["source_migration"]["name"] == expected_source_name + assert plan["source_migration"]["revision"] == SOURCE_ADAPTERS[case["source_version"]]["revision"] + assert [item["revision"] for item in plan["pending_migrations"]] == plan["revisions"] + assert [item["name"] for item in plan["pending_migrations"][:3]] == ["v1_4_1_001", "v1_4_1_002", "v1_4_1_003"] + assert plan["target_migration"] == plan["pending_migrations"][-1] + # 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 applied["migration"] == verified["migration"] == plan["target_migration"] + 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, + SOURCE_ADAPTERS[case["source_version"]]["candidate_prefix"] + "/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"] == SOURCE_ADAPTERS[case["source_version"]]["revision"] + 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_api_contract.py b/tests/test_api_contract.py index b5cd57ad74..a4672b80b2 100644 --- a/tests/test_api_contract.py +++ b/tests/test_api_contract.py @@ -197,7 +197,7 @@ def test_contract_uses_the_namespaced_request_id_header() -> None: def test_server_info_contract_is_observable_and_forward_compatible() -> None: contract = yaml.safe_load(CONTRACT_PATH.read_text()) - assert contract["info"]["version"] == "1.4.0" + assert contract["info"]["version"] == "1.4.1" operation = contract["paths"]["/v1/server-info"]["get"] assert operation["operationId"] == "get_server_info" assert operation["x-powercontext-access"] == { 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_ci_release_verification.py b/tests/test_ci_release_verification.py index ab9b868d4d..abb9ffeea0 100644 --- a/tests/test_ci_release_verification.py +++ b/tests/test_ci_release_verification.py @@ -56,11 +56,15 @@ def run(workflow_name: str, tag: str, package_version: str): workflow = yaml.safe_load( (Path(__file__).parents[1] / ".github" / "workflows" / workflow_name).read_text(encoding="utf-8") ) - job, step_name = ( - ("release-build", "Verify package version from Release tag") - if workflow_name == "release.yml" - else ("verify", "Resolve release metadata") - ) + if workflow_name == "release.yml": + # Follow the publishing workflow's real call into shared acceptance. + # Running a copied check would miss drift in the release entry point. + build = workflow["jobs"]["release-build"] + assert build["with"]["release"] is True + workflow = yaml.safe_load((Path(__file__).parents[1] / build["uses"]).read_text(encoding="utf-8")) + job, step_name = "prepare", "Verify package version from Release tag" + else: + job, step_name = "verify", "Resolve release metadata" command = next(step["run"] for step in workflow["jobs"][job]["steps"] if step.get("name") == step_name) result = subprocess.run( [bash, "-eu", "-c", command], 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_maintenance_evidence.py b/tests/test_managed_maintenance_evidence.py new file mode 100644 index 0000000000..79bbc31bb3 --- /dev/null +++ b/tests/test_managed_maintenance_evidence.py @@ -0,0 +1,223 @@ +# 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. + +"""Partial maintenance preserves the full migration's historical evidence.""" + +import json +import shutil +import sqlite3 +from contextlib import closing + +import pytest +from alembic import command +from sqlalchemy import create_engine + +from powercontext.builtin.persistence.migrations.atomic_projection import AtomicProjectionMigrationRunner +from powercontext.builtin.persistence.migrations.bundle import MigrationBundle +from powercontext.builtin.persistence.migrations.connections import MaintenanceConnections +from powercontext.builtin.persistence.migrations.deployment import deployment_runner, server_bundle +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.processing_configuration import ProcessingConfigurationMigrationRunner +from powercontext.builtin.persistence.migrations.sqlite import SQLiteMigrationRunner +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.runtime.config import RuntimeConfig +from powercontext.server.settings import ServerSettings + + +def _managed(tmp_path, source): + path = tmp_path / "managed.db" + settings = ServerSettings(database=SQLiteConfig(url=f"sqlite+aiosqlite:///{path}")) + full = deployment_runner(settings) + if source: + bundle = server_bundle(source_release=source) + engine = create_engine(f"sqlite:///{path}") + try: + with engine.begin() as connection: + config = bundle.config(connection) + config.attributes["configuration"] = full.configuration + command.upgrade(config, bundle.source_revision) + connection.exec_driver_sql("DROP TABLE pc_schema_revision") + finally: + engine.dispose() + plan = full.plan(backup_policy="skip") + full.apply( + plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True, backup_policy="skip", accept_no_backup=True + ) + return path, settings, full + + +@pytest.mark.parametrize("source", [None, "1.4.0"], ids=["canonical", "release140"]) +@pytest.mark.parametrize("operation", ["processing", "atomic-projection"]) +@pytest.mark.parametrize("damage", ["checksum", "resource"]) +def test_partial_maintenance_rejects_changed_released_history(tmp_path, source, operation, damage): + path, settings, full = _managed(tmp_path, source) + requested = settings.model_copy(update={"runtime": RuntimeConfig(artifact_processing_supervisor_mode="dedicated")}) + partial = deployment_runner(requested if operation == "processing" else settings, bundle=operation) + reviewed = partial.plan(backup_policy="skip") + if damage == "checksum": + evidence = json.loads(full.evidence_path.read_text()) + evidence["release_checksums"][full.bundle.head] = "0" * 64 + full.evidence_path.write_text(json.dumps(evidence)) + else: + directory = tmp_path / "resources" + shutil.copytree(full.bundle.directory, directory) + resource = directory / "versions/s1402_portable_storage.py" + resource.write_text(resource.read_text() + "\n# Changed released migration resource.\n") + changed = MigrationBundle(directory, source_directory=full.bundle.source_directory) + full = SQLiteMigrationRunner(path, changed, configuration=full.configuration) + runner = ( + ProcessingConfigurationMigrationRunner if operation == "processing" else AtomicProjectionMigrationRunner + ) + partial = runner( + MaintenanceConnections(settings.database), + changed, + configuration=partial.configuration, + evidence_directory=full.evidence_path.parent, + ) + original = path.read_bytes() + original_evidence = full.evidence_path.read_bytes() + with pytest.raises(MigrationError, match="checksum_conflict"): + full.verify() + for inspect in ( + partial.plan, + partial.verify, + lambda: partial.apply( + plan_id=reviewed.plan_id, + accepted=True, + maintenance_confirmed=True, + backup_policy="skip", + accept_no_backup=True, + ), + ): + with pytest.raises(MigrationError, match="checksum_conflict"): + inspect() + assert path.read_bytes() == original + assert full.evidence_path.read_bytes() == original_evidence + assert not list(full.evidence_path.parent.glob("*/*/maintenance.json")) + assert not list(path.parent.glob("*.pc-migration-backups/*")) + + +def test_source140_processing_switch_keeps_original_migration_evidence(tmp_path): + path, settings, full = _managed(tmp_path, "1.4.0") + requested = settings.model_copy(update={"runtime": RuntimeConfig(artifact_processing_supervisor_mode="dedicated")}) + original_evidence = full.evidence_path.read_bytes() + partial = deployment_runner(requested, bundle="processing", migration_id="source140-switch") + plan = partial.plan(backup_policy="skip") + result = partial.apply( + plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True, backup_policy="skip", accept_no_backup=True + ) + assert result.changed and partial.verify().state == "ready" + assert full.evidence_path.read_bytes() == original_evidence + assert deployment_runner(requested).verify().state == "ready" + with closing(sqlite3.connect(path)) as connection: + assert ( + json.loads(connection.execute("SELECT config_manifest FROM pc_artifact_processing_schema").fetchone()[0])[ + "mode" + ] + == "dedicated" + ) + + +@pytest.mark.parametrize("source", [None, "1.4.0"], ids=["canonical", "release140"]) +@pytest.mark.parametrize("backend", ["seekdb", "oceanbase"]) +@pytest.mark.parametrize("damage", [None, "checksum", "database-id", "head", "unfinished"]) +def test_native_partial_admission_requires_original_completed_evidence(tmp_path, monkeypatch, source, backend, damage): + """Use real SQLite storage with modeled native identity/evidence, not a native backend.""" + from pydantic import SecretStr + + from powercontext.builtin.persistence.migrations import processing_configuration + from powercontext.builtin.persistence.migrations.connections import BackendIdentity + from powercontext.builtin.persistence.migrations.mysql import MySQLMigrationRunner, _Evidence, _Journal + from powercontext.builtin.persistence.oceanbase import OceanBaseConfig + from powercontext.builtin.persistence.seekdb import SeekDBConfig + + path, _settings, original = _managed(tmp_path, source) + directory = tmp_path / "native-evidence" + config = ( + SeekDBConfig(path=tmp_path / "seekdb") + 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 + ) + full = MySQLMigrationRunner( + connections, + original.bundle, + configuration=original.configuration, + evidence_directory=directory, + ) + identity = BackendIdentity(backend, "modeled-native-database", "test") + evidence = _Evidence( + state="complete", + database_id=identity.database_id, + source_revision=original.bundle.source_revision, + source_fingerprint="registered-source", + bundle_checksum=full.checksum, + configuration_digest=full.configuration_digest, + last_revision=full.bundle.head, + maintenance_window_id="completed-native-migration", + backup_policy="skip", + backup_state="skipped", + release_checksums=full.checksums, + ) + journal = _Journal(directory, identity) + journal.write(evidence) + value = json.loads(journal.path.read_text()) + if damage == "checksum": + value["release_checksums"][full.bundle.head] = "0" * 64 + elif damage == "database-id": + value["database_id"] = "another-database" + elif damage == "head": + value["last_revision"] = "s1401" + elif damage == "unfinished": + value["pending_revision"] = "s1402" + value["last_revision"] = "s1401" + journal.path.write_text(json.dumps(value)) + partial = ProcessingConfigurationMigrationRunner( + connections, + server_bundle(), + source_bundles=(server_bundle(source_release="1.4.0"),), + configuration=original.configuration, + evidence_directory=directory, + ) + engine = create_engine(f"sqlite:///{path}") + try: + with engine.connect() as connection: + monkeypatch.setattr( + connections, "run", lambda operation, **_options: operation(connection, identity, lambda: None) + ) + # The storage is SQLite; provide the corresponding registered native + # layout solely for the partial operation's inspection fingerprint. + monkeypatch.setattr( + processing_configuration, "mysql_inventory", lambda _connection: full.schema._schemas[full.bundle.head] + ) + before, before_evidence = path.read_bytes(), journal.path.read_bytes() + if damage: + code = "checksum_conflict" if damage == "checksum" else "recovery_required" + for inspect in ( + partial.plan, + partial.verify, + lambda: partial.apply(accepted=True, maintenance_confirmed=True), + ): + with pytest.raises(MigrationError, match=code): + inspect() + else: + assert partial.verify().state == "ready" + assert not partial.apply().changed + assert path.read_bytes() == before + assert journal.path.read_bytes() == before_evidence + assert list(directory.rglob("maintenance.json")) == [journal.path] + finally: + engine.dispose() diff --git a/tests/test_managed_processing_maintenance.py b/tests/test_managed_processing_maintenance.py new file mode 100644 index 0000000000..c1857ad893 --- /dev/null +++ b/tests/test_managed_processing_maintenance.py @@ -0,0 +1,331 @@ +# 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 + + +@pytest.mark.parametrize( + ("bundle", "missing", "recorded"), + [ + ("processing", "pc_retained_dream_candidate_heads", True), + ("atomic-projection", "pc_retained_dream_candidate_versions", False), + ], +) +def test_partial_maintenance_rejects_missing_required_history(managed, bundle, missing, recorded): + path, settings = managed + full = deployment_runner(settings) + journal = path.with_name(path.name + ".pc-migration-state") / "maintenance.json" + evidence = json.loads(journal.read_text()) + assert missing in evidence["retained_objects"] + if not recorded: + # The source-to-head transition still requires retention when its final + # inventory has not been recorded in the external journal. + del evidence["retained_objects"] + journal.write_text(json.dumps(evidence)) + partial = deployment_runner(switched(settings) if bundle == "processing" else settings, bundle=bundle) + reviewed = partial.plan() + with closing(sqlite3.connect(path)) as connection, connection: + connection.execute(f"DROP TABLE {missing}") + original = path.read_bytes() + original_evidence = journal.read_bytes() + with pytest.raises(MigrationError, match="recovery_required"): + full.verify() + + for operation in ( + partial.plan, + partial.verify, + lambda: partial.apply(plan_id=reviewed.plan_id, accepted=True, maintenance_confirmed=True), + ): + with pytest.raises(MigrationError, match="recovery_required"): + operation() + assert path.read_bytes() == original + assert journal.read_bytes() == original_evidence + assert not list(journal.parent.glob("*/*/maintenance.json")) + assert not list(path.parent.glob("*.pc-migration-backups/*")) + + +@pytest.mark.parametrize("bundle", ["processing", "atomic-projection"]) +def test_partial_maintenance_accepts_current_storage_without_external_evidence(managed, bundle): + path, settings = managed + full = deployment_runner(settings) + (path.with_name(path.name + ".pc-migration-state") / "maintenance.json").unlink() + # A current-layout database without an upgrade journal has no recorded + # historical retention obligation, matching the full runner's admission. + with closing(sqlite3.connect(path)) as connection, connection: + connection.execute("DROP TABLE pc_retained_dream_candidate_heads") + connection.execute("DROP TABLE pc_retained_dream_candidate_versions") + original = path.read_bytes() + assert full.verify().state == "ready" + assert deployment_runner(settings, bundle=bundle).verify().state == "ready" + assert path.read_bytes() == original + + +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_admission.py b/tests/test_managed_startup_admission.py new file mode 100644 index 0000000000..cd5af632c0 --- /dev/null +++ b/tests/test_managed_startup_admission.py @@ -0,0 +1,244 @@ +# 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. + +"""New runtimes respect managed maintenance and recorded history retention.""" + +import asyncio +import json +import sqlite3 +import subprocess +import sys +from contextlib import closing + +import pytest +from typer.testing import CliRunner + +from powercontext.builtin.persistence.migrations.backup import SQLiteBackupProvider +from powercontext.builtin.persistence.migrations.deployment import deployment_runner +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.runtime import BuiltinConfig, open_builtin_contexts +from powercontext.builtin.runtime.config import RuntimeConfig +from powercontext.cli.app import create_cli +from powercontext.server.cli import app +from tests.test_managed_maintenance_evidence import _managed + + +def open_runtime(database, *, background=False): + async def run(): + async with open_builtin_contexts(BuiltinConfig(database=database), _topic_memory_worker=background): + pass + + asyncio.run(run()) + + +@pytest.mark.parametrize("source", [None, "1.4.0"]) +@pytest.mark.parametrize("missing", ["pc_retained_dream_candidate_heads", "pc_retained_dream_candidate_versions"]) +def test_sdk_and_background_reject_missing_recorded_history(tmp_path, source, missing): + path, settings, runner = _managed(tmp_path, source) + open_runtime(settings.database) + with closing(sqlite3.connect(path)) as connection, connection: + connection.execute(f"DROP TABLE {missing}") + original = path.read_bytes() + evidence = runner.evidence_path.read_bytes() + for background in (False, True): + with pytest.raises(MigrationError, match="recovery_required"): + open_runtime(settings.database, background=background) + assert path.read_bytes() == original + assert runner.evidence_path.read_bytes() == evidence + + +def test_sdk_accepts_current_storage_without_recorded_history(tmp_path): + path, settings, runner = _managed(tmp_path, None) + runner.evidence_path.unlink() + with closing(sqlite3.connect(path)) as connection, connection: + connection.execute("DROP TABLE pc_retained_dream_candidate_heads") + connection.execute("DROP TABLE pc_retained_dream_candidate_versions") + open_runtime(settings.database) + assert runner.verify().state == "ready" + + +@pytest.mark.parametrize("damage", ["unfinished", "checksum"]) +def test_sdk_rejects_incomplete_or_changed_observable_history(tmp_path, damage): + _, settings, runner = _managed(tmp_path, None) + evidence = json.loads(runner.evidence_path.read_text()) + if damage == "unfinished": + evidence["state"] = "active" + else: + evidence["release_checksums"][runner.bundle.head] = "0" * 64 + runner.evidence_path.write_text(json.dumps(evidence)) + with pytest.raises(MigrationError, match="recovery_required" if damage == "unfinished" else "checksum_conflict"): + open_runtime(settings.database) + + +_START_CHILD = """ +import asyncio, sys +from powercontext.builtin.persistence.sqlite import SQLiteConfig +from powercontext.builtin.persistence.migrations.models import MigrationError +from powercontext.builtin.persistence.migrations.server_readiness import prepare_server_storage +from powercontext.builtin.runtime import BuiltinConfig, open_builtin_contexts +from powercontext.server.settings import ServerSettings +database = SQLiteConfig(url=sys.argv[1]) +async def sdk(background): + async with open_builtin_contexts(BuiltinConfig(database=database), _topic_memory_worker=background): + raise AssertionError('SDK admitted a writer during locked maintenance') +for operation in ( + lambda: prepare_server_storage(ServerSettings(database=database)), + lambda: asyncio.run(sdk(False)), + lambda: asyncio.run(sdk(True)), +): + try: + operation() + except MigrationError as error: + assert error.code == 'migration_locked', error + else: + raise AssertionError('Server admitted a writer during locked maintenance') +print('All startup entries rejected active maintenance') +""" + + +def test_new_process_cannot_start_after_backup_while_maintenance_owns_lock(tmp_path, monkeypatch): + _, settings, _ = _managed(tmp_path, None) + target = settings.model_copy(update={"runtime": RuntimeConfig(artifact_processing_supervisor_mode="dedicated")}) + runner = deployment_runner(target, bundle="processing") + plan = runner.plan() + original_backup = SQLiteBackupProvider.create_backup + + def check_after_backup(provider, context): + backup = original_backup(provider, context) + child = subprocess.run( + [sys.executable, "-c", _START_CHILD, settings.database.url], + capture_output=True, + text=True, + timeout=60, + check=False, + ) + assert child.returncode == 0, child.stdout + child.stderr + assert "All startup entries rejected active maintenance" in child.stdout + return backup + + monkeypatch.setattr(SQLiteBackupProvider, "create_backup", check_after_backup) + assert runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True).state == "ready" + assert deployment_runner(target).verify().state == "ready" + + async def reopen(): + async with open_builtin_contexts(BuiltinConfig(database=settings.database, runtime=target.runtime)): + pass + + asyncio.run(reopen()) + + +def test_interrupted_partial_maintenance_blocks_startup_after_lock_release(tmp_path, monkeypatch): + path, settings, full = _managed(tmp_path, None) + target = settings.model_copy(update={"runtime": RuntimeConfig(artifact_processing_supervisor_mode="dedicated")}) + runner = deployment_runner(target, bundle="processing") + plan = runner.plan() + original_backup = SQLiteBackupProvider.create_backup + + def interrupt_after_backup(provider, context): + original_backup(provider, context) + raise KeyboardInterrupt + + with monkeypatch.context() as patch: + patch.setattr(SQLiteBackupProvider, "create_backup", interrupt_after_backup) + with pytest.raises(KeyboardInterrupt): + runner.apply(plan_id=plan.plan_id, accepted=True, maintenance_confirmed=True) + with pytest.raises(MigrationError, match="recovery_required"): + open_runtime(settings.database) + environment = tmp_path / "server.env" + environment.write_text( + f"POWERCONTEXT_SERVER_DATABASE_KIND=sqlite\nPOWERCONTEXT_SERVER_DATABASE_URL={settings.database.url}\n" + ) + database_before = path.read_bytes() + journals_before = {entry: entry.read_bytes() for entry in full.evidence_path.parent.rglob("maintenance.json")} + for action in ("status", "plan", "verify", "apply"): + result = CliRunner().invoke(create_cli([app]), ["server", "db-migrate", action, "--env-file", str(environment)]) + assert result.exit_code == 1, result.output + assert json.loads(result.output)["error"] == "recovery_required", result.output + assert path.read_bytes() == database_before + assert { + entry: entry.read_bytes() for entry in full.evidence_path.parent.rglob("maintenance.json") + } == journals_before + resumed = runner.plan() + assert runner.apply(plan_id=resumed.plan_id, accepted=True, maintenance_confirmed=True).state == "ready" + assert deployment_runner(target).verify().state == "ready" + + +def test_seekdb_startup_rejects_local_maintenance_before_opening_engine(tmp_path): + from powercontext.builtin.persistence.migrations.locking import local_migration_lock + from powercontext.builtin.persistence.seekdb import SeekDBConfig, SeekDBProfile + + target = tmp_path / "seekdb" + + async def start(): + async with SeekDBProfile.open(SeekDBConfig(path=target), tables=()): + pytest.fail("Startup admitted an embedded engine during maintenance") + + with local_migration_lock(target), pytest.raises(MigrationError, match="migration_locked"): + asyncio.run(start()) + assert not target.exists() + + +@pytest.mark.parametrize("backend", ["sqlite", "seekdb", "oceanbase"]) +@pytest.mark.parametrize("operation", ["processing_configuration", "atomic_projection"]) +@pytest.mark.parametrize("state", ["preparing", "active"]) +def test_full_admission_rejects_unfinished_domain_journals(tmp_path, monkeypatch, backend, operation, state): + """Native cases model the connection identity; they do not claim native engine acceptance.""" + from pydantic import SecretStr + + from powercontext.builtin.persistence.migrations.connections import BackendIdentity, MaintenanceConnections + from powercontext.builtin.persistence.migrations.models import digest + from powercontext.builtin.persistence.migrations.mysql import MySQLMigrationRunner, _Evidence, _Journal + from powercontext.builtin.persistence.oceanbase import OceanBaseConfig + from powercontext.builtin.persistence.seekdb import SeekDBConfig + + path, _settings, full = _managed(tmp_path, None) + directory = full.evidence_path.parent + identity = BackendIdentity("sqlite", digest({"kind": "sqlite", "path": str(path.resolve())}), path.name) + if backend != "sqlite": + config = ( + SeekDBConfig(path=tmp_path / "seekdb") + 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 + ) + full = MySQLMigrationRunner(connections, full.bundle, evidence_directory=directory) + identity = BackendIdentity(backend, "modeled-native-database", "test") + monkeypatch.setattr(connections, "run", lambda callback, **_options: callback(None, identity, lambda: None)) + assert full.plan().state == "uninitialized" + else: + assert full.verify().state == "ready" + journal = _Journal(directory / operation, identity) + journal.write( + _Evidence( + state=state, + database_id=identity.database_id, + source_revision=full.bundle.head, + source_fingerprint="interrupted-domain-maintenance", + bundle_checksum="domain-bundle", + configuration_digest="domain-configuration", + last_revision=full.bundle.head, + maintenance_window_id="interrupted-domain-window", + backup_policy="skip", + backup_state="skipped", + ) + ) + before = journal.path.read_bytes() + database_before = path.read_bytes() + for inspect in (full.plan, full.verify, lambda: full.apply(accepted=True, maintenance_confirmed=True)): + with pytest.raises(MigrationError, match="recovery_required"): + inspect() + assert journal.path.read_bytes() == before + assert path.read_bytes() == database_before 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_migration_revision_names.py b/tests/test_migration_revision_names.py new file mode 100644 index 0000000000..562f82f9ab --- /dev/null +++ b/tests/test_migration_revision_names.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. + +"""Release-based names preserve the existing upgrade and recovery identities.""" + +import json +import sqlite3 +from contextlib import closing + +import pytest +from typer.testing import CliRunner + +from powercontext.builtin.persistence.migrations.revision_names import describe_revision +from powercontext.cli.app import create_cli +from powercontext.server.cli import app +from tests.builtin.persistence.test_release140_server_migrations import _source +from tests.test_registered_source_maintenance_cli import _environment + + +@pytest.mark.parametrize("release", ["1.3.0", "1.4.0"]) +@pytest.mark.parametrize("vectors", [False, True]) +def test_release_names_preserve_source_upgrade_plan_and_evidence(tmp_path, monkeypatch, release, vectors): + settings, embedding, runner = _source(tmp_path, release=release, vectors=vectors) + monkeypatch.chdir(tmp_path) + _environment(monkeypatch, settings, embedding) + cli = create_cli([app]) + + def invoke(action, *options): + result = CliRunner().invoke(cli, ["server", "db-migrate", action, *options]) + assert result.exit_code == 0, result.output + return json.loads(result.output) + + reviewed = runner.plan(backup_policy="skip") + original = runner.database.read_bytes() + status = invoke("status") + plan = invoke("plan", "--backup", "skip") + expected_source = { + "revision": "s1300" if release == "1.3.0" else "r1400", + "name": "v" + release.replace(".", "_") + "_000", + "software_version": release, + "sequence": 0, + } + assert status["source_migration"] == plan["source_migration"] == expected_source + assert plan["plan_id"] == reviewed.plan_id + assert runner.database.read_bytes() == original + assert [item["name"] for item in plan["pending_migrations"]] == ["v1_4_1_001", "v1_4_1_002", "v1_4_1_003"] + assert [item["revision"] for item in plan["pending_migrations"]] == list(reviewed.revisions) + target = {"revision": "s1402", "name": "v1_4_1_003", "software_version": "1.4.1", "sequence": 3} + assert plan["target_migration"] == target + + applied = invoke( + "apply", + "--backup", + "skip", + "--plan-id", + reviewed.plan_id, + "--yes", + "--maintenance-confirmed", + "--accept-no-backup", + ) + assert applied["migration"] == target and applied["server_ready"] + with closing(sqlite3.connect(runner.database)) as connection: + assert connection.execute("SELECT version_num FROM pc_schema_revision").fetchall() == [("s1402",)] + evidence = runner.evidence_path.read_bytes() + recorded = json.loads(evidence) + assert recorded["source_revision"] == expected_source["revision"] + assert set(recorded["release_checksums"]) == {expected_source["revision"], "s1400", "s1401", "s1402"} + assert runner.verify().state == "ready" + assert invoke("verify")["migration"] == target + repeated = invoke("apply") + assert repeated["changed"] is False and repeated["migration"] == target + assert runner.evidence_path.read_bytes() == evidence + + +@pytest.mark.parametrize("revision", ["v1_4_2_001", "v1_5_0_002"]) +def test_new_revisions_describe_their_own_release_without_an_alias(revision): + _prefix, minor, patch, sequence = revision.split("_") + assert describe_revision(revision) == { + "revision": revision, + "name": revision, + "software_version": f"1.{minor}.{patch}", + "sequence": int(sequence), + } + + +def test_unregistered_legacy_revisions_do_not_claim_a_software_release(): + assert describe_revision(None) is None + assert describe_revision("p0004") == { + "revision": "p0004", + "name": "p0004", + "software_version": None, + "sequence": None, + } diff --git a/tests/test_native_service_diagnostics.py b/tests/test_native_service_diagnostics.py index 09c47995a8..871b491341 100644 --- a/tests/test_native_service_diagnostics.py +++ b/tests/test_native_service_diagnostics.py @@ -14,15 +14,20 @@ from __future__ import annotations +import errno import json +import socket import subprocess +import sys +from contextlib import nullcontext from pathlib import Path from unittest.mock import Mock import pytest from powercontext.service.adapters.launchd import LaunchdUserAdapter -from powercontext.service.model import ServiceError, SupportState +from powercontext.service.model import ProbeState, ServiceError, SupportState +from powercontext.service.probe import probe_server from tests.native import test_personal_service_lifecycle as lifecycle @@ -64,3 +69,40 @@ def cleanup(adapter: LaunchdUserAdapter) -> None: assert snapshot == {"retry_token_present": True, "retry_state": '{"attempts":[1]}'} assert "state = running\npid = 123" in (logs / "launchd-before-cleanup.log").read_text(encoding="utf-8") assert (logs / "launchd-process-sample.log").read_text(encoding="utf-8") == "Service process stack sample" + + +@pytest.mark.parametrize("startup_raises", [False, True], ids=["normal-exit", "failed-start"]) +def test_native_startup_fault_drains_probes_and_releases_port( + monkeypatch: pytest.MonkeyPatch, startup_raises: bool +) -> None: + port = lifecycle._unused_loopback_port() + address = ("127.0.0.1", port) + adapter = Mock() + + def start(*, reload_definition: bool) -> None: + # The native manager boundary is modeled; port ownership and probes are real. + with socket.socket() as contender: + with pytest.raises(OSError) as conflict: + contender.bind(address) + assert conflict.value.errno == errno.EADDRINUSE + if startup_raises: + raise ServiceError("fixture start failed") # noqa: TRY003 + + adapter.start.side_effect = start + expected = pytest.raises(ServiceError, match="fixture start failed") if startup_raises else nullcontext() + with expected, lifecycle._native_upgrade_fault(adapter, port, "startup", monkeypatch): + adapter.start(reload_definition=False) + # A controller and retried native launches can probe more often than + # the listener backlog; every probe must still see a port conflict. + for _ in range(40): + assert probe_server(f"http://127.0.0.1:{port}").state is ProbeState.CONFLICT + with socket.create_connection(address, timeout=2): + pass + + # Exiting the fault, including a failed adapter.start(), releases the endpoint + # so the subsequent native recovery can bind it normally. + with socket.socket() as recovered: + if sys.platform != "win32": + recovered.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + recovered.bind(address) + recovered.listen() 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_registered_source_maintenance_cli.py b/tests/test_registered_source_maintenance_cli.py new file mode 100644 index 0000000000..e4d6b2c13e --- /dev/null +++ b/tests/test_registered_source_maintenance_cli.py @@ -0,0 +1,121 @@ +# 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. + +"""Compatibility commands preserve registered Server sources before adoption.""" + +import json +import os +import sqlite3 +from contextlib import closing + +import pytest +from typer.testing import CliRunner + +from powercontext.cli.app import create_cli +from powercontext.server.cli import app +from tests.builtin.persistence.test_release140_server_migrations import _candidate_rows, _source + + +def _environment(monkeypatch, settings, embedding): + 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", settings.database.url) + monkeypatch.setenv("POWERCONTEXT_SERVER_MCP_ENABLED", "false") + monkeypatch.setenv("POWERCONTEXT_SERVER_CODE", settings.code.model_dump_json()) + if embedding is not None: + monkeypatch.setenv("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_MODEL", embedding.model) + monkeypatch.setenv("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_PROFILE_ID", embedding.profile_id) + monkeypatch.setenv("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_DIMENSION", str(embedding.dimension)) + monkeypatch.setenv("POWERCONTEXT_SERVER_INFERENCE_EMBEDDING_NORMALIZATION", embedding.normalization) + + +@pytest.mark.parametrize(("vectors", "code"), [(False, False), (True, False), (False, True), (True, True)]) +def test_release140_compatibility_commands_preserve_source_and_share_full_upgrade(tmp_path, monkeypatch, vectors, code): + settings, embedding, runner = _source(tmp_path, vectors=vectors, code=code) + monkeypatch.chdir(tmp_path) + _environment(monkeypatch, settings, embedding) + cli = create_cli([app]) + original = runner.database.read_bytes() + candidates = _candidate_rows(runner.database) + + for command in ("processing-migrate", "atomic-memory-rebuild-projection", "atomic-memory-migrate"): + result = CliRunner().invoke(cli, ["server", command, "--action", "apply", "--maintenance-confirmed"]) + assert result.exit_code != 0, result.output + assert runner.database.read_bytes() == original + status = CliRunner().invoke(cli, ["server", "db-migrate", "status"]) + assert status.exit_code == 0, status.output + assert json.loads(status.output)["source_revision"] == "r1400" + + arguments = ["server", "atomic-memory-migrate", "--action"] + planned = CliRunner().invoke(cli, [*arguments, "plan", "--backup", "skip"]) + assert planned.exit_code == 0, planned.output + plan = json.loads(planned.output) + assert plan["source_revision"] == "r1400" and plan["target_revision"] == "s1402" + assert plan["readiness_scope"] == "server" + full = CliRunner().invoke(cli, ["server", "db-migrate", "plan", "--backup", "skip"]) + assert full.exit_code == 0, full.output + assert json.loads(full.output)["plan_id"] == plan["plan_id"] + assert runner.database.read_bytes() == original + + applied = CliRunner().invoke( + cli, + [ + *arguments, + "apply", + "--maintenance-confirmed", + "--plan-id", + plan["plan_id"], + "--backup", + "skip", + "--accept-no-backup", + "--yes", + ], + ) + assert applied.exit_code == 0, applied.output + assert json.loads(applied.output)["readiness_scope"] == "server" + verified = CliRunner().invoke(cli, ["server", "db-migrate", "verify"]) + assert verified.exit_code == 0, verified.output + assert json.loads(verified.output)["state"] == "ready" + assert _candidate_rows(runner.database) == candidates + + +@pytest.mark.parametrize("command", ["atomic-memory-migrate", "processing-migrate", "atomic-memory-rebuild-projection"]) +@pytest.mark.parametrize("mismatch", ["extra-object", "missing-embedding-configuration"]) +def test_unrecognized_server_never_falls_back_to_legacy_writes(tmp_path, monkeypatch, command, mismatch): + vectors = mismatch == "missing-embedding-configuration" + settings, embedding, runner = _source(tmp_path, vectors=vectors) + monkeypatch.chdir(tmp_path) + if mismatch == "extra-object": + with closing(sqlite3.connect(runner.database)) as connection, connection: + connection.execute("CREATE TABLE unknown_server_object(value TEXT)") + _environment(monkeypatch, settings, None) + original = runner.database.read_bytes() + cli = create_cli([app]) + + denied = CliRunner().invoke(cli, ["server", command, "--action", "apply", "--maintenance-confirmed"]) + + assert denied.exit_code != 0, denied.output + assert runner.database.read_bytes() == original + if vectors: + _environment(monkeypatch, settings, embedding) + status = CliRunner().invoke(cli, ["server", "db-migrate", "status"]) + assert status.exit_code == 0, status.output + assert json.loads(status.output)["source_revision"] == "r1400" + else: + status = CliRunner().invoke(cli, ["server", "db-migrate", "status"]) + assert status.exit_code != 0 + assert "unknown_baseline" in status.output + assert runner.database.read_bytes() == original diff --git a/tests/test_release_version.py b/tests/test_release_version.py index 9629fd7c07..5af0f99c3c 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 and 1.4.0 require passing full Server acceptance" in english + assert "1.3.0 和 1.4.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..8377f2cf9b --- /dev/null +++ b/tests/test_upgrade_release_gate.py @@ -0,0 +1,1207 @@ +# 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 re +import shutil +import signal +import subprocess +import sys +import time +import zipfile +from pathlib import Path +from urllib.parse import urlsplit +from xml.etree import ElementTree + +import parallel_upgrade_release_gate as parallel +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 +from tests.release_upgrades.test_full_server import environment_for + + +@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"]) + + +@pytest.mark.parametrize("target", ["1.4.1", "1.5.0"]) +def test_reviewed_sources_each_require_every_backend_and_configuration(release, monkeypatch, target): + _, _, policy = release + profiles = [ + { + "backend": backend, + "configuration": configuration, + "suite": "tests/release_upgrades/test_example.py", + } + for backend in ("sqlite", "seekdb", "oceanbase") + for configuration in ( + "full-server-no-vector", + "full-server-code", + "full-server-vector", + "full-server-vector-code", + ) + ] + policy["automatic_sources"]["profiles"] = profiles + policy["cases"] = [{"source_version": "1.4.0", **profile} for profile in profiles] + catalog = catalog_for("1.3.0", "1.4.0", "1.4.1", "1.5.0") + catalog["releases"]["1.4.0"][0]["digests"]["sha256"] = "b" * 64 + monkeypatch.setattr(matrix, "fetch_catalog", lambda: catalog) + resolved = matrix.resolve_matrix(policy, target) + assert resolved["required_cases"] == resolved["candidate_cases"] + assert len(resolved["required_cases"]) == 24 + assert { + (case["source_version"], case["backend"], case["configuration"]) for case in resolved["required_cases"] + } == { + (source, profile["backend"], profile["configuration"]) for source in ("1.3.0", "1.4.0") for profile in profiles + } + for case in resolved["required_cases"]: + assert case["source_artifact"]["sha256"] == {"1.3.0": "a" * 64, "1.4.0": "b" * 64}[case["source_version"]] + del catalog["releases"]["1.4.0"] + with pytest.raises(ValueError, match=r"1\.4\.0"): + matrix.resolve_matrix(policy, target) + + +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"] + assert build["uses"] == "./.github/workflows/upgrade-release.yml" + assert build["with"]["release"] is True + assert build["with"]["selection"] == "automatic" + assert not build.get("continue-on-error") + shared = yaml.safe_load((root / ".github/workflows/upgrade-release.yml").read_text())["jobs"] + prepare = shared["prepare"]["steps"] + freeze = next(step for step in prepare if "parallel_upgrade_release_gate.py prepare" in step.get("run", "")) + assert "--release --release-event" in freeze["run"] + steps = shared["collect"]["steps"] + index = next(i for i, step in enumerate(steps) if "parallel_upgrade_release_gate.py collect" in step.get("run", "")) + assert "if" not in steps[index] and not steps[index].get("continue-on-error") + upload = next(i for i, step in enumerate(steps) if step.get("with", {}).get("name") == "release-dists") + assert index < upload + assert steps[upload]["if"] == "success() && inputs.release" + assert steps[upload]["with"]["path"] == "${{ runner.temp }}/upgrade-release-evidence/validated-dist/*" + assert shared["collect"]["needs"] == ["prepare", "cases"] + assert any('test "$CASE_JOBS_RESULT" = success' in step.get("run", "") for step in steps) + for name in ("pypi-publish", "release-assets"): + assert workflow["jobs"][name]["needs"] == "release-build" + main = yaml.safe_load((root / ".github/workflows/master.yml").read_text())["jobs"] + assert main["upgrade-acceptance"]["uses"] == build["uses"] + verdict = main["upgrade-release-gate"] + assert verdict["name"] == "Upgrade release gate" and verdict["if"] == "always()" + assert verdict["needs"] == "upgrade-acceptance" + assert verdict["steps"][0]["run"] == 'test "$UPGRADE_RESULT" = success' + + +@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, monkeypatch): + 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", + } + monkeypatch.setattr(matrix, "fetch_catalog", lambda: catalog_for("1.3.0", "1.4.0", "1.4.1")) + resolved = matrix.resolve_matrix(policy, policy["target_version"]) + assert len(resolved["required_cases"]) == 12 + assert {case["source_version"] for case in resolved["required_cases"]} == {"1.3.0"} + assert {case["backend"] for case in resolved["required_cases"] if case["configuration"] == configuration} == { + "sqlite", + "seekdb", + "oceanbase", + } + + +def test_upgrade_cases_require_isolated_backends_and_always_clean_up(): + root = Path(__file__).parents[1] + workflow = yaml.safe_load((root / ".github/workflows/upgrade-release.yml").read_text()) + job = workflow["jobs"]["cases"] + assert job["needs"] == "prepare" + assert job["strategy"]["fail-fast"] is False + assert job["strategy"]["matrix"] == "${{ fromJSON(needs.prepare.outputs.matrix) }}" + 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 "parallel_upgrade_release_gate.py case" in step.get("run", "") + ) + assert setup_index < gate_index + assert steps[setup_index]["if"] == "matrix.backend == 'oceanbase'" + assert "if" not in steps[gate_index] + assert not any(step.get("continue-on-error") for step in steps) + 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() && matrix.backend == 'oceanbase'" + + +def test_oceanbase_sources_and_profiles_use_separate_provisioned_databases(tmp_path, monkeypatch): + urls = { + "PC_UPGRADE_OCEANBASE_URL": "mysql+aoceanbase://root%40test:fixture@127.0.0.1:2881/pc_upgrade_test?charset=utf8mb4", + "PC_UPGRADE_OCEANBASE_FRESH_URL": "mysql+aoceanbase://root%40test:fixture@127.0.0.1:2881/pc_upgrade_fresh?charset=utf8mb4", + } + for key, value in urls.items(): + monkeypatch.setenv(key, value) + action = yaml.safe_load( + (Path(__file__).parents[1] / ".github/actions/setup-upgrade-oceanbase/action.yml").read_text() + ) + setup = action["runs"]["steps"][0]["run"] + provisioned = set(re.findall(r"CREATE DATABASE (pc_upgrade[a-z0-9_]*)", setup)) + provisioned.update(re.findall(r"--env OB_DATABASE=(pc_upgrade[a-z0-9_]*)", setup)) + selected = set() + for source in ("1.3.0", "1.4.0"): + for configuration in ( + "full-server-no-vector", + "full-server-code", + "full-server-vector", + "full-server-vector-code", + ): + case = { + "source_version": source, + "backend": "oceanbase", + "configuration": configuration, + "code_enabled": configuration.endswith("-code"), + "vector_dimension": 0, + "model": {"url": "http://127.0.0.1:1/v1"}, + "work": tmp_path, + } + for fresh in (False, True): + environment = environment_for(case, fresh=fresh) + selected_url = urlsplit(environment["POWERCONTEXT_SERVER_DATABASE_URL"]) + assert selected_url.geturl() == environment["PC_UPGRADE_OCEANBASE_URL"] + assert selected_url.path not in selected, "Source/configuration cases must not reuse stored data" + assert selected_url.path.removeprefix("/") in provisioned + selected.add(selected_url.path) + original = urlsplit(urls["PC_UPGRADE_OCEANBASE_FRESH_URL" if fresh else "PC_UPGRADE_OCEANBASE_URL"]) + assert selected_url._replace(path=original.path) == original + assert len(selected) == 16 + assert {key: os.environ[key] for key in urls} == urls + + +@pytest.mark.parametrize( + "database", ["production", "pc_upgrade_vector_test_deadbeef", "pc_upgrade_140_code_test_deadbeef"] +) +def test_oceanbase_source_adapter_rejects_other_databases(tmp_path, monkeypatch, database): + monkeypatch.setenv( + "PC_UPGRADE_OCEANBASE_URL", + f"mysql+aoceanbase://root%40test:fixture@127.0.0.1:2881/{database}?charset=utf8mb4", + ) + case = { + "source_version": "1.4.0", + "backend": "oceanbase", + "configuration": "full-server-vector", + "code_enabled": False, + "vector_dimension": 3, + "model": {"url": "http://127.0.0.1:1/v1"}, + "work": tmp_path, + } + with pytest.raises(AssertionError, match="dedicated disposable"): + environment_for(case) + + +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() + journal = b'{"state":"active","last_revision":"s1300","pending_revision":"s1400"}' + maintenance = "database.pc-migration-state/disposable-database/maintenance.json" + fresh_maintenance = "fresh.pc-migration-state/disposable-fresh/maintenance.json" + + 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") + for relative in (maintenance, fresh_maintenance): + path = work / relative + path.parent.mkdir(parents=True) + path.write_bytes(journal) + (path.parent / "backup.data").write_bytes(b"backup payload must not be collected") + 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", + maintenance, + fresh_maintenance, + } + 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" + assert archive.read(maintenance) == journal + assert archive.read(fresh_maintenance) == journal + manifest = json.loads((output / "seekdb-native-logs.json").read_text()) + assert len(manifest["files"]) == 4 + assert not any(item["truncated"] for item in manifest["files"]) + + +def test_seekdb_diagnostics_failure_preserves_acceptance_failure(tmp_path, capsys): + with ( + pytest.raises(gate.GateError, match="fixture migration failed"), + gate.case_workspace("seekdb", tmp_path / "missing-output") as work, + ): + raise gate.GateError("fixture migration failed") # noqa: TRY003 -- synthetic acceptance failure + + assert not work.exists() + assert "seekDB native log collection failed: FileNotFoundError" in capsys.readouterr().err + + +def test_seekdb_diagnostics_keep_bounded_head_and_tail_of_large_native_logs(tmp_path): + output = tmp_path / "evidence" + output.mkdir() + content = b"initial DDL diagnostic\n" + b"x" * (5 * 1024 * 1024) + b"final wait diagnostic\n" + with gate.case_workspace("seekdb", output) as work: + native = work / "database/log" + native.mkdir(parents=True) + (native / "observer.log").write_bytes(content) + + with zipfile.ZipFile(output / "seekdb-native-logs.zip") as archive: + head = archive.read("head/database/log/observer.log") + tail = archive.read("tail/database/log/observer.log") + assert len(head) + len(tail) <= 4 * 1024 * 1024 + assert head.startswith(b"initial DDL diagnostic\n") + assert tail.endswith(b"final wait diagnostic\n") + manifest = json.loads((output / "seekdb-native-logs.json").read_text()) + record = manifest["files"][0] + assert record["truncated"] is True + assert record["size_bytes"] == len(content) + assert record["captured_bytes"] == len(head) + len(tail) + for segment in record["segments"]: + start = segment["offset_bytes"] + assert archive.read(segment["path"]) == content[start : start + segment["captured_bytes"]] + + +def test_seekdb_diagnostics_share_one_budget_with_maintenance_evidence(tmp_path): + output = tmp_path / "evidence" + output.mkdir() + maintenance = "database.pc-migration-state/disposable-database/maintenance.json" + journal = b'{"state":"active","pending_revision":"s1400"}' + with gate.case_workspace("seekdb", output) as work: + path = work / maintenance + path.parent.mkdir(parents=True) + path.write_bytes(journal) + for store in ("database", "fresh"): + native = work / store / "log" + native.mkdir(parents=True) + for number in range(3): + (native / f"observer.log.{number}").write_bytes(b"x" * (5 * 1024 * 1024)) + + manifest = json.loads((output / "seekdb-native-logs.json").read_text()) + with zipfile.ZipFile(output / "seekdb-native-logs.zip") as archive: + assert archive.read(maintenance) == journal + captured = sum(entry.file_size for entry in archive.infolist()) + assert captured <= 16 * 1024 * 1024 + assert captured == sum(record["captured_bytes"] for record in manifest["files"]) + assert manifest["budget_exhausted"] is True + + +@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 +from urllib.parse import urlsplit + +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 + + +@pytest.fixture +def parallel_reports(release, monkeypatch, request): + root, wheel, policy = release + selection = getattr(request, "param", "full") + if hasattr(request, "param"): + policy["automatic_sources"]["profiles"] = [ + {"backend": backend, "configuration": configuration, "suite": "tests/release_upgrades/test_example.py"} + for backend in ("sqlite", "seekdb", "oceanbase") + for configuration in ( + "full-server-no-vector", + "full-server-vector", + "full-server-code", + "full-server-vector-code", + ) + ] + policy["mode"] = "supported-upgrades" + parallel.write_json(root / gate.MANIFEST, policy) + gate.check_docs(root, policy, write=True) + dist = root / "dist" + dist.mkdir() + shutil.copyfile(wheel, dist / wheel.name) + prepared = root / "prepared" + scheduled = parallel.prepare(root, dist, prepared, release=False, release_notes=None, selection=selection) + executed = [] + + def execute(_root, tested_wheel, case, output): + executed.append(case) + assert gate.sha256(tested_wheel) == gate.sha256(wheel) + output.mkdir() + installed = {"source": {"powercontext": case["source_version"]}, "target": {"powercontext": "2.0.0"}} + backend = { + "backend": case["backend"], + "configuration": case["configuration"], + "engine_version": "fixture", + "driver_version": "fixture", + "source_schema": "source", + "target_schema": "target", + "fresh_schema": "fresh", + "tenant_mode": "mysql", + "proxy_version": "direct", + } + if case["configuration"] in {"full-server-code", "full-server-vector-code"}: + backend["code"] = { + "fingerprint": "a" * 64, + "immutable_cache_files_preserved": True, + "symbol_ids_preserved": True, + "server_query_ready": True, + } + if case["configuration"] in {"full-server-vector", "full-server-vector-code"}: + backend["vectors"] = { + "dimension": 4 if case["configuration"].endswith("-code") else 3, + "maintenance_embedding_requests": 0, + "historical_atomic_and_topic_embeddings_preserved": True, + "historical_and_new_vector_reads": True, + "fresh_vector_read": True, + "profile_drift_rejected": True, + "missing_sqlite_helper_rejected": True, + } + parallel.write_json(output / "installed.json", installed) + parallel.write_json(output / "backend.json", backend) + tree = ElementTree.Element("testsuite") + for phase in gate.PHASES: + ElementTree.SubElement(tree, "testcase", name=phase) + ElementTree.ElementTree(tree).write(output / "junit.xml") + return {"case": case, "status": "passed", "installed": installed, "database": backend} + + monkeypatch.setattr(gate, "run_case", execute) + # Source discovery must not recur on independent workers or during collection. + monkeypatch.setattr(matrix, "fetch_catalog", lambda: pytest.fail("Catalog resolved twice")) + reports = root / "reports" + for entry in reversed(scheduled["include"]): + parallel.execute_case(root, prepared, entry["index"], reports / f"case-{entry['index']}") + assert len(executed) == len(scheduled["include"]) + assert len({(case["source_version"], case["backend"], case["configuration"]) for case in executed}) == len(executed) + return root, prepared, reports, policy + + +def test_parallel_collection_accepts_each_case_once_and_keeps_exact_artifacts(parallel_reports): + root, prepared, reports, _ = parallel_reports + accepted = parallel.collect(root, prepared, reports, root / "accepted") + plan = json.loads((prepared / "plan.json").read_text()) + assert {path.name: gate.sha256(path) for path in accepted} == plan["artifacts"] + verdict = json.loads((root / "accepted/result.json").read_text()) + assert verdict["status"] == "passed" + assert [result["case"] for result in verdict["cases"]] == plan["resolved_matrix"]["required_cases"] + + +@pytest.mark.parametrize( + "broken", + [ + "missing", + "duplicate", + "stale", + "failed", + "wrong-case", + "skipped", + "missing-phase", + "installed", + "backend", + "artifact", + ], +) +def test_parallel_collection_rejects_incomplete_or_mixed_evidence(parallel_reports, broken): + root, prepared, reports, _ = parallel_reports + path = reports / "case-0/result.json" + result = json.loads(path.read_text()) + if broken == "missing": + path.unlink() + elif broken == "duplicate": + another = reports / "case-1/result.json" + parallel.write_json(another, result) + elif broken == "stale": + result["plan_sha256"] = "0" * 64 + parallel.write_json(path, result) + elif broken == "failed": + result["status"] = "failed" + parallel.write_json(path, result) + elif broken == "wrong-case": + result["result"]["case"]["configuration"] = "different-profile" + parallel.write_json(path, result) + elif broken in {"skipped", "missing-phase"}: + report = path.parent / "acceptance/junit.xml" + tree = ElementTree.parse(report) # noqa: S314 -- disposable locally generated test report + if broken == "skipped": + ElementTree.SubElement(next(tree.iter("testcase")), "skipped") + else: + tree.getroot().remove(next(tree.iter("testcase"))) + tree.write(report) + elif broken == "installed": + parallel.write_json(path.parent / "acceptance/installed.json", {"target": {"powercontext": "old"}}) + elif broken == "backend": + parallel.write_json(path.parent / "acceptance/backend.json", {"backend": "sqlite"}) + else: + wheel = next((prepared / "inputs").glob("*.whl")) + wheel.write_bytes(b"a different build") + with pytest.raises(gate.GateError): + parallel.collect(root, prepared, reports, root / "accepted") + assert not (root / "accepted/validated-dist").exists() + + +def test_parallel_workers_reject_another_run(parallel_reports, monkeypatch): + root, prepared, reports, _ = parallel_reports + monkeypatch.setenv("GITHUB_RUN_ID", "another-run") + with pytest.raises(gate.GateError, match="another workflow"): + parallel.collect(root, prepared, reports, root / "accepted") + + +def test_parallel_failed_case_still_records_failure_and_allows_other_workers(release, monkeypatch): + root, wheel, _ = release + dist = root / "dist" + dist.mkdir() + shutil.copyfile(wheel, dist / wheel.name) + prepared = root / "prepared" + parallel.prepare(root, dist, prepared, release=False, release_notes=None) + + def fail(*_args): + raise gate.GateError("migration_required") + + monkeypatch.setattr(gate, "run_case", fail) + with pytest.raises(gate.GateError, match="migration_required"): + parallel.execute_case(root, prepared, 0, root / "reports/case-0") + assert json.loads((root / "reports/case-0/result.json").read_text())["status"] == "failed" + monkeypatch.setattr(gate, "run_case", lambda *_args: {"status": "passed"}) + parallel.execute_case(root, prepared, 1, root / "reports/case-1") + assert json.loads((root / "reports/case-1/result.json").read_text())["status"] == "passed" + + +@pytest.mark.parametrize("changed", ["policy", "checkout", "matrix"]) +def test_parallel_workers_reject_changed_preparation_inputs(parallel_reports, monkeypatch, changed): + root, prepared, _reports, policy = parallel_reports + if changed == "policy": + policy["schema_review"]["rationale"] += " Changed review." + parallel.write_json(root / gate.MANIFEST, policy) + elif changed == "checkout": + monkeypatch.setenv("GITHUB_SHA", "another-commit") + else: + plan = json.loads((prepared / "plan.json").read_text()) + plan["resolved_matrix"]["required_cases"].pop() + parallel.write_json(prepared / "plan.json", plan) + with pytest.raises(gate.GateError): + parallel.execute_case(root, prepared, 0, root / "another-case") + assert not (root / "another-case").exists() + + +def test_parallel_passes_do_not_override_unreviewed_release_support(parallel_reports): + root, prepared, reports, policy = parallel_reports + # Model the release preparation metadata; distribution validation itself is + # exercised with real sdist rebuilds in test_upgrade_publication.py. + plan = json.loads((prepared / "plan.json").read_text()) + plan["release"] = True + plan["release_notes"] = gate.support_text(policy, "en") + parallel.write_json(prepared / "plan.json", plan) + for path in reports.glob("*/result.json"): + result = json.loads(path.read_text()) + result["plan_sha256"] = gate.sha256(prepared / "plan.json") + parallel.write_json(path, result) + with pytest.raises(gate.GateError, match="support declaration"): + parallel.collect(root, prepared, reports, root / "accepted") + assert not (root / "accepted/validated-dist").exists() + + +def test_parallel_worker_rejects_artifact_changed_during_execution(release, monkeypatch): + root, wheel, _ = release + dist = root / "dist" + dist.mkdir() + shutil.copyfile(wheel, dist / wheel.name) + prepared = root / "prepared" + parallel.prepare(root, dist, prepared, release=False, release_notes=None) + + def replace_wheel(_root, tested_wheel, _case, _output): + tested_wheel.write_bytes(b"replaced while acceptance ran") + return {"status": "passed"} + + monkeypatch.setattr(gate, "run_case", replace_wheel) + with pytest.raises(gate.GateError, match="distributions changed"): + parallel.execute_case(root, prepared, 0, root / "reports/case-0") + assert json.loads((root / "reports/case-0/result.json").read_text())["status"] == "failed" + + +@pytest.mark.parametrize("parallel_reports", ["automatic", "seekdb-code", "full"], indirect=True) +def test_manual_seekdb_code_selection_preserves_publication_boundary(parallel_reports): + root, prepared, reports, policy = parallel_reports + plan = json.loads((prepared / "plan.json").read_text()) + selection = plan["selection"] + expected = {"automatic": 10, "seekdb-code": 2, "full": 12}[selection] + if selection != "seekdb-code": + # Model release metadata, as in the existing support-declaration test. + # Real sdist validation remains covered by test_upgrade_publication.py. + plan["release"] = True + plan["release_notes"] = gate.support_text(policy, "en") + parallel.write_json(prepared / "plan.json", plan) + for path in reports.glob("*/result.json"): + result = json.loads(path.read_text()) + result["plan_sha256"] = gate.sha256(prepared / "plan.json") + parallel.write_json(path, result) + accepted = parallel.collect(root, prepared, reports, root / "accepted") + verdict = json.loads((root / "accepted/result.json").read_text()) + assert len(verdict["cases"]) == expected + assert len(verdict["deferred_cases"]) == 12 - expected + assert verdict["complete"] is (selection == "full") + assert bool(accepted) is (selection != "seekdb-code") + if selection == "automatic": + assert all(gate.manual_upgrade_case(case) for case in verdict["deferred_cases"]) + publication = json.loads((root / "accepted/publication.json").read_text()) + assert publication["complete"] is False + assert publication["deferred_cases"] == verdict["deferred_cases"] + elif selection == "seekdb-code": + assert all(gate.manual_upgrade_case(result["case"]) for result in verdict["cases"]) + assert not (root / "accepted/publication.json").exists() + plan["release"] = True + plan["release_notes"] = gate.support_text(policy, "en") + parallel.write_json(prepared / "plan.json", plan) + with pytest.raises(gate.GateError, match="cannot authorize publication"): + parallel.collect(root, prepared, reports, root / "release-rejected") + support = gate.support_text(policy, "en") + assert "| seekdb | full-server-code |" not in support + assert "| seekdb | full-server-vector-code |" not in support + assert "| oceanbase | full-server-vector-code |" in support + + +@pytest.mark.parametrize("parallel_reports", ["automatic"], indirect=True) +@pytest.mark.parametrize("broken", ["missing", "failed", "skipped"]) +def test_manual_waiver_does_not_accept_missing_or_failed_automatic_cases(parallel_reports, broken): + root, prepared, reports, _ = parallel_reports + path = next(reports.glob("*/result.json")) + if broken == "missing": + path.unlink() + elif broken == "failed": + result = json.loads(path.read_text()) + result["status"] = "failed" + parallel.write_json(path, result) + else: + junit = path.parent / "acceptance/junit.xml" + tree = ElementTree.parse(junit) # noqa: S314 -- locally generated test fixture + ElementTree.SubElement(next(tree.iter("testcase")), "skipped") + tree.write(junit) + with pytest.raises(gate.GateError): + parallel.collect(root, prepared, reports, root / "accepted") + assert not (root / "accepted/validated-dist").exists() + + +@pytest.mark.parametrize("parallel_reports", ["automatic"], indirect=True) +def test_automatic_workers_reject_manual_case_execution(parallel_reports): + root, prepared, _reports, _policy = parallel_reports + plan = json.loads((prepared / "plan.json").read_text()) + index = next( + i for i, case in enumerate(plan["resolved_matrix"]["required_cases"]) if gate.manual_upgrade_case(case) + ) + with pytest.raises(gate.GateError, match="Unselected"): + parallel.execute_case(root, prepared, index, root / "unexpected-case") + assert not (root / "unexpected-case").exists() + + +def test_serial_publication_gate_also_waives_only_seekdb_code(release): + root, wheel, policy = release + policy["automatic_sources"]["profiles"].extend( + {"backend": backend, "configuration": configuration, "suite": "tests/release_upgrades/test_example.py"} + for backend in ("sqlite", "seekdb", "oceanbase") + for configuration in ("full-server-code", "full-server-vector-code") + ) + policy["mode"] = "supported-upgrades" + parallel.write_json(root / gate.MANIFEST, policy) + gate.check_docs(root, policy, write=True) + gate.gate(root, wheel, root / "serial", release=True, release_notes=gate.support_text(policy, "en")) + result = json.loads((root / "serial/result.json").read_text()) + assert len(result["cases"]) == 7 + assert len(result["deferred_cases"]) == 2 + assert result["complete"] is False + assert all(gate.manual_upgrade_case(case) for case in result["deferred_cases"]) + assert all(not gate.manual_upgrade_case(item["case"]) for item in result["cases"]) diff --git a/website/src/lib/releases.ts b/website/src/lib/releases.ts index ce7aec1644..087abd95bc 100644 --- a/website/src/lib/releases.ts +++ b/website/src/lib/releases.ts @@ -28,6 +28,36 @@ export interface Release { // Only stable releases belong here; publish pre-release notes on GitHub Releases. export const releases: Release[] = [ + { + version: 'v1.4.1', + date: '2026-10-26', + title: { + en: 'Registered database upgrades and recovery', + zh: '已登记数据库的升级与恢复', + }, + summary: { + en: 'PowerContext 1.4.1 completes the reusable Server migration framework with source recognition, ordered schema and data transitions, recovery evidence and full readiness checks. Release acceptance tests the real 1.3.0 source against the exact target artifact.', + zh: 'PowerContext 1.4.1 完善可复用的 Server 迁移框架,提供来源识别、有序结构与数据转换、恢复证据和完整就绪校验。发布验收使用真实 1.3.0 来源与本次目标产物。', + }, + changes: { + en: [ + 'Use db-migrate status, plan, apply and verify for registered Server storage. Exact source layouts, reviewed plans, maintenance locks and explicit backup policies guard schema and data transitions; incomplete validation cannot report full Server readiness.', + 'Preserve registered source history and original recovery evidence across interrupted upgrades and idempotent retries. Startup validates authoritative records, search projections, embedding profiles and retained history before serving requests.', + 'Display migration steps as v1_4_1_001 through v1_4_1_003 while preserving existing revision IDs and checksums. The 1.3.0 and 1.4.0 source bridges remain separate; registration does not certify every intermediate software release.', + 'Require twelve installed-release upgrade combinations for 1.3.0 across SQLite, embedded seekDB and OceanBase, with vectors and Code enabled or disabled. The 1.4.0 bridge is outside this CI upgrade matrix. Failed, skipped or missing cases block publication; the support policy remains upgrade-required until acceptance is complete.', + 'Stop writers and back up existing databases before maintenance. Drain unsupported queued or running 1.3.0 Dream tasks, preserve the historical embedding configuration, and follow the documented recovery procedure after interruption.', + ], + zh: [ + '通过 db-migrate status、plan、apply、verify 维护已登记的 Server 存储。以精确来源结构、已审阅计划、维护锁和明确备份策略约束结构与数据转换;校验不完整时不能报告完整 Server 就绪。', + '中断升级和幂等重试保留已登记来源历史与原始恢复证据。启动前检查权威记录、搜索投影、向量配置和必留历史,再开放服务。', + '迁移步骤显示为 v1_4_1_001 至 v1_4_1_003,保留原有 revision ID 和校验和。1.3.0、1.4.0 来源桥接独立登记,不能据此推导所有中间软件版本均通过验收。', + '发布门禁验证真实 1.3.0 到目标产物的十二个组合,覆盖 SQLite、嵌入式 seekDB、OceanBase 及向量、Code 开关。1.4.0 桥接不纳入该 CI 升级矩阵。失败、跳过或证据缺失均阻止发布;验收完成前保留 upgrade-required 策略。', + '维护前停止写入并备份已有数据库,排空不兼容的 1.3.0 queued/running Dream 任务,保持历史向量配置;发生中断时遵循文档中的恢复流程。', + ], + }, + installCommand: 'uv tool install --force "powercontext[cli,server]==1.4.1"', + githubUrl: 'https://github.com/oceanbase/powercontext/releases/tag/powercontext-v1.4.1', + }, { version: 'v1.4.0', date: '2026-10-19',