diff --git a/.github/workflows/desktop-candidate.yml b/.github/workflows/desktop-candidate.yml
new file mode 100644
index 0000000..eb51930
--- /dev/null
+++ b/.github/workflows/desktop-candidate.yml
@@ -0,0 +1,119 @@
+name: Rieke OS desktop candidate
+on:
+ push:
+ tags: ['v*']
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: Exact reviewed version tag in Rieke-OS
+ required: true
+ signed:
+ description: Require Developer ID signing and notarization
+ type: boolean
+ default: false
+permissions:
+ contents: read
+concurrency:
+ group: rieke-desktop-candidate-${{ inputs.tag || github.ref_name }}
+ cancel-in-progress: false
+jobs:
+ build:
+ if: github.repository == 'maxwellsdm1867/Rieke-OS'
+ runs-on: macos-15
+ environment: ${{ (github.event_name == 'push' || inputs.signed) && 'desktop-signing' || 'desktop-build' }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.tag || github.ref }}
+ fetch-depth: 0
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '22.12.0'
+ - uses: astral-sh/setup-uv@v6
+ - name: Verify clean canonical source and coordinated versions
+ env:
+ RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
+ RELEASE_REPOSITORY: ${{ github.repository }}
+ run: |
+ test "$(uname -m)" = arm64
+ python3 tools/desktop_release.py baseline --tag "$RELEASE_TAG" --repository "$RELEASE_REPOSITORY" --output "$RUNNER_TEMP/desktop-baseline.json"
+ - run: npm ci && npm test && npm run build
+ working-directory: workspace-app
+ - run: npm ci && npm test
+ working-directory: desktop
+ - name: Assemble the complete portable runtime
+ run: python3 tools/desktop_build_runtime.py --skip-frontend
+ - name: Verify backend and runtime contracts
+ run: |
+ desktop/build/runtime/python/bin/python3.11 -m unittest discover -s python/tests -p 'test_*desktop*.py' -v
+ desktop/build/runtime/python/bin/python3.11 -m unittest discover -s python/tests -p test_workspace_bootstrap.py -v
+ python3 tools/desktop_runtime_manifest.py
+ env:
+ PYTHONPATH: python
+ PYTHONDONTWRITEBYTECODE: '1'
+ - name: Build unsigned local qualification candidate
+ if: github.event_name != 'push' && !inputs.signed
+ working-directory: desktop
+ env:
+ CSC_IDENTITY_AUTO_DISCOVERY: 'false'
+ run: npm run dist
+ - name: Build signed and notarized candidate
+ if: github.event_name == 'push' || inputs.signed
+ working-directory: desktop
+ env:
+ CSC_LINK: ${{ secrets.CSC_LINK }}
+ CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
+ APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
+ APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
+ APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
+ CSC_IDENTITY_AUTO_DISCOVERY: 'false'
+ run: |
+ node -e "if(require('./distribution.json').channel!=='signed')throw new Error('Commit the reviewed signed distribution policy before building a signed release')"
+ test -n "$CSC_LINK"
+ test -n "$CSC_KEY_PASSWORD"
+ test -n "$APPLE_API_KEY"
+ test -n "$APPLE_API_KEY_ID"
+ test -n "$APPLE_API_ISSUER"
+ npm run dist
+ codesign --verify --deep --strict 'dist/mac-arm64/Rieke OS.app'
+ spctl --assess --type execute 'dist/mac-arm64/Rieke OS.app'
+ xcrun stapler validate 'dist/mac-arm64/Rieke OS.app'
+ - name: Exercise updater transport failures without installing
+ run: npm run test:e2e:updater --prefix desktop
+ - name: Exercise the actual packaged desktop UI and orderly lifecycle
+ run: npm run test:e2e --prefix desktop
+ - name: Exercise explicit unsigned Install and Open
+ if: github.event_name != 'push' && !inputs.signed
+ run: npm run test:e2e:bootstrap --prefix desktop
+ - name: Verify recovery before opening projects when backend startup fails
+ run: npm run test:e2e:startup-failure --prefix desktop
+ - name: Audit extracted installers and native database workflows
+ run: python3 tools/desktop_artifact_e2e.py --installed 'desktop/dist/mac-arm64/Rieke OS.app' --output docs/dev/desktop-artifact-e2e.json
+ - name: Inventory exact candidate bytes
+ run: |
+ python3 tools/desktop_release.py inventory --output desktop/dist/artifacts.json
+ cp "$RUNNER_TEMP/desktop-baseline.json" desktop/dist/desktop-baseline.json
+ cp desktop/build/runtime/runtime-audit.json desktop/dist/runtime-audit.json
+ - uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: desktop-e2e-evidence
+ path: |
+ docs/dev/desktop-updater-client-e2e.json
+ docs/dev/desktop-ui-e2e/
+ docs/dev/desktop-artifact-e2e.json
+ if-no-files-found: warn
+ retention-days: 30
+ - uses: actions/upload-artifact@v4
+ with:
+ name: rieke-desktop-macos-arm64
+ path: |
+ desktop/dist/*.dmg
+ desktop/dist/*.zip
+ desktop/dist/*.blockmap
+ desktop/dist/latest-mac.yml
+ desktop/dist/artifacts.json
+ desktop/dist/desktop-baseline.json
+ desktop/dist/runtime-audit.json
+ if-no-files-found: error
+ retention-days: 30
diff --git a/.github/workflows/desktop-promote.yml b/.github/workflows/desktop-promote.yml
new file mode 100644
index 0000000..c53b72f
--- /dev/null
+++ b/.github/workflows/desktop-promote.yml
@@ -0,0 +1,69 @@
+name: Promote qualified Rieke OS desktop
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: Reviewed stable tag
+ required: true
+ candidate_run_id:
+ description: Successful desktop-candidate run containing the exact qualified artifacts
+ required: true
+ evidence_path:
+ description: Reviewed qualification JSON committed to the default branch
+ required: true
+permissions:
+ contents: read
+ actions: read
+concurrency:
+ # All stable versions share a lock; an older build cannot race a newer promotion.
+ group: rieke-desktop-stable-promotion
+ cancel-in-progress: false
+jobs:
+ promote:
+ if: github.repository == 'maxwellsdm1867/Rieke-OS'
+ runs-on: macos-15
+ # Repository owners must enable required reviewers here. YAML alone does not.
+ environment: desktop-publishing
+ permissions:
+ contents: write
+ actions: read
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '22.12.0'
+ - run: npm ci --ignore-scripts
+ working-directory: desktop
+ - name: Require a successful canonical candidate run
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ CANDIDATE_RUN: ${{ inputs.candidate_run_id }}
+ run: |
+ python3 - <<'PY'
+ import json, os, re, subprocess
+ run_id = os.environ['CANDIDATE_RUN']
+ if not re.fullmatch(r'\d+', run_id): raise SystemExit('Invalid candidate run ID')
+ receipt = json.loads(subprocess.check_output(['gh','api',f'repos/maxwellsdm1867/Rieke-OS/actions/runs/{run_id}'],text=True))
+ if receipt['conclusion'] != 'success' or receipt['path'] != '.github/workflows/desktop-candidate.yml':
+ raise SystemExit('Candidate must be a successful desktop build')
+ PY
+ - uses: actions/download-artifact@v4
+ with:
+ name: rieke-desktop-macos-arm64
+ run-id: ${{ inputs.candidate_run_id }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ path: desktop/dist
+ - name: Verify reviewed evidence, recover draft uploads, and promote exact bytes
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ RELEASE_TAG: ${{ inputs.tag }}
+ EVIDENCE_PATH: ${{ inputs.evidence_path }}
+ run: |
+ python3 - <<'PY'
+ import os, pathlib, subprocess
+ root = pathlib.Path.cwd().resolve()
+ evidence = (root / os.environ['EVIDENCE_PATH']).resolve(strict=True)
+ if not evidence.is_relative_to(root / 'release-evidence') or evidence.suffix != '.json':
+ raise SystemExit('Evidence must be a reviewed release-evidence JSON in this checkout')
+ subprocess.run(['python3','tools/desktop_release.py','promote','--tag',os.environ['RELEASE_TAG'],'--evidence',str(evidence)],check=True)
+ PY
diff --git a/.github/workflows/workspace-release-candidate.yml b/.github/workflows/workspace-release-candidate.yml
index 1bda0ea..f9d8f9d 100644
--- a/.github/workflows/workspace-release-candidate.yml
+++ b/.github/workflows/workspace-release-candidate.yml
@@ -1,12 +1,19 @@
-name: Build signed Rieke OS release candidate
+name: Legacy source-release candidate (manual)
on:
workflow_dispatch:
inputs:
tag:
description: Reviewed stable version tag, for example v0.1.0
required: true
+ publish:
+ description: Publish the validated candidate as the latest stable GitHub Release
+ type: boolean
+ default: false
permissions:
contents: read
+concurrency:
+ group: rieke-release-${{ inputs.tag || github.ref_name }}
+ cancel-in-progress: false
jobs:
candidate:
# First advertised platform only. Linux needs its own native validation.
@@ -15,7 +22,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
- ref: ${{ inputs.tag }}
+ ref: ${{ inputs.tag || github.ref }}
fetch-depth: 0
- uses: actions/setup-node@v4
with:
@@ -58,5 +65,26 @@ jobs:
name: signed-rieke-release-candidate
path: ${{ runner.temp }}/rieke-candidate/
if-no-files-found: error
- # Deliberately no public release publication. Promotion requires the real
- # database, transfer, old-to-new upgrade and licensed MATLAB gates in docs.
+ publish:
+ needs: candidate
+ if: github.repository == 'maxwellsdm1867/Rieke-OS' && (github.event_name == 'push' || inputs.publish)
+ runs-on: ubuntu-latest
+ # Configure required reviewers here to attest the real upgrade/recovery and
+ # worker-shutdown evidence in docs/dev/releasing-rieke-os.md before promotion.
+ environment: release-publishing
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ name: signed-rieke-release-candidate
+ path: ${{ runner.temp }}/rieke-candidate
+ - name: Publish the exact signed candidate
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
+ CANDIDATE_DIRECTORY: ${{ runner.temp }}/rieke-candidate
+ run: |
+ gh release create "$RELEASE_TAG" "$CANDIDATE_DIRECTORY"/* \
+ --verify-tag --latest=false --generate-notes --title "Rieke OS source $RELEASE_TAG"
diff --git a/.gitignore b/.gitignore
index 562c5ea..45274f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -69,3 +69,9 @@ docs/legacy/old_epochtree/
# Clone-local Rieke OS dependencies/config (never scientific project data)
/.rieke-runtime/
+
+# Desktop build closure and local packages (source/locks remain tracked)
+/desktop/node_modules/
+/desktop/build/
+/desktop/dist/
+/desktop/resources/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58e5826..2123aec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Rieke OS [0.1.3] - 2026-09-30
+
+This desktop candidate uses the explicit unsigned testing channel. It is separate
+from the MATLAB library version below and from signed production distribution.
+
+### Added
+- Self-contained Apple Silicon desktop packaging with private Python and MySQL runtimes, folder selection, controlled installation, and testing-channel updates.
+- Persistent native tag membership and autocomplete dictionaries, with Unicode prefix indexes, maintained usage counts, and exact author attribution.
+- Transactional annotation history, incremental current-state recovery, and verified index migration and reuse.
+
+### Improved
+- Small annotation batches acknowledge durable saves promptly; cell tags inherit by cell UUID without copying annotations into every epoch.
+- Large-project browsing, selection, metadata storage, cache validation, and protocol-scoped curation preserve exact scientific identities.
+- Portable project handling, folder-boundary checks, draft preservation, and desktop service lifecycle validation.
+
+At 100,000 synthetic epochs, tag autocomplete measured 21–26 ms. Initial native
+index preparation took about two minutes; the paired two-save/filter API sequence
+measured 192 ms. See the [reproducible benchmark report](docs/dev/scale-audit-2026-09-29/native-tag-sequence-results.md)
+for scope, first-use measurements, and limitations.
+
## [1.0.0] - 2026-02-28
### Added
diff --git a/CONTEXT.md b/CONTEXT.md
index 8bb0866..db82380 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -28,6 +28,34 @@ _Avoid_: Matching cells solely by a repeated label such as Cell2
Recording date plus original cell name/number, with a separate cell-type tag; include a session qualifier when labels collide. Stable cell UUIDs are available as details.
_Avoid_: Using a mutable cell-type classification as a database key
+**Recorded cell identity**:
+The identity of a cell in an acquisition, together with its recorded source and experimental ancestry. Matching dates, labels, classifications or measured values do not establish that two records identify the same cell.
+_Avoid_: Cell number as identity, identity inferred from matching metadata
+
+**Cell correspondence**:
+A reviewed scientific claim that distinct recorded cell identities refer to the same biological cell, supported by retained evidence and provenance.
+_Avoid_: Automatic merge, renaming a cell to resolve uncertain identity
+
+**Source revision**:
+One exact version of a recording's original contents. Moving or renaming an unchanged recording does not create a new source revision; changing its contents does.
+_Avoid_: Filename as identity
+
+**Identity conflict**:
+Evidence that a recorded identifier, its ancestry, or its source revision disagrees with another representation of the same acquisition. A conflict is distinct from two legitimate acquisitions sharing a display label.
+_Avoid_: Duplicate inferred from name, overwriting to reconcile
+
+**Shared tag**:
+A project annotation attached to an exact cell or epoch identity, with its author profile and provenance retained across protocol workspaces.
+_Avoid_: A dataset-specific inclusion decision, an acquisition identifier
+
+**Inherited cell tag**:
+A cell annotation visible on its epochs through their recorded cell UUID relationship. Inheritance does not copy or reassign the annotation to each epoch.
+_Avoid_: Inheritance by matching cell labels or dates
+
+**Dataset tag**:
+A curation tag scoped to an epoch within a particular protocol workspace. Multiple dataset tags and shared tags may coexist without changing acquisition identity.
+_Avoid_: Treating all tags as one unscoped set
+
**Recorded duration**:
The sum of recording durations for distinct epochs in an explicit scope and timebase, counting an epoch once across its streams and excluding gaps.
_Avoid_: Session elapsed time, stimulation time, summing the same epoch once per device
diff --git a/desktop/.gitignore b/desktop/.gitignore
new file mode 100644
index 0000000..1096628
--- /dev/null
+++ b/desktop/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+dist/
+resources/
+build-work/
+build/
diff --git a/desktop/README.md b/desktop/README.md
new file mode 100644
index 0000000..162c99a
--- /dev/null
+++ b/desktop/README.md
@@ -0,0 +1,166 @@
+# Rieke OS desktop
+
+This package contains the Electron shell for the existing scientific React app.
+The first implementation targets macOS Apple Silicon. Its complete resource
+closure currently requires a macOS 14.0 floor; support on that minimum still
+needs real-device qualification. Other platforms are not advertised. The optional
+debugpy PID attachment helper is excluded with provenance in the runtime receipt.
+
+Native signing applies the existing `allow-jit` entitlement only to the bundled
+Python executable. The signed scientific and JIT workflow remains unqualified
+without Developer ID credentials and independent final-artifact evidence.
+
+## Local build
+
+Building requires Node/npm, Python 3.11 or newer for build tools, uv, the macOS
+developer toolchain and network access. Running the assembled app uses only its
+bundled interpreter, parser, MySQL and frontend. It never provisions dependencies
+at launch.
+
+The desktop app requires no Docker CLI, Docker Desktop or Docker daemon. Each
+desktop project uses the native MySQL executable inside the app bundle, with its
+database files outside the bundle in the project folder. Opening, creating,
+importing, querying, exporting and reopening desktop projects work independently
+of Docker.
+
+An older project can still have its only database inside the previous source
+installation's Docker container. Reading that existing database is a one-time
+source migration option, not a desktop runtime requirement. Receive a portable
+transfer prepared by the source installation instead when Docker is unavailable
+on the destination. A completed desktop copy uses the bundled native database.
+
+```sh
+npm ci --prefix workspace-app
+npm ci --prefix desktop
+python3 tools/desktop_build_runtime.py
+npm test --prefix desktop
+python3 tools/desktop_runtime_manifest.py
+CSC_IDENTITY_AUTO_DISCOVERY=false npm run dist --prefix desktop
+```
+
+The artifacts are in `desktop/dist`, including the complete
+`mac-arm64/Rieke OS.app`, DMG, updater ZIP, blockmaps and `latest-mac.yml`.
+Build caches and artifacts are ignored by Git. Parser and native dependency pins,
+license inventory, source provenance and resource hashes are carried inside the
+runtime; recordings, scientific projects and credentials are excluded.
+
+For development against the assembled runtime, run `npm start --prefix desktop`.
+For local icon-based testing, place the complete unsigned app at
+`~/Applications/Rieke OS.app` without overwriting an existing installation.
+The complete app at that fixed location runs independently of the checkout.
+This build explicitly selects the `unsigned-testing` distribution. Its Install
+and Open action verifies the complete app and installs a user-owned copy while
+preserving macOS quarantine. Its GitHub checks show newer testing versions;
+downloads and restart/install require the user's action. Ordinary Quit never
+installs a testing update. See [the testing release workflow](../docs/dev/GITHUB_TESTING_RELEASE.md).
+
+The default signed distribution still requires Developer ID verification for
+installation and updates. Unsigned testing does not qualify that production path.
+
+## Packaged end-to-end tests
+
+After building, run the actual packaged UI and failure suites:
+
+```sh
+npm run test:e2e --prefix desktop
+npm run test:e2e:startup-failure --prefix desktop
+npm run test:e2e:updater --prefix desktop
+```
+
+The UI suite copies the complete packaged app into a fresh temporary HOME, uses
+an isolated Electron user-data directory and creates disposable native projects.
+It tests the real sandboxed React UI, durable saved views, project navigation,
+duplicate launches, renderer recovery and orderly shutdown. Only native folder
+chooser return values are stubbed. Set `RIEKE_E2E_H5` to a read-only source fixture
+to also test real import activity and native close deferral; the suite copies it
+before import, and CI runs without private recordings. The startup-failure suite
+changes and reseals only its disposable app copy to test failed backend readiness.
+These tests never open or close the user's installed app. If orderly shutdown
+fails, the test preserves its isolated processes for investigation.
+
+For an explicit no-Docker recording workflow check, run:
+
+```sh
+python3 tools/desktop_no_docker_smoke.py --recording /path/to/test-recording.h5
+```
+
+This uses the packaged runtime with a fresh HOME and restricted PATH. It denies
+container commands and non-MySQL Unix sockets before access, tests those denials
+with separate negative controls, and then exercises native project creation,
+recording import, tags, query, export, reopen and orderly shutdown. The source
+recording is copied for import; the host Docker daemon and installed app are
+untouched. This is process-level test instrumentation, not a kernel sandbox or
+clean-machine qualification.
+
+Receipts and screenshots go to `docs/dev/desktop-ui-e2e/`. The updater suite uses
+the pinned real client with local HTTP transport faults. These unsigned local
+tests do not qualify signed installation or signed old-to-new recovery.
+
+## Ownership and shutdown
+
+The Electron main process owns the private authenticated production Waitress
+backend and all project services. A private child-pipe binding receipt precedes
+the first authenticated HTTP request. The renderer is sandboxed, has no Node
+integration and uses narrow validated top-frame IPC. Main injects per-session
+headers only into verified owned origins; Python source update APIs are disabled
+for desktop services.
+
+Renderer drafts and outstanding preference writes must acknowledge persistence
+before backend drain. Imports, inbox work, transfers, streaming responses and
+other active writes defer closure. Every owned project/database service and the
+root backend must acknowledge actual exit. Timeouts and crashes do not authorize
+process termination or app replacement.
+
+An older failed launcher can retain its idle root after rejecting a legacy
+project. If recovery refuses Quit, preserve the installed bundle and service
+registry for an ownership review. A controlled shutdown must first verify the
+exact owned processes and confirm that no project service, database, import or
+other writer is active; app replacement continues to refuse a live prior root.
+Deleting registry files cannot establish that shutdown. The new launcher offers
+legacy migration into a separate verified copy while preserving the source
+project and its existing source application. This work's isolated tests leave
+the user's older running installation unchanged.
+
+## Signed releases
+
+The public provider is explicitly `maxwellsdm1867/Rieke-OS`. Application,
+frontend, and desktop metadata are coordinated at version 0.1.3. The current
+`unsigned-testing` policy uses the separate `desktop-test-v0.1.3` prerelease;
+it does not promote or replace the stable source release. Build from the exact
+clean canonical source commit and qualify the resulting bytes before publishing.
+`tools/desktop_release.py` separately rejects dirty, mismatched or foreign signed
+production baselines and incomplete promotion.
+
+The desktop candidate workflow builds exact tagged sources. Signing is performed
+on the native closure before resealing the runtime manifest and signing the outer
+bundle. Notarization follows. Configure protected `desktop-signing` secrets
+`CSC_LINK`, `CSC_KEY_PASSWORD`, `APPLE_API_KEY`, `APPLE_API_KEY_ID` and
+`APPLE_API_ISSUER`; no credentials are included in the app. Configure required
+reviewers for `desktop-publishing` in GitHub settings; YAML does not enable them.
+
+Promotion takes an existing successful candidate run and reviewed qualification
+JSON from `release-evidence/` on the default branch. Evidence uses format
+`rieke-desktop-qualification`, version 1, exact application/source/platform
+identity, the artifact inventory from `artifacts.json`, and an entry for every
+R01–R12 requirement. Each entry requires `passed: true` and `receipts` containing
+a description and SHA-256 of the real-artifact evidence. Unit-test counts do not
+substitute for qualification. Use confidential-source-free receipts.
+
+Promotion is serialized across versions. Existing release bytes cannot be
+overwritten on retry. A complete draft becomes public without changing latest;
+all public asset hashes are checked without authentication before stable/latest
+is changed. An unsigned candidate must never be supplied as qualified evidence.
+The legacy source-release workflow is manual and does not promote itself latest.
+
+Updates download quietly, are checked about hourly with jitter and require exact
+signature, version, platform, resource and data-format validation. A signed prior
+app is retained before installation. Ready means those candidate checks passed;
+native staging and startup health are separate. Only the coordinator invokes the
+native installer after draft/drain acknowledgements. Recovery verifies the
+retained signer and data contract and uses the same controlled installation code.
+
+The production specification remains the acceptance authority:
+[electron-production-spec.md](../docs/design/electron-production-spec.md).
+Signing, notarization, clean-machine installation, source-user migration, full
+fault-injection qualification and a real signed old-to-new update remain gates
+until independently demonstrated with the final artifacts.
diff --git a/desktop/bootstrap.cjs b/desktop/bootstrap.cjs
new file mode 100644
index 0000000..bc8af7a
--- /dev/null
+++ b/desktop/bootstrap.cjs
@@ -0,0 +1,174 @@
+'use strict';
+const fs = require('./physical-fs.cjs').promises;
+const path = require('node:path');
+const os = require('node:os');
+const {execFile} = require('node:child_process');
+const {promisify} = require('node:util');
+const {randomUUID, createHash} = require('node:crypto');
+const runFile = promisify(execFile);
+const {verifyResources, compareVersions, stableVersion, compatibleMacMinimum} = require('./updater-validation.cjs');
+const APP_ID = 'org.riekeos.desktop';
+function enclosingApp(executable) {
+ const marker = `${path.sep}Contents${path.sep}MacOS${path.sep}`;
+ const index = executable.lastIndexOf(marker);
+ return index >= 0 ? executable.slice(0, index) : null;
+}
+async function signatureIdentity(bundle, run = runFile) {
+ await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', bundle]);
+ const details = await run('/usr/bin/codesign', ['--display', '--verbose=4', bundle]);
+ const text = `${details.stdout}\n${details.stderr}`;
+ const identifier = /^Identifier=(.+)$/m.exec(text)?.[1];
+ const team = /^TeamIdentifier=(.+)$/m.exec(text)?.[1];
+ if (identifier !== APP_ID || !team || team === 'not set' || !/^Authority=Developer ID Application:/m.test(text))
+ throw new Error('Install and Open requires the verified Developer ID signed Rieke OS app');
+ return {identifier, team};
+}
+async function assertNotRunning(bundle, run = runFile, ignorePid = null) {
+ const {stdout} = await run('/bin/ps', ['-axo', 'pid=,comm=']);
+ const prefix = `${bundle}${path.sep}`;
+ if (stdout.split('\n').some(line => {
+ const match = /^\s*(\d+)\s+(.+)$/.exec(line);
+ return match && Number(match[1]) !== ignorePid && match[2].startsWith(prefix);
+ }))
+ throw new Error('The installed Rieke OS app is running. Quit it before installation.');
+}
+async function readBundleManifest(bundle) {
+ return JSON.parse(await fs.readFile(path.join(bundle, 'Contents', 'Resources', 'runtime', 'runtime-manifest.json'), 'utf8'));
+}
+async function quarantineAttribute(bundle, run) {
+ try {
+ const result = await run('/usr/bin/xattr', ['-px', 'com.apple.quarantine', bundle]);
+ return result.stdout.replace(/\s/g, '').toLowerCase();
+ } catch (error) {
+ if (error.code === 1 && /No such xattr/i.test(error.stderr || '')) return null;
+ throw error;
+ }
+}
+async function bundleDigest(bundle) {
+ const info = await fs.lstat(bundle);
+ if (!info.isDirectory() || info.isSymbolicLink()) throw new Error('A regular complete application bundle is required');
+ const root = await fs.realpath(bundle), records = [];
+ let bytes = 0;
+ async function visit(directory) {
+ for (const name of (await fs.readdir(directory)).sort()) {
+ const file = path.join(directory, name), stat = await fs.lstat(file);
+ const relative = path.relative(root, file).split(path.sep).join('/');
+ if (records.length >= 120000) throw new Error('Application bundle exceeds inventory bounds');
+ const record = {path: relative, mode: stat.mode & 0o777};
+ if (stat.isSymbolicLink()) {
+ const target = await fs.readlink(file), physical = await fs.realpath(file);
+ if (path.isAbsolute(target) || (!physical.startsWith(root + path.sep) && physical !== root)) throw new Error('Application symlink escapes its bundle');
+ records.push({...record, type: 'symlink', target});
+ } else if (stat.isDirectory()) {
+ records.push({...record, type: 'directory'}); await visit(file);
+ } else if (stat.isFile()) {
+ bytes += stat.size;
+ if (bytes > 8 * 1024 ** 3) throw new Error('Application bundle exceeds size bounds');
+ const digest = createHash('sha256'), handle = await fs.open(file, 'r');
+ try { for await (const chunk of handle.createReadStream()) digest.update(chunk); } finally { await handle.close(); }
+ records.push({...record, type: 'file', size: stat.size, sha256: digest.digest('hex')});
+ } else throw new Error('Application contains a special file');
+ }
+ }
+ await visit(root);
+ return createHash('sha256').update(JSON.stringify(records)).digest('hex');
+}
+function testingDistribution(distribution) {
+ if (distribution === undefined || distribution === null || distribution.channel === 'signed') return false;
+ if (distribution.channel !== 'unsigned-testing') throw new Error('Unsupported application distribution policy');
+ return true;
+}
+async function bundleIdentity(bundle, distribution, run, {verifyTestingSeal = true} = {}) {
+ if (!testingDistribution(distribution)) return signatureIdentity(bundle, run);
+ if (verifyTestingSeal) await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', bundle]);
+ const plist = path.join(bundle, 'Contents', 'Info.plist');
+ const declared = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleIdentifier', plist]);
+ if (declared.stdout.trim() !== APP_ID) throw new Error('Unsigned testing app identifier differs');
+ const executable = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleExecutable', plist]);
+ if (executable.stdout.trim() !== 'Rieke OS' || !(await fs.stat(path.join(bundle, 'Contents', 'MacOS', 'Rieke OS'))).isFile()) throw new Error('Unsigned testing app executable is missing');
+ return {identifier: APP_ID, team: null, channel: 'unsigned-testing'};
+}
+async function verifyTestingBundle(bundle, run = runFile) {
+ await bundleIdentity(bundle, {channel: 'unsigned-testing'}, run);
+ const manifest = await readBundleManifest(bundle);
+ const required = ['python/bin/python3.11', 'mysql/bin/mysqld', 'mysql/bin/mysql', 'mysql/bin/mysqldump', 'application/python/workspace_desktop.py'];
+ if (!compatibleManifest(manifest, manifest) || !/^[a-f0-9]{40}$/.test(manifest.source_commit || '') || !/^[a-f0-9]{40}$/.test(manifest.parser_commit || '') ||
+ !/^3\.11\.\d+$/.test(manifest.python_version || '') || !/^8\.4\.\d+$/.test(manifest.mysql_version || '') || !Array.isArray(manifest.workspace_formats) || !manifest.workspace_formats.length ||
+ !Number.isInteger(manifest.database_compatibility) || !required.every(name => manifest.resources?.[name])) throw new Error('Unsigned testing runtime closure is incomplete');
+ stableVersion(manifest.application_version);
+ const plist = path.join(bundle, 'Contents', 'Info.plist');
+ const version = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleShortVersionString', plist]);
+ const minimum = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :LSMinimumSystemVersion', plist]);
+ if (version.stdout.trim() !== manifest.application_version || minimum.stdout.trim() !== manifest.minimum_macos_version) throw new Error('Unsigned testing app and runtime versions differ');
+ const host = await run('/usr/bin/sw_vers', ['-productVersion']);
+ compatibleMacMinimum(manifest.minimum_macos_version, host.stdout.trim());
+ return manifest;
+}
+function compatibleManifest(candidate, current) {
+ return candidate.format === 'rieke-desktop-runtime' && candidate.version === 1 && candidate.platform === 'darwin' && candidate.architecture === 'arm64' &&
+ candidate.mysql_version === current.mysql_version &&
+ candidate.database_compatibility === current.database_compatibility &&
+ JSON.stringify(candidate.workspace_formats) === JSON.stringify(current.workspace_formats);
+}
+async function installCompleteBundle({source, destination = path.join(os.homedir(), 'Applications', 'Rieke OS.app'), run = runFile, allowRollback = false, ignorePid = null, distribution, expectedBundleSha256}) {
+ const unsignedTesting = testingDistribution(distribution);
+ if (ignorePid !== null && (!allowRollback || ignorePid !== process.pid)) throw new Error('Only the dedicated current recovery helper may be exempted from running-app checks');
+ if (!source || !source.endsWith('.app')) throw new Error('A complete app bundle is required');
+ const parent = path.dirname(destination);
+ await fs.mkdir(parent, {recursive: true, mode: 0o700});
+ const info = await fs.lstat(parent);
+ if (info.isSymbolicLink() || info.uid !== process.getuid()) throw new Error('Installation folder must be owned by the current user');
+ const sourceDigest = unsignedTesting ? await bundleDigest(source) : null;
+ const quarantine = unsignedTesting ? await quarantineAttribute(source, run) : null;
+ if (expectedBundleSha256 !== undefined && sourceDigest !== expectedBundleSha256) throw new Error('Downloaded application bundle checksum differs');
+ const sourceIdentity = await bundleIdentity(source, distribution, run);
+ const sourceManifest = await readBundleManifest(source);
+ if (unsignedTesting) await verifyTestingBundle(source, run);
+ stableVersion(sourceManifest.application_version);
+ if (!compatibleManifest(sourceManifest, sourceManifest) || !sourceManifest.source_commit || !sourceManifest.resources ||
+ !Array.isArray(sourceManifest.workspace_formats) || !Number.isInteger(sourceManifest.database_compatibility))
+ throw new Error('Downloaded app runtime manifest is invalid');
+ const sourcePlist = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleShortVersionString', path.join(source, 'Contents', 'Info.plist')]);
+ if (sourcePlist.stdout.trim() !== sourceManifest.application_version) throw new Error('App and runtime versions differ');
+ await verifyResources(path.join(source, 'Contents', 'Resources', 'runtime'), sourceManifest.resources);
+ const lock = path.join(parent, '.rieke-os-install.lock');
+ await fs.mkdir(lock, {mode: 0o700});
+ const staging = path.join(parent, `.Rieke OS.install-${randomUUID()}.app`);
+ const previous = path.join(parent, '.Rieke OS.previous.app');
+ let movedPrevious = false;
+ try {
+ let existing = false;
+ try {
+ const targetInfo = await fs.lstat(destination);
+ if (targetInfo.isSymbolicLink() || targetInfo.uid !== process.getuid()) throw new Error('Installed app is not a user-owned bundle');
+ const installedIdentity = await bundleIdentity(destination, distribution, run, {verifyTestingSeal: false});
+ if (installedIdentity.team !== sourceIdentity.team) throw new Error('Installed app has a different signing identity');
+ const installedManifest = await readBundleManifest(destination);
+ if (!compatibleManifest(sourceManifest, installedManifest)) throw new Error('Installed app uses incompatible workspace or database formats');
+ if (!allowRollback && compareVersions(sourceManifest.application_version, installedManifest.application_version) < 0) throw new Error('Install and Open cannot downgrade the installed app');
+ await verifyResources(path.join(destination, 'Contents', 'Resources', 'runtime'), installedManifest.resources);
+ await assertNotRunning(destination, run, ignorePid); existing = true;
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
+ await run('/usr/bin/ditto', ['--rsrc', '--extattr', '--acl', source, staging]);
+ const copiedIdentity = await bundleIdentity(staging, distribution, run);
+ if (copiedIdentity.team !== sourceIdentity.team) throw new Error('Copied app signature differs from downloaded app');
+ await verifyResources(path.join(staging, 'Contents', 'Resources', 'runtime'), sourceManifest.resources);
+ if (unsignedTesting && await bundleDigest(staging) !== sourceDigest) throw new Error('Copied application bundle checksum differs');
+ if (unsignedTesting && await quarantineAttribute(staging, run) !== quarantine) throw new Error('Copied application quarantine attribute differs');
+ if (!unsignedTesting) await run('/usr/sbin/spctl', ['--assess', '--type', 'execute', staging]);
+ if (existing) {
+ await assertNotRunning(destination, run, ignorePid);
+ // Retain exactly one verified previous signed bundle, only after the new copy is complete.
+ await fs.rm(previous, {recursive: true, force: true});
+ await fs.rename(destination, previous); movedPrevious = true;
+ }
+ try { await fs.rename(staging, destination); }
+ catch (error) { if (movedPrevious) await fs.rename(previous, destination); throw error; }
+ await bundleIdentity(destination, distribution, run);
+ return {destination, previous: movedPrevious ? previous : null};
+ } finally {
+ await fs.rm(staging, {recursive: true, force: true});
+ await fs.rm(lock, {recursive: true, force: true});
+ }
+}
+module.exports = {APP_ID, enclosingApp, signatureIdentity, assertNotRunning, compatibleManifest, installCompleteBundle, readBundleManifest, bundleDigest, verifyTestingBundle};
diff --git a/desktop/distribution.cjs b/desktop/distribution.cjs
new file mode 100644
index 0000000..e15f340
--- /dev/null
+++ b/desktop/distribution.cjs
@@ -0,0 +1,10 @@
+'use strict';
+const REPOSITORY = 'maxwellsdm1867/Rieke-OS';
+function distributionPolicy(value) {
+ if (value === undefined) return Object.freeze({channel:'signed',repository:REPOSITORY});
+ if (value?.format !== 'rieke-desktop-distribution' || value.version !== 1 ||
+ !['signed','unsigned-testing'].includes(value.channel) || value.repository !== REPOSITORY)
+ throw new Error('The desktop distribution policy is invalid.');
+ return Object.freeze({...value});
+}
+module.exports={distributionPolicy};
diff --git a/desktop/distribution.json b/desktop/distribution.json
new file mode 100644
index 0000000..a893a0b
--- /dev/null
+++ b/desktop/distribution.json
@@ -0,0 +1,6 @@
+{
+ "format": "rieke-desktop-distribution",
+ "version": 1,
+ "channel": "unsigned-testing",
+ "repository": "maxwellsdm1867/Rieke-OS"
+}
diff --git a/desktop/draft-barrier.cjs b/desktop/draft-barrier.cjs
new file mode 100644
index 0000000..096b198
--- /dev/null
+++ b/desktop/draft-barrier.cjs
@@ -0,0 +1,26 @@
+'use strict';
+const {randomUUID} = require('node:crypto');
+class DraftBarrier {
+ constructor({timeout = 10000, send = (window, value) => window.webContents.send('desktop:prepare-close', value)} = {}) {
+ this.timeout = timeout; this.send = send; this.pending = new Map();
+ }
+ acknowledge(payload, window) {
+ if (!payload || typeof payload.requestId !== 'string' || typeof payload.ok !== 'boolean' || Object.keys(payload).sort().join(',') !== 'ok,requestId') throw new TypeError('Invalid draft acknowledgement');
+ const pending = this.pending.get(payload.requestId);
+ if (!pending || pending.window !== window) throw new Error('No matching draft request');
+ pending.resolve(payload.ok); return {acknowledged: true};
+ }
+ async prepare(windows) {
+ if ([...windows].some(window => window.isDestroyed() || window.draftUnavailable))
+ return {ready: false, reason: 'Renderer drafts have not been acknowledged. Restore the scientific window before quitting.'};
+ const results = await Promise.all([...windows].map(window => new Promise(resolve => {
+ const requestId = randomUUID();
+ const finish = ok => { clearTimeout(timer); this.pending.delete(requestId); resolve(ok); };
+ const timer = setTimeout(() => finish(false), this.timeout);
+ this.pending.set(requestId, {window, resolve: finish});
+ try { this.send(window, {requestId}); } catch { finish(false); }
+ })));
+ return results.every(Boolean) ? {ready: true} : {ready: false, reason: 'Draft persistence was not acknowledged; quit and replacement are deferred'};
+ }
+}
+module.exports = {DraftBarrier};
diff --git a/desktop/draft-store.cjs b/desktop/draft-store.cjs
new file mode 100644
index 0000000..b992331
--- /dev/null
+++ b/desktop/draft-store.cjs
@@ -0,0 +1,55 @@
+'use strict';
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const {randomUUID} = require('node:crypto');
+const {validateDraft, validateProjectId} = require('./security.cjs');
+const MAX_DRAFT_BYTES = 2 * 1024 * 1024;
+function validStoredDraft(value, projectId) {
+ return value !== null && typeof value === 'object' && !Array.isArray(value) &&
+ value.format === 'rieke-renderer-draft' && value.version === 1 && value.projectId === projectId &&
+ value.value !== null && typeof value.value === 'object' && !Array.isArray(value.value);
+}
+class DraftStore {
+ constructor(userData) { this.userData = userData; this.directory = path.join(userData, 'drafts'); this.pending = new Set(); }
+ async location(projectId) {
+ const id = validateProjectId(projectId);
+ await fs.mkdir(this.directory, {recursive: true, mode: 0o700});
+ const stat = await fs.lstat(this.directory);
+ if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== process.getuid()) throw new Error('Draft state directory is redirected or not privately owned');
+ return {id, filename: path.join(this.directory, `${id}.json`)};
+ }
+ recovery(id) { this.pending.add(id); return {format: 'rieke-draft-recovery', version: 1, projectId: id, reason: 'saved-draft-unreadable'}; }
+ async load(projectId) {
+ const id = validateProjectId(projectId);
+ if (this.pending.has(id)) return this.recovery(id);
+ let filename;
+ try { ({filename} = await this.location(id)); }
+ catch { return this.recovery(id); }
+ try {
+ const stat = await fs.lstat(filename);
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_DRAFT_BYTES || stat.uid !== process.getuid()) return this.recovery(id);
+ const value = JSON.parse(await fs.readFile(filename, 'utf8'));
+ return validStoredDraft(value, id) ? value : this.recovery(id);
+ } catch (error) { return error.code === 'ENOENT' ? null : this.recovery(id); }
+ }
+ async save(payload) {
+ const {projectId, serialized} = validateDraft(payload);
+ if (!validStoredDraft(payload.value, projectId)) throw new TypeError('Invalid renderer draft identity or schema');
+ await this.load(projectId);
+ if (this.pending.has(projectId)) throw new Error('Saved view needs an explicit recovery decision before it can be replaced');
+ const {filename} = await this.location(projectId);
+ const temporary = `${filename}.${randomUUID()}.tmp`;
+ try { await fs.writeFile(temporary, serialized, {mode: 0o600, flag: 'wx'}); await fs.rename(temporary, filename); }
+ finally { await fs.rm(temporary, {force: true}); }
+ return {saved: true};
+ }
+ async reset(projectId) {
+ const id = validateProjectId(projectId);
+ if (!this.pending.has(id)) throw new Error('No verified unreadable draft is pending recovery');
+ const {filename} = await this.location(id);
+ try { await fs.rename(filename, path.join(this.directory, `${id}.corrupt-${randomUUID()}.json`)); }
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
+ this.pending.delete(id); return {reset: true};
+ }
+}
+module.exports = {DraftStore, validStoredDraft, MAX_DRAFT_BYTES};
diff --git a/desktop/e2e/backend-failure.e2e.cjs b/desktop/e2e/backend-failure.e2e.cjs
new file mode 100644
index 0000000..d3ed942
--- /dev/null
+++ b/desktop/e2e/backend-failure.e2e.cjs
@@ -0,0 +1,41 @@
+'use strict';
+const assert = require('node:assert/strict');
+const fs = require('node:fs/promises'), path = require('node:path');
+const {createHash} = require('node:crypto');
+const {createFixture, launch, run} = require('./helpers.cjs');
+const {resealRuntimeManifest} = require('../sign-runtime.cjs');
+const {verifyResources} = require('../updater-validation.cjs');
+(async () => {
+ const fixture = await createFixture({reuse: false});
+ const runtime = path.join(fixture.bundle, 'Contents/Resources/runtime');
+ const sourceManifestSha256 = createHash('sha256').update(await fs.readFile(path.join(runtime, 'runtime-manifest.json'))).digest('hex');
+ const appAsarSha256 = createHash('sha256').update(await fs.readFile(path.join(fixture.bundle, 'Contents/Resources/app.asar'))).digest('hex');
+ const entry = path.join(runtime, 'application/python/workspace_desktop.py');
+ // This faulted copy is disposable and never supplies signing/release evidence.
+ // The literal statement cannot import project code, bind services or start DB.
+ const literalFailure = "raise RuntimeError('Injected packaged backend failure before imports, binding, projects or databases')\n";
+ await fs.writeFile(entry, literalFailure);
+ const manifest = await resealRuntimeManifest(runtime); await verifyResources(runtime, manifest.resources);
+ const {application, page} = await launch(fixture);
+ await page.getByRole('heading', {name: 'Rieke OS recovery', exact: true}).waitFor({timeout: 90000});
+ const status = await page.evaluate(() => window.riekeDesktop.status());
+ assert.equal(status.state, 'Recovery'); assert.match(status.detail, /exited before owning its listener/);
+ assert.equal(await page.getByRole('heading', {name: 'Your projects', exact: true}).count(), 0);
+ const record = JSON.parse(await fs.readFile(path.join(fixture.userData, 'desktop-service.json'), 'utf8'));
+ assert.ok(record.executable.startsWith(fixture.bundle + '/'));
+ await assert.rejects(run('/bin/ps', ['-p', String(record.pid), '-o', 'comm=']));
+ assert.equal((await page.evaluate(() => window.riekeDesktop.quit())).ready, false);
+ assert.equal((await fs.readdir(path.join(fixture.userData, 'backend'))).length, 0);
+ const output = path.resolve(__dirname, '../../docs/dev/desktop-ui-e2e'); await fs.mkdir(output, {recursive: true});
+ await page.screenshot({path: path.join(output, 'backend-startup-recovery.png')});
+ const receipt = {format: 'rieke-packaged-backend-failure-e2e', version: 1, passed: true, signed_qualification: false,
+ method: 'Separate disposable packaged clone; literal raise-before-import backend; clone-only manifest reseal',
+ source_manifest_sha256: sourceManifestSha256, app_asar_sha256: appAsarSha256,
+ fault_code_sha256: createHash('sha256').update(literalFailure).digest('hex'), recovery_before_projects: true,
+ backend_exited: true, no_service_or_database_start: true, unsafe_quit_deferred: true,
+ teardown: 'Only this test GUI exits after the known literal failed backend has exited; no workers or databases existed'};
+ await fs.writeFile(path.join(output, 'backend-startup-failure.json'), JSON.stringify(receipt, null, 2) + '\n');
+ const exited = new Promise(resolve => application.process().once('exit', resolve));
+ await application.evaluate(({app}) => app.exit(0)); assert.equal(await exited, 0);
+ console.log(JSON.stringify(receipt));
+})().catch(error => {console.error(error.message); process.exitCode = 1;});
diff --git a/desktop/e2e/bootstrap.e2e.cjs b/desktop/e2e/bootstrap.e2e.cjs
new file mode 100644
index 0000000..0c30bd1
--- /dev/null
+++ b/desktop/e2e/bootstrap.e2e.cjs
@@ -0,0 +1,89 @@
+'use strict';
+// Real downloaded app UI, installer copy/seal/quarantine checks and installed
+// startup. Only Launch Services dispatch is stubbed in this disposable clone;
+// it must never target the user's running application or profile.
+const assert=require('node:assert/strict'),fs=require('node:fs/promises'),path=require('node:path');
+const {createHash}=require('node:crypto');
+const {_electron}=require('playwright');
+const asar=require('@electron/asar');
+const {createFixture,launch,gracefulQuit,run}=require('./helpers.cjs');
+const {verifyResources}=require('../updater-validation.cjs');
+const {bundleDigest}=require('../bootstrap.cjs');
+const output=path.resolve(__dirname,'../../docs/dev/desktop-ui-e2e/bootstrap-receipt.json');
+const receipt={format:'rieke-packaged-bootstrap-e2e',version:1,signed_qualification:false,checks:[],failures:[],method:'Real downloaded packaged clone with native install checks; only /usr/bin/open dispatch stubbed; isolated installed profile launched with Playwright'};
+let fixture,application,page;
+async function write(){await fs.mkdir(path.dirname(output),{recursive:true});await fs.writeFile(output,JSON.stringify(receipt,null,2)+'\n');}
+async function check(name,fn){try{const evidence=await fn();receipt.checks.push({name,passed:true,evidence});console.log('PASS '+name);}catch(error){receipt.failures.push({name,message:error.message});await write();throw error;}await write();}
+async function main(){
+ fixture=await createFixture({reuse:false});receipt.fixture_root=fixture.root;
+ const target=fixture.bundle,downloaded=path.join(fixture.home,'Downloads','Rieke OS.app');
+ const resources=path.join(target,'Contents/Resources'),sourceAsar=path.join(resources,'app.asar');
+ receipt.source_app_asar_sha256=createHash('sha256').update(await fs.readFile(sourceAsar)).digest('hex');
+ const manifestBytes=await fs.readFile(path.join(resources,'runtime/runtime-manifest.json')),manifest=JSON.parse(manifestBytes);
+ receipt.source_manifest_sha256=createHash('sha256').update(manifestBytes).digest('hex');receipt.application_version=manifest.application_version;
+ await fs.mkdir(path.dirname(downloaded),{recursive:true});await fs.rename(target,downloaded);
+ fixture.bundle=downloaded;fixture.executable=path.join(downloaded,'Contents/MacOS/Rieke OS');
+ const archive=path.join(downloaded,'Contents/Resources/app.asar'),unpacked=path.join(fixture.root,'bootstrap-test-asar'),handoff=path.join(fixture.home,'launch-services-handoff.json');
+ asar.extractAll(archive,unpacked);
+ const mainFile=path.join(unpacked,'main.cjs'),original=await fs.readFile(mainFile,'utf8');
+ const exact="const {execFile} = require('node:child_process');";
+ assert.ok(original.includes(exact),'Expected packaged main OS process boundary');
+ const wrapper=`const nativeExecFile = require('node:child_process').execFile;
+const execFile = (...args) => nativeExecFile(...args);
+execFile[require('node:util').promisify.custom] = async (file, args) => {
+ if (file === '/usr/bin/open') { require('node:fs').writeFileSync(${JSON.stringify(handoff)}, JSON.stringify({file,args,home:process.env.HOME,userData:app.getPath('userData')})+'\\n',{mode:0o600}); return {stdout:'',stderr:''}; }
+ return require('node:util').promisify(nativeExecFile)(file,args);
+};`;
+ await fs.writeFile(mainFile,original.replace(exact,wrapper));await asar.createPackage(unpacked,archive);asar.uncacheAll();
+ const headerHash=createHash('sha256').update(asar.getRawHeader(archive).headerString).digest('hex');
+ await run('/usr/libexec/PlistBuddy',['-c',`Set :ElectronAsarIntegrity:Resources/app.asar:hash ${headerHash}`,path.join(downloaded,'Contents/Info.plist')]);
+ await run('/usr/bin/codesign',['--force','--sign','-','--entitlements',path.resolve(__dirname,'../entitlements.mac.plist'),downloaded]);
+ await run('/usr/bin/codesign',['--verify','--deep','--strict',downloaded]);
+ const quarantine='0083;66000000;Rieke-E2E;00000000-0000-4000-8000-000000000001';
+ // Quarantine is assigned only AFTER this source test clone has launched.
+ // The quarantined installed copy is never executed or stripped of quarantine.
+ const before=await bundleDigest(downloaded);
+ await check('downloaded clone displays explicit unsigned Install and Open before any backend starts',async()=>{
+ application=await _electron.launch({executablePath:fixture.executable,args:[`--user-data-dir=${fixture.userData}`],env:{...process.env,HOME:fixture.home,TMPDIR:fixture.root,XDG_CONFIG_HOME:fixture.userData},chromiumSandbox:true,bypassCSP:false,timeout:45000});
+ const actual=await application.evaluate(({app})=>({home:process.env.HOME,userData:app.getPath('userData'),appPath:app.getAppPath(),packaged:app.isPackaged}));
+ assert.equal(actual.home,fixture.home);assert.equal(actual.userData,path.join(fixture.userData,'installer'));assert.ok(actual.appPath.startsWith(downloaded+'/'));assert.equal(actual.packaged,true);
+ page=await application.firstWindow();await page.getByRole('heading',{name:'Install Rieke OS',exact:true}).waitFor();
+ await page.getByText('Unsigned testing',{exact:true}).waitFor();assert.match(await page.locator('#detail').innerText(),/Open Anyway/);
+ await page.getByRole('button',{name:'Install and Open',exact:true}).waitFor();
+ assert.equal(await fs.stat(target).then(()=>true,()=>false),false);assert.equal(await fs.stat(handoff).then(()=>true,()=>false),false);
+ assert.equal(await fs.stat(path.join(fixture.userData,'desktop-service.json')).then(()=>true,()=>false),false);
+ await page.screenshot({path:path.join(path.dirname(output),'bootstrap-install-and-open.png')});
+ await run('/usr/bin/xattr',['-w','com.apple.quarantine',quarantine,downloaded]);return {bootstrap:true,no_automatic_install:true,no_backend:true,isolated_installer_profile:true};
+ });
+ await check('explicit Install and Open copies the complete verified bundle and preserves quarantine and installed profile',async()=>{
+ const exited=new Promise(resolve=>application.process().once('exit',resolve));await page.getByRole('button',{name:'Install and Open',exact:true}).click();
+ const code=await Promise.race([exited,new Promise((_r,reject)=>setTimeout(()=>reject(new Error('Installer failed to complete its native copy')),420000).unref())]);assert.equal(code,0);
+ const dispatch=JSON.parse(await fs.readFile(handoff,'utf8'));
+ assert.deepEqual(dispatch.args,['-n','-a',target,'--env',`HOME=${fixture.home}`,'--args',`--user-data-dir=${fixture.userData}`]);assert.equal(dispatch.file,'/usr/bin/open');assert.equal(dispatch.home,fixture.home);
+ assert.equal(await bundleDigest(target),before);assert.equal(await bundleDigest(downloaded),before);
+ assert.equal((await run('/usr/bin/xattr',['-p','com.apple.quarantine',target])).stdout.trim(),quarantine);
+ await verifyResources(path.join(target,'Contents/Resources/runtime'),manifest.resources);await run('/usr/bin/codesign',['--verify','--deep','--strict',target]);
+ return {real_complete_copy:true,copy_hash_identical:true,source_unchanged:true,quarantine_preserved:true,exact_isolated_launch_profile:true,os_dispatch_only_stubbed:true};
+ });
+ await check('installed copy cold starts into Your projects using the base isolated profile',async()=>{
+ // A completely separate never-quarantined fixture qualifies profile startup.
+ fixture=await createFixture({reuse:false});receipt.unquarantined_profile_fixture_root=fixture.root;
+ const cleanTarget=fixture.bundle,cleanDownload=path.join(fixture.home,'Downloads','Rieke OS.app'),cleanHandoff=path.join(fixture.home,'launch-services-handoff.json');
+ await fs.mkdir(path.dirname(cleanDownload),{recursive:true});await fs.rename(cleanTarget,cleanDownload);
+ const cleanArchive=path.join(cleanDownload,'Contents/Resources/app.asar'),cleanUnpacked=path.join(fixture.root,'profile-test-asar');asar.extractAll(cleanArchive,cleanUnpacked);
+ const cleanMain=path.join(cleanUnpacked,'main.cjs'),cleanOriginal=await fs.readFile(cleanMain,'utf8');assert.ok(cleanOriginal.includes(exact));
+ await fs.writeFile(cleanMain,cleanOriginal.replace(exact,wrapper.replace(JSON.stringify(handoff),JSON.stringify(cleanHandoff))));await asar.createPackage(cleanUnpacked,cleanArchive);asar.uncacheAll();
+ await run('/usr/libexec/PlistBuddy',['-c',`Set :ElectronAsarIntegrity:Resources/app.asar:hash ${createHash('sha256').update(asar.getRawHeader(cleanArchive).headerString).digest('hex')}`,path.join(cleanDownload,'Contents/Info.plist')]);
+ await run('/usr/bin/codesign',['--force','--sign','-','--entitlements',path.resolve(__dirname,'../entitlements.mac.plist'),cleanDownload]);await run('/usr/bin/codesign',['--verify','--deep','--strict',cleanDownload]);
+ application=await _electron.launch({executablePath:path.join(cleanDownload,'Contents/MacOS/Rieke OS'),args:[`--user-data-dir=${fixture.userData}`],env:{...process.env,HOME:fixture.home,TMPDIR:fixture.root,XDG_CONFIG_HOME:fixture.userData},chromiumSandbox:true,bypassCSP:false,timeout:45000});
+ page=await application.firstWindow();await page.getByRole('heading',{name:'Install Rieke OS',exact:true}).waitFor();
+ const exited=new Promise(resolve=>application.process().once('exit',resolve));await page.getByRole('button',{name:'Install and Open',exact:true}).click();assert.equal(await Promise.race([exited,new Promise((_r,reject)=>setTimeout(()=>reject(new Error('Clean installer did not finish')),420000).unref())]),0);
+ assert.deepEqual(JSON.parse(await fs.readFile(cleanHandoff,'utf8')).args,['-n','-a',cleanTarget,'--env',`HOME=${fixture.home}`,'--args',`--user-data-dir=${fixture.userData}`]);
+ assert.equal(await run('/usr/bin/xattr',['-p','com.apple.quarantine',cleanTarget]).then(()=>true,error=>{if(error.code===1)return false;throw error;}),false,'This startup fixture must never carry quarantine');
+ fixture.bundle=cleanTarget;fixture.executable=path.join(cleanTarget,'Contents/MacOS/Rieke OS');({application,page}=await launch(fixture));
+ await page.getByRole('heading',{name:'Your projects',exact:true}).waitFor({timeout:90000});assert.equal((await page.evaluate(()=>window.riekeDesktop.status())).channel,'unsigned-testing');
+ await gracefulQuit(application,page);await verifyResources(path.join(cleanTarget,'Contents/Resources/runtime'),manifest.resources);return {manual_macos_approval_unqualified:true,separate_never_quarantined_fixture:true,real_installed_startup:true,base_profile_restored:true,native_orderly_exit:true,runtime_immutable:true};
+ });
+ assert.equal(receipt.failures.length,0);await write();console.log('Bootstrap receipt: '+output);
+}
+main().catch(async error=>{console.error(error.stack);receipt.failures.push({name:'bootstrap-suite',message:error.message});await write();process.exitCode=1;});
diff --git a/desktop/e2e/github-updates.e2e.cjs b/desktop/e2e/github-updates.e2e.cjs
new file mode 100644
index 0000000..17e2102
--- /dev/null
+++ b/desktop/e2e/github-updates.e2e.cjs
@@ -0,0 +1,79 @@
+'use strict';
+// Only the official GitHub HTTPS transport is mapped to a loopback asset server
+// inside an isolated test clone. Descriptor, ZIP, candidate/runtime validation,
+// UI, draft/writer drain and ordinary Quit all run the packaged implementation.
+const assert=require('node:assert/strict'),fs=require('node:fs/promises'),nativeFs=require('node:fs'),path=require('node:path'),http=require('node:http');
+const {createHash}=require('node:crypto'),asar=require('@electron/asar');
+const {createFixture,launch,gracefulQuit,ownedControl,run}=require('./helpers.cjs');
+const {verifyResources}=require('../updater-validation.cjs');
+const output=path.resolve(__dirname,'../../docs/dev/desktop-ui-e2e/github-update-receipt.json');
+const receipt={format:'rieke-packaged-github-update-ui-e2e',version:1,signed_qualification:false,checks:[],failures:[],method:'Version-only Current clone, official HTTPS GitHub URLs mapped only at transport boundary to real final descriptor and ZIP; no candidate validation or drain mocks'};
+let fixture,application,page,server;const counts={api:0,descriptor:0,archive:0};
+const delay=ms=>new Promise(resolve=>setTimeout(resolve,ms));
+async function write(){await fs.mkdir(path.dirname(output),{recursive:true});await fs.writeFile(output,JSON.stringify({...receipt,requests:counts},null,2)+'\n');}
+async function check(name,fn){const start=Date.now();try{const evidence=await fn();receipt.checks.push({name,passed:true,elapsed_ms:Date.now()-start,evidence});console.log('PASS '+name);}catch(error){receipt.failures.push({name,message:error.message});await write();throw error;}await write();}
+async function main(){
+ fixture=await createFixture({reuse:false});receipt.fixture_root=fixture.root;
+ const published=path.resolve(__dirname,'../dist/mac-arm64/Rieke OS.app'),sourceManifestFile=path.join(published,'Contents/Resources/runtime/runtime-manifest.json'),sourceBytes=await fs.readFile(sourceManifestFile),sourceManifest=JSON.parse(sourceBytes);
+ receipt.source_manifest_sha256=createHash('sha256').update(sourceBytes).digest('hex');receipt.source_app_asar_sha256=createHash('sha256').update(await fs.readFile(path.join(published,'Contents/Resources/app.asar'))).digest('hex');
+ const candidateVersion=sourceManifest.application_version;assert.equal(candidateVersion,'0.1.3','Test scenario requires the final 0.1.3 package');
+ const zip=path.resolve(__dirname,`../dist/Rieke-OS-${candidateVersion}-arm64.zip`),descriptorPath=path.join(fixture.root,'desktop-release.json');
+ await run(path.join(fixture.bundle,'Contents/Resources/runtime/python/bin/python3.11'),['-B',path.resolve(__dirname,'../../tools/desktop_test_release.py'),'--app',published,'--archive',zip,'--output',descriptorPath],{env:{...process.env,PYTHONDONTWRITEBYTECODE:'1'}});
+ const descriptorBytes=await fs.readFile(descriptorPath),descriptor=JSON.parse(descriptorBytes);receipt.candidate_archive_sha256=descriptor.archive.sha256;
+ const tag=`desktop-test-v${candidateVersion}`,root=`https://github.com/maxwellsdm1867/Rieke-OS/releases/download/${tag}/`;
+ const releases=Buffer.from(JSON.stringify([{draft:false,prerelease:true,tag_name:tag,assets:[{name:'desktop-release.json',browser_download_url:root+'desktop-release.json',size:descriptorBytes.length},{name:descriptor.archive.filename,browser_download_url:root+descriptor.archive.filename,size:descriptor.archive.size}]}]));
+ server=http.createServer((request,response)=>{
+ if(request.url==='/api'){counts.api++;response.writeHead(200,{'content-length':releases.length});response.end(releases);}
+ else if(request.url==='/descriptor'){counts.descriptor++;response.writeHead(200,{'content-length':descriptorBytes.length});response.end(descriptorBytes);}
+ else if(request.url==='/archive'){counts.archive++;response.writeHead(200,{'content-length':descriptor.archive.size});nativeFs.createReadStream(zip).pipe(response);}
+ else{response.writeHead(404);response.end();}
+ });await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
+ const base=`http://127.0.0.1:${server.address().port}`,transport=path.join(fixture.root,'github-transport.cjs');
+ await fs.writeFile(transport,`'use strict';const http=require('node:http');exports.transport=(url,{kind}={})=>{const target=url==='https://api.github.com/repos/maxwellsdm1867/Rieke-OS/releases?per_page=100&page=1'?'/api':url===${JSON.stringify(root+'desktop-release.json')}?'/descriptor':url===${JSON.stringify(root+descriptor.archive.filename)}?'/archive':null;if(!target)return Promise.reject(new Error('Unexpected official fixture URL'));return new Promise((resolve,reject)=>{http.get(${JSON.stringify(base)}+target,response=>resolve({statusCode:response.statusCode,headers:response.headers,body:response})).on('error',reject);});};\n`,{mode:0o600});
+ const runtime=path.join(fixture.bundle,'Contents/Resources/runtime'),manifestFile=path.join(runtime,'runtime-manifest.json'),manifest=JSON.parse(await fs.readFile(manifestFile));manifest.application_version='0.1.2';
+ const releaseNames=Object.keys(manifest.resources).filter(name=>name.endsWith('/rieke-release.json'));
+ assert.ok(releaseNames.includes('application/rieke-release.json'));
+ for(const name of releaseNames){const file=path.join(runtime,name),release=JSON.parse(await fs.readFile(file));release.version='0.1.2';const bytes=Buffer.from(JSON.stringify(release,null,2)+'\n');await fs.writeFile(file,bytes);manifest.resources[name]={sha256:createHash('sha256').update(bytes).digest('hex'),size:bytes.length};}
+ await fs.writeFile(manifestFile,JSON.stringify(manifest,null,2)+'\n');
+ const archive=path.join(fixture.bundle,'Contents/Resources/app.asar'),unpacked=path.join(fixture.root,'update-ui-test-asar');asar.extractAll(archive,unpacked);
+ const mainFile=path.join(unpacked,'main.cjs'),original=await fs.readFile(mainFile,'utf8'),anchor='manifest: supervisor.manifest, distribution});';assert.ok(original.includes(anchor));
+ await fs.writeFile(mainFile,original.replace(anchor,`manifest: supervisor.manifest, distribution, transport: require(${JSON.stringify(transport)}).transport});`));
+ const packageFile=path.join(unpacked,'package.json'),pack=JSON.parse(await fs.readFile(packageFile));pack.version='0.1.2';await fs.writeFile(packageFile,JSON.stringify(pack,null,2)+'\n');
+ await asar.createPackage(unpacked,archive);asar.uncacheAll();const plist=path.join(fixture.bundle,'Contents/Info.plist');
+ await run('/usr/libexec/PlistBuddy',['-c','Set :CFBundleShortVersionString 0.1.2',plist]);await run('/usr/libexec/PlistBuddy',['-c','Set :CFBundleVersion 0.1.2',plist]);
+ await run('/usr/libexec/PlistBuddy',['-c',`Set :ElectronAsarIntegrity:Resources/app.asar:hash ${createHash('sha256').update(asar.getRawHeader(archive).headerString).digest('hex')}`,plist]);
+ await run('/usr/bin/codesign',['--force','--sign','-','--entitlements',path.resolve(__dirname,'../entitlements.mac.plist'),fixture.bundle]);await run('/usr/bin/codesign',['--verify','--deep','--strict',fixture.bundle]);
+ await verifyResources(runtime,manifest.resources);receipt.test_current_version='0.1.2';
+ ({application,page}=await launch(fixture));page.on('pageerror',error=>receipt.failures.push({name:'renderer-pageerror',message:error.message}));
+ await page.getByRole('heading',{name:'Your projects',exact:true}).waitFor({timeout:90000});
+ await check('background official release metadata creates a quiet version notice without downloading',async()=>{
+ await page.waitForFunction(()=>[...document.querySelectorAll('.app-update-badge')].some(node=>node.textContent==='v0.1.3'),undefined,{timeout:30000});
+ assert.ok(counts.api>0&&counts.descriptor>0);assert.equal(counts.archive,0);const status=await page.evaluate(()=>window.riekeDesktop.status());assert.equal(status.state,'Available');assert.equal(status.channel,'unsigned-testing');assert.equal(status.manual_updates,true);return {visible_available_version:'0.1.3',automatic_metadata:true,no_archive_download:true};
+ });
+ await check('explicit Check and Download prepare a fully validated real ZIP and show Restart to update',async()=>{
+ await page.getByRole('button',{name:/^Release \/ Publish/}).first().click();const dialog=page.getByRole('dialog',{name:'Release / Publish',exact:true});
+ assert.match(await dialog.innerText(),/Unsigned testing/);assert.doesNotMatch(await dialog.innerText(),/New versions download automatically|next launch applies/);
+ const before=counts.api;await dialog.getByRole('button',{name:'Check for updates',exact:true}).click();await page.waitForFunction(()=>window.riekeDesktop.status().then(s=>s.state==='Available'));assert.ok(counts.api>before);assert.equal(counts.archive,0);
+ await dialog.getByRole('button',{name:'Download update',exact:true}).click();await dialog.getByRole('button',{name:'Restart to update',exact:true}).waitFor({timeout:180000});
+ const status=await page.evaluate(()=>window.riekeDesktop.status());assert.equal(status.state,'Ready');assert.equal(status.can_restart,true);assert.equal(counts.archive,1);
+ await page.screenshot({path:path.join(path.dirname(output),'github-testing-update-ready.png')});return {explicit_check:true,explicit_download:true,real_zip_validated:true,ready_version:status.available,no_install_before_restart:true};
+ });
+ if(process.env.RIEKE_E2E_H5)await check('Restart to update defers an actual importer while the current app remains usable',async()=>{
+ await page.keyboard.press('Escape');await fs.mkdir(fixture.projects,{recursive:true});await page.getByRole('button',{name:/Start a brand new project/}).click();const form=page.locator('.onboarding-create-form');await form.getByRole('textbox').first().fill('Testing update writer fixture');
+ await application.evaluate(({dialog},directory)=>{dialog.showOpenDialog=async()=>({canceled:false,filePaths:[directory]});},fixture.projects);
+ await form.getByRole('button',{name:'Browse: New project folder',exact:true}).click();const picker=page.getByRole('dialog',{name:'New project folder',exact:true});await picker.getByRole('checkbox',{name:'Create a new folder inside this location'}).check();await picker.getByRole('textbox',{name:'New folder name',exact:true}).fill('writer');await picker.getByRole('button',{name:'Use new folder',exact:true}).click();await page.getByRole('button',{name:'Create & open',exact:true}).click();await page.getByRole('button',{name:'Project overview',exact:true}).waitFor({timeout:90000});
+ const author=page.getByRole('dialog',{name:'Tag author',exact:true});if(await author.waitFor({state:'visible',timeout:3000}).then(()=>true,()=>false)){await author.getByRole('textbox').fill('Update E2E');await author.getByRole('button',{name:'Create profile',exact:true}).click();}
+ const source=path.join(fixture.home,'writer.h5');await fs.copyFile(path.resolve(process.env.RIEKE_E2E_H5),source);
+ const job=await page.evaluate(async source=>{const response=await fetch('/api/imports',{method:'POST',headers:{'content-type':'application/json','X-Workspace-Request':'1'},body:JSON.stringify({source_path:source})});if(!response.ok)throw new Error(await response.text());return response.json();},source);
+ const before=await ownedControl(fixture,'health');await page.getByRole('button',{name:/^Release \/ Publish/}).first().click();const dialog=page.getByRole('dialog',{name:'Release / Publish',exact:true});await dialog.getByRole('button',{name:'Restart to update',exact:true}).click();await dialog.getByRole('alert').waitFor({timeout:30000});
+ assert.match(await dialog.getByRole('alert').innerText(),/import|work|busy|writer/i);assert.equal(page.isClosed(),false);assert.equal((await ownedControl(fixture,'health')).pid,before.pid);assert.equal((await page.evaluate(()=>window.riekeDesktop.status())).state,'Ready');
+ let done;for(let n=0;n<240;n++){done=await page.evaluate(async id=>(await fetch('/api/jobs').then(r=>r.json())).jobs.find(j=>j.job_uuid===id),job.job_uuid);if(['complete','completed','failed','cancelled'].includes(done?.status))break;await delay(500);}assert.ok(['complete','completed'].includes(done?.status));await page.keyboard.press('Escape');return {busy_restart_deferred:true,root_preserved:true,current_app_usable:true,real_import_completed:true};
+ });
+ await check('ordinary Quit with a prepared testing update leaves the current complete app installed',async()=>{
+ await page.keyboard.press('Escape');assert.equal((await page.evaluate(()=>window.riekeDesktop.status())).state,'Ready');await gracefulQuit(application,page);application=null;
+ const after=JSON.parse(await fs.readFile(manifestFile));assert.equal(after.application_version,'0.1.2');await verifyResources(runtime,manifest.resources);
+ assert.equal(await fs.stat(path.join(path.dirname(fixture.bundle),'.Rieke OS.previous.app')).then(()=>true,()=>false),false);const cache=path.join(fixture.userData,'updates','unsigned-testing');const entries=await fs.readdir(cache).catch(()=>[]);assert.ok(!entries.some(name=>/^install-.*\.json$/.test(name)));return {ordinary_quit_did_not_install:true,current_version:'0.1.2',runtime_immutable:true,native_orderly_exit:true};
+ });
+ assert.equal(receipt.failures.length,0);await write();await new Promise(resolve=>server.close(resolve));server=null;console.log('GitHub update UI receipt: '+output);
+}
+main().catch(async error=>{console.error(error.stack);receipt.failures.push({name:'github-update-suite',message:error.message});await write();server?.close();process.exitCode=1;});
diff --git a/desktop/e2e/helpers.cjs b/desktop/e2e/helpers.cjs
new file mode 100644
index 0000000..27577e3
--- /dev/null
+++ b/desktop/e2e/helpers.cjs
@@ -0,0 +1,54 @@
+'use strict';
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const os = require('node:os');
+const {execFile} = require('node:child_process');
+const {promisify} = require('node:util');
+const {_electron} = require('playwright');
+const run = promisify(execFile);
+async function createFixture({reuse = true} = {}) {
+ if (reuse && process.env.RIEKE_E2E_FIXTURE_ROOT) {
+ const root = await fs.realpath(process.env.RIEKE_E2E_FIXTURE_ROOT);
+ if (!root.startsWith(await fs.realpath(os.tmpdir()) + path.sep) || !path.basename(root).startsWith('rieke-packaged-ui-e2e-')) throw new Error('Reusable fixture must be an owned isolated test directory');
+ const home = path.join(root, 'home'), bundle = path.join(home, 'Applications', 'Rieke OS.app');
+ if ((await fs.stat(root)).uid !== process.getuid()) throw new Error('Test directory owner mismatch');
+ return {root, home, bundle, userData: path.join(home, 'state'), executable: path.join(bundle, 'Contents/MacOS/Rieke OS'), projects: path.join(home, 'projects')};
+ }
+ const root = await fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(), 'rieke-packaged-ui-e2e-')));
+ const home = path.join(root, 'home');
+ const bundle = path.join(home, 'Applications', 'Rieke OS.app');
+ const userData = path.join(home, 'state');
+ await fs.mkdir(path.dirname(bundle), {recursive: true}); await fs.mkdir(userData, {recursive: true});
+ await run('/usr/bin/ditto', [path.resolve(__dirname, '../dist/mac-arm64/Rieke OS.app'), bundle]);
+ return {root, home, bundle, userData, executable: path.join(bundle, 'Contents/MacOS/Rieke OS'), projects: path.join(home, 'projects')};
+}
+async function launch(fixture) {
+ const application = await _electron.launch({executablePath: fixture.executable, args: [`--user-data-dir=${fixture.userData}`],
+ env: {...process.env, HOME: fixture.home, TMPDIR: fixture.root, XDG_CONFIG_HOME: fixture.userData},
+ chromiumSandbox: true, bypassCSP: false, timeout: 45000});
+ fixture.activeApplication = application;
+ const actual = await application.evaluate(({app}) => ({home: process.env.HOME, userData: app.getPath('userData'),
+ appPath: app.getAppPath(), packaged: app.isPackaged, pid: process.pid, electronVersion: process.versions.electron}));
+ if (actual.home !== fixture.home || actual.userData !== fixture.userData || !actual.appPath.startsWith(fixture.bundle + '/'))
+ throw new Error('Application state is not isolated; no UI interactions are permitted');
+ const page = await application.firstWindow({timeout: 45000});
+ return {application, page, actual};
+}
+async function gracefulQuit(application, page) {
+ const exited = new Promise(resolve => application.process().once('exit', resolve));
+ await page.evaluate(() => { void window.riekeDesktop.quit(); });
+ const code = await Promise.race([exited, new Promise((_resolve, reject) => setTimeout(() => reject(new Error('App did not acknowledge orderly exit')), 45000).unref())]);
+ if (code !== 0) throw new Error('App exited without a successful orderly shutdown');
+}
+async function ownedControl(fixture, endpoint, body) {
+ const record = JSON.parse(await fs.readFile(path.join(fixture.userData, 'desktop-service.json'), 'utf8'));
+ if (!record.executable.startsWith(fixture.bundle + '/')) throw new Error('Service does not belong to this test bundle');
+ const command = await run('/bin/ps', ['eww', '-p', String(record.pid), '-o', 'command=']);
+ const capability = command.stdout.match(/(?:^|\s)RIEKE_DESKTOP_CAPABILITY=([a-f0-9]{64})(?:\s|$)/)?.[1];
+ if (!capability) throw new Error('No capability for the owned test service');
+ const response = await fetch(`http://127.0.0.1:${record.port}/api/desktop/${endpoint}`, {method: body === undefined ? 'GET' : 'POST',
+ headers: {'X-Rieke-Desktop-Capability': capability, 'X-Workspace-Request': '1', 'Content-Type': 'application/json'},
+ ...(body === undefined ? {} : {body: JSON.stringify(body)}), signal: AbortSignal.timeout(35000)});
+ const value = await response.json(); if (!response.ok) throw new Error(value.error || 'Owned test control failed'); return value;
+}
+module.exports = {createFixture, launch, gracefulQuit, ownedControl, run};
diff --git a/desktop/e2e/packaged.e2e.cjs b/desktop/e2e/packaged.e2e.cjs
new file mode 100644
index 0000000..f7ebef1
--- /dev/null
+++ b/desktop/e2e/packaged.e2e.cjs
@@ -0,0 +1,410 @@
+'use strict';
+const assert = require('node:assert/strict');
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const http = require('node:http');
+const {spawn} = require('node:child_process');
+const {createHash, randomUUID} = require('node:crypto');
+const {chromium} = require('playwright');
+const {createFixture, launch, gracefulQuit, ownedControl, run} = require('./helpers.cjs');
+const {verifyResources} = require('../updater-validation.cjs');
+const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
+const output = path.resolve(__dirname, '../../docs/dev/desktop-ui-e2e');
+let fixture, application, page, recoveredBrowser, deliberateCrash = false;
+const receipt = {format: 'rieke-packaged-ui-e2e', version: 1, signed_qualification: false, checks: [], failures: [],
+ method: 'Playwright Electron 1.63.0; exact packaged app clone; isolated HOME and standard --user-data-dir; sandbox enabled and CSP intact; native chooser return values stubbed only'};
+receipt.run_id = randomUUID().slice(0, 8);
+const projectName = 'Electron lifecycle fixture ' + receipt.run_id;
+process.on('uncaughtException', error => {
+ // Pinned Playwright throws this client assertion when Electron recovers the
+ // same target after forcefullyCrashRenderer. The app is inspected through
+ // its actual owned WebContents API during that fault, instead of its stale
+ // Playwright Page session. Other unexpected exceptions still fail the run.
+ if (deliberateCrash && error.message === 'Target crashed') {receipt.playwright_crash_session_warnings = (receipt.playwright_crash_session_warnings || 0) + 1; return;}
+ throw error;
+});
+async function chooseTestProfile() {
+ const dialog = page.getByRole('dialog', {name: 'Tag author', exact: true});
+ const visible = await dialog.waitFor({state: 'visible', timeout: 3000}).then(() => true).catch(() => false);
+ if (visible) {
+ const existing = dialog.getByRole('button', {name: 'Electron E2E', exact: true});
+ if (await existing.count()) await existing.first().click();
+ else {await dialog.getByRole('textbox').fill('Electron E2E'); await dialog.getByRole('button', {name: 'Create profile', exact: true}).click();}
+ await dialog.waitFor({state: 'hidden'});
+ }
+}
+async function chooseNewProjectFolder(form, name) {
+ const selected = form.locator('#new-project-directory');
+ assert.equal(await selected.getAttribute('readonly'), '');
+ await application.evaluate(({dialog}, directory) => {dialog.showOpenDialog = async () => ({canceled:false,filePaths:[directory]});}, fixture.projects);
+ await form.getByRole('button', {name:'Browse: New project folder',exact:true}).click();
+ const picker = page.getByRole('dialog', {name:'New project folder',exact:true});
+ await picker.getByRole('checkbox', {name:'Create a new folder inside this location'}).check();
+ await picker.getByRole('textbox', {name:'New folder name',exact:true}).fill(name);
+ await picker.getByRole('button', {name:'Use new folder',exact:true}).click();
+ await picker.waitFor({state:'hidden'});
+ assert.equal(await selected.inputValue(), path.join(fixture.projects,name));
+}
+async function verifyHeaderIcon(selector, headerSelector, screenName) {
+ const measurements = [];
+ try {
+ for (const width of [1440, 960]) {
+ await application.evaluate(({BrowserWindow}, width) => BrowserWindow.getAllWindows()[0].setContentSize(width, 900), width);
+ await page.waitForFunction(({selector, width}) => {
+ const icon = document.querySelector(selector);
+ return window.innerWidth === width && icon?.complete && icon.naturalWidth > 0;
+ }, {selector, width}, {timeout: 15000});
+ const geometry = await page.evaluate(({selector, headerSelector}) => {
+ const icon = document.querySelector(selector), header = document.querySelector(headerSelector);
+ const bounds = node => {const r = node.getBoundingClientRect(); return {left:r.left, right:r.right, top:r.top, bottom:r.bottom, width:r.width, height:r.height};};
+ const others = [...header.children].filter(node => node !== icon && !node.classList.contains('spacer') && node.getClientRects().length && getComputedStyle(node).visibility !== 'hidden')
+ .map(node => ({tag: node.tagName, className: node.className, rect: bounds(node)})).filter(node => node.rect.width > 0 && node.rect.height > 0);
+ return {viewport: window.innerWidth, source: new URL(icon.currentSrc).pathname, naturalWidth:icon.naturalWidth, naturalHeight:icon.naturalHeight,
+ icon:bounds(icon), header:bounds(header), others};
+ }, {selector, headerSelector});
+ await page.screenshot({path: path.join(output, `${screenName}-${width}.png`)});
+ assert.equal(geometry.source, '/rieke-os-icon.png'); assert.ok(geometry.naturalWidth > 0);
+ const expectedSize = selector === '.onboarding-app-icon' ? 48 : 32;
+ assert.equal(geometry.icon.width, expectedSize); assert.equal(geometry.icon.height, expectedSize);
+ assert.ok(geometry.icon.left >= geometry.header.left && geometry.icon.right <= geometry.header.right + 1 && geometry.icon.right <= width + 1,
+ 'App icon must remain inside its header and viewport: ' + JSON.stringify(geometry));
+ assert.ok(geometry.icon.left > (geometry.header.left + geometry.header.right) / 2, 'App icon must be on the right');
+ for (const sibling of geometry.others) {
+ const r = sibling.rect, icon = geometry.icon;
+ assert.ok(!(r.left < icon.right - 1 && r.right > icon.left + 1 && r.top < icon.bottom - 1 && r.bottom > icon.top + 1),
+ 'App icon overlaps an adjacent header control: ' + JSON.stringify({width, sibling, icon}));
+ }
+ measurements.push({viewport: width, naturalWidth: geometry.naturalWidth, icon: geometry.icon, header: geometry.header});
+ }
+ } finally {await application.evaluate(({BrowserWindow}) => BrowserWindow.getAllWindows()[0].setContentSize(1440, 900));}
+ return {actual_packaged_icon_loaded: true, header_right: true, no_icon_overlap: true, native_window_widths: measurements};
+}
+async function writeReceipt() { await fs.mkdir(output, {recursive: true}); await fs.writeFile(path.join(output, 'receipt.json'), JSON.stringify(receipt, null, 2) + '\n'); }
+async function check(name, fn) {
+ const start = Date.now();
+ try { const evidence = await fn(); receipt.checks.push({name, passed: true, elapsed_ms: Date.now() - start, ...(evidence ? {evidence} : {})}); console.log('PASS ' + name); }
+ catch (error) { receipt.failures.push({name, message: error.message}); console.error('FAIL ' + name + ': ' + error.message); await writeReceipt(); throw error; }
+ await writeReceipt();
+}
+async function start(heading = 'Your projects') {
+ const launched = await launch(fixture); ({application, page} = launched);
+ page.on('pageerror', error => receipt.failures.push({name: 'renderer-pageerror', message: error.message}));
+ await page.getByRole('heading', {name: heading, exact: true}).waitFor({timeout: 90000});
+ return launched.actual;
+}
+async function main() {
+ fixture = await createFixture(); console.log('Isolated fixture: ' + fixture.root);
+ const runtime = path.join(fixture.bundle, 'Contents/Resources/runtime');
+ const bytes = await fs.readFile(path.join(runtime, 'runtime-manifest.json')); const manifest = JSON.parse(bytes);
+ receipt.manifest_sha256 = createHash('sha256').update(bytes).digest('hex'); receipt.application_version = manifest.application_version;
+ receipt.app_asar_sha256 = createHash('sha256').update(await fs.readFile(path.join(fixture.bundle, 'Contents/Resources/app.asar'))).digest('hex');
+ receipt.app_icon_sha256 = createHash('sha256').update(await fs.readFile(path.join(runtime, 'frontend/rieke-os-icon.png'))).digest('hex');
+ assert.equal(receipt.app_icon_sha256, '614f7bcd44ced5ff8cf76f648e7a4e23ec45bee3236ea5ad17f59e3a1e173483', 'Packaged renderer must use the existing app icon');
+ await check('cold packaged launcher and state isolation', async () => {const actual = await start(); assert.equal(actual.packaged, true); assert.equal(actual.electronVersion, '44.5.0'); return {isolated_user_state: true, packaged: true, electron_version: actual.electronVersion};});
+ await check('launcher actual app icon loads at upper right at normal and narrow native widths',
+ () => verifyHeaderIcon('.onboarding-app-icon', '.project-onboarding>header', 'launcher-icon'));
+ await check('renderer isolation, sandbox and CSP', async () => {
+ const settings = await application.evaluate(({BrowserWindow}) => {
+ const prefs = BrowserWindow.getAllWindows()[0].webContents.getLastWebPreferences();
+ return {contextIsolation: prefs.contextIsolation, sandbox: prefs.sandbox, nodeIntegration: prefs.nodeIntegration, webSecurity: prefs.webSecurity};
+ });
+ assert.deepEqual(settings, {contextIsolation: true, sandbox: true, nodeIntegration: false, webSecurity: true});
+ assert.deepEqual(await page.evaluate(() => ({require: typeof require, process: typeof process, rawIpc: typeof window.riekeDesktop.ipcRenderer})), {require: 'undefined', process: 'undefined', rawIpc: 'undefined'});
+ const inlineRan = await page.evaluate(() => { const script = document.createElement('script'); script.textContent = 'window.__inline_e2e=true'; document.body.append(script); return window.__inline_e2e === true; });
+ assert.equal(inlineRan, false); return settings;
+ });
+ await check('testing updates preserve explicit download, restart and bootstrap boundaries', async () => {
+ const status = await page.evaluate(() => window.riekeDesktop.status());
+ if(status.channel==='unsigned-testing') {
+ assert.equal(status.manual_updates,true);
+ assert.ok(!['Downloading','Validating','Ready','Draining','Installing'].includes(status.state), 'Background checks must never download or install');
+ const result=await page.evaluate(()=>window.riekeDesktop.restartToUpdate());assert.equal(result.ready,false);
+ } else assert.equal(status.state, 'Deferred');
+ const message = await page.evaluate(async () => { try { await window.riekeDesktop.installAndOpen(); return ''; } catch (error) { return error.message; } });
+ assert.match(message, /before backend startup/);
+ });
+ await check('malformed draft IPC and external release URLs reject', async () => {
+ const results = await page.evaluate(async () => {
+ const actions = [() => window.riekeDesktop.saveDraft({projectId: '../../escape', value: {}}),
+ () => window.riekeDesktop.saveDraft({projectId: 'launcher', value: {}, path: '/outside'}),
+ () => window.riekeDesktop.loadDraft('../../escape'), () => window.riekeDesktop.openReleaseNotes('https://evil.example/'),
+ () => window.riekeDesktop.acknowledgeDrafts('foreign-request', {ok: true})];
+ return Promise.all(actions.map(async action => {try {await action(); return false;} catch {return true;}}));
+ }); assert.deepEqual(results, [true, true, true, true, true]);
+ });
+ await check('renderer cannot drain, stop or source-update services', async () => {
+ const statuses = await page.evaluate(async () => Promise.all(['/api/desktop/drain', '/api/desktop/stop', '/api/desktop/authorize-project'].map(async url =>
+ (await fetch(url, {method: 'POST', headers: {'Content-Type': 'application/json', 'X-Workspace-Request': '1'}, body: '{}'})).status)));
+ assert.deepEqual(statuses, [403, 403, 403]);
+ const sourceStatuses = await page.evaluate(async () => Promise.all(['/api/app/updates', '/api/app/updates/check', '/api/app/updates/stage', '/api/app/updates/download']
+ .map(async url => (await fetch(url)).status)));
+ assert.deepEqual(sourceStatuses, [409, 409, 409, 409]);
+ });
+ await check('scientific project creation cannot write into packaged resources or redirected parents', async () => {
+ const direct = path.join(fixture.bundle, 'Contents/Resources/forbidden-project');
+ const alias = path.join(fixture.root, 'resource-parent-alias-' + receipt.run_id);
+ await fs.symlink(path.join(fixture.bundle, 'Contents/Resources'), alias);
+ for (const directory of [direct, path.join(alias, 'forbidden-project'),
+ path.join(fixture.bundle, 'forbidden-project'), path.join(fixture.bundle, 'Contents/MacOS/forbidden-project')]) {
+ const result = await page.evaluate(async project_directory => {
+ const response = await fetch('/api/projects', {method: 'POST', headers: {'Content-Type': 'application/json', 'X-Workspace-Request': '1'}, body: JSON.stringify({name: 'Must not be created', project_directory})});
+ return {status: response.status, value: await response.json()};
+ }, directory);
+ assert.equal(result.status, 400); assert.ok(result.value.error);
+ await assert.rejects(fs.stat(directory), {code: 'ENOENT'});
+ }
+ });
+ await check('foreign navigation, popups and cross-origin requests blocked', async () => {
+ let visits = 0; const server = http.createServer((_request, response) => {visits++; response.end('foreign');});
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); const target = `http://127.0.0.1:${server.address().port}/`;
+ const previous = page.url();
+ try {
+ await page.evaluate(url => {window.open(url); window.location.assign(url);}, target); await delay(600);
+ assert.equal(page.url(), previous); assert.equal(application.windows().length, 1); assert.equal(visits, 0);
+ const blocked = await page.evaluate(async url => {try {await fetch(url); return false;} catch {return true;}}, target); assert.equal(blocked, true);
+ await page.evaluate(url => {const frame = document.createElement('iframe'); frame.src = url; document.body.append(frame);}, target); await delay(100); assert.equal(visits, 0);
+ await page.evaluate(() => document.querySelectorAll('iframe').forEach(frame => frame.remove()));
+ // Chromium reports the deliberately denied navigation to CDP before
+ // Electron cancels it. Reset this test-created pending-navigation state.
+ await application.evaluate(({BrowserWindow}) => BrowserWindow.getAllWindows()[0].webContents.reload());
+ await page.getByRole('heading', {name: 'Your projects', exact: true}).waitFor({timeout: 15000});
+ } finally { await new Promise(resolve => server.close(resolve)); }
+ });
+ await check('foreign renderer IPC cannot access desktop controls', async () => {
+ const preload = path.join(fixture.root, 'foreign-preload.cjs');
+ await fs.writeFile(preload, "const {contextBridge,ipcRenderer}=require('electron');contextBridge.exposeInMainWorld('foreignProbe',{request:()=>ipcRenderer.invoke('desktop:status').then(()=>({accepted:true}),e=>({accepted:false,message:e.message}))});");
+ const result = await application.evaluate(async ({BrowserWindow}, preload) => {
+ const foreign = new BrowserWindow({show: false, webPreferences: {sandbox: true, contextIsolation: true, nodeIntegration: false, preload}});
+ try {await foreign.loadURL('data:text/html,
Foreign test renderer
'); return await foreign.webContents.executeJavaScript('window.foreignProbe.request()');}
+ finally {foreign.destroy();}
+ }, preload);
+ assert.equal(result.accepted, false); assert.match(result.message, /Unauthorized desktop frame/);
+ });
+ await check('native project-folder chooser uses typed result', async () => {
+ await fs.mkdir(fixture.projects, {recursive: true});
+ await application.evaluate(({dialog}, directory) => {dialog.showOpenDialog = async () => ({canceled: false, filePaths: [directory]});}, fixture.projects);
+ assert.equal(await page.evaluate(() => window.riekeDesktop.chooseProjectFolder()), fixture.projects);
+ await application.evaluate(({dialog}) => {dialog.showOpenDialog = async () => ({canceled: true, filePaths: []});});
+ assert.equal(await page.evaluate(() => window.riekeDesktop.chooseProjectFolder()), null);
+ });
+ await check('duplicate launch preserves one owner and existing backend', async () => {
+ const before = await ownedControl(fixture, 'health');
+ const child = spawn(fixture.executable, [`--user-data-dir=${fixture.userData}`], {env: {...process.env, HOME: fixture.home, TMPDIR: fixture.root}, stdio: 'ignore'});
+ const code = await Promise.race([new Promise(resolve => child.once('exit', resolve)), delay(10000).then(() => {throw new Error('Duplicate app did not exit');})]);
+ assert.equal(code, 0); assert.equal((await ownedControl(fixture, 'health')).pid, before.pid); assert.equal(application.windows().length, 1);
+ });
+ await check('corrupted saved view is visible, blocks overwrite and supports preserved quit or explicit reset', async () => {
+ await gracefulQuit(application, page); application = null; page = null;
+ const filename = path.join(fixture.userData, 'drafts', 'launcher.json');
+ const valid = JSON.parse(await fs.readFile(filename, 'utf8'));
+ const corrupted = '{intentional unreadable saved-view fixture'; await fs.writeFile(filename, corrupted);
+ await start('Saved view needs recovery');
+ assert.ok((await page.getByRole('alert').innerText()).includes('saved view'));
+ await delay(3300); assert.equal(await fs.readFile(filename, 'utf8'), corrupted);
+ assert.equal(await page.evaluate(async value => {try {await window.riekeDesktop.saveDraft({projectId: 'launcher', value}); return false;} catch {return true;}}, valid), true);
+ assert.equal((await page.evaluate(() => window.riekeDesktop.quit())).ready, false);
+ await page.screenshot({path: path.join(output, 'corrupted-view-recovery.png')});
+ const exited = new Promise(resolve => application.process().once('exit', resolve));
+ await page.getByRole('button', {name: 'Keep saved view and quit', exact: true}).click();
+ assert.equal(await exited, 0); application = null; page = null;
+ assert.equal(await fs.readFile(filename, 'utf8'), corrupted);
+ await start('Saved view needs recovery');
+ await page.getByRole('button', {name: 'Start with a new view', exact: true}).click();
+ await page.getByRole('heading', {name: 'Your projects', exact: true}).waitFor();
+ const backups = (await fs.readdir(path.dirname(filename))).filter(name => name.startsWith('launcher.corrupt-'));
+ assert.ok((await Promise.all(backups.map(name => fs.readFile(path.join(path.dirname(filename), name), 'utf8')))).includes(corrupted));
+ assert.equal(await page.evaluate(async () => {try {await window.riekeDesktop.resetDraft('launcher'); return false;} catch {return true;}}), true);
+ return {visible_recovery: true, corrupt_bytes_preserved: true, ordinary_quit_deferred: true, preserved_quit_acknowledged: true, explicit_reset_preserved_backup: true};
+ });
+ await check('create and open project through actual React interface', async () => {
+ await page.getByRole('button', {name: /Start a brand new project/}).click();
+ const form = page.locator('.onboarding-create-form');
+ await form.getByRole('textbox').nth(0).fill(projectName);
+ const folderBeforeCancel=await form.locator('#new-project-directory').inputValue();
+ await application.evaluate(({dialog})=>{dialog.showOpenDialog=async()=>({canceled:true,filePaths:[]});});
+ await form.getByRole('button',{name:'Browse: New project folder',exact:true}).click();
+ await form.getByRole('button',{name:'Browse: New project folder',exact:true}).waitFor({state:'visible'});
+ await page.waitForFunction(()=>!document.querySelector('.folder-path-control button')?.disabled);
+ assert.equal(await form.locator('#new-project-directory').inputValue(),folderBeforeCancel);
+ assert.equal(await page.getByRole('dialog',{name:'New project folder',exact:true}).count(),0);
+ assert.equal(await fs.stat(path.join(fixture.projects,receipt.run_id)).then(()=>true,()=>false),false);
+ assert.equal(await form.getByRole('button',{name:'Create & open',exact:true}).isDisabled(),true);
+ await chooseNewProjectFolder(form, receipt.run_id);
+ await page.getByRole('button', {name: 'Create & open', exact: true}).click();
+ await page.getByRole('button', {name: 'Project overview', exact: true}).waitFor({timeout: 90000});
+ assert.match(await page.title(), /Electron lifecycle fixture/);
+ const project = await page.evaluate(() => fetch('/api/projects').then(response => response.json()));
+ receipt.test_project_uuid = project.current_project_uuid; assert.ok(project.current_project_uuid);
+ await chooseTestProfile();
+ await page.screenshot({path: path.join(output, 'project.png')});
+ });
+ await check('workspace actual app icon loads at upper right without overlap at normal and narrow native widths',
+ () => verifyHeaderIcon('.header-app-icon', '.app-header', 'workspace-icon'));
+ await check('rapid warm project navigation persists the current view before document replacement', async () => {
+ const secondName = projectName + ' second';
+ await page.locator('.sidebar').getByRole('button', {name: /Start a brand new project/}).click();
+ const form = page.locator('.onboarding-create-form'); await form.getByRole('textbox').nth(0).fill(secondName);
+ await chooseNewProjectFolder(form, receipt.run_id + '-second');
+ await page.getByRole('button', {name: 'Create & open', exact: true}).click();
+ await page.waitForFunction(expected => document.title.startsWith(expected), secondName, {timeout: 90000}); await chooseTestProfile();
+ receipt.second_project_uuid = (await page.evaluate(() => fetch('/api/projects').then(response => response.json()))).current_project_uuid;
+ assert.ok(receipt.second_project_uuid);
+ await page.getByRole('navigation', {name: 'Research projects'}).getByRole('button', {name: projectName, exact: true}).click();
+ await page.waitForFunction(expected => document.title.startsWith(expected) && !document.title.includes(' second'), projectName, {timeout: 30000});
+ const navigationStart = Date.now();
+ await page.getByRole('button', {name: 'Activity & logs', exact: true}).click();
+ await page.getByRole('navigation', {name: 'Research projects'}).getByRole('button', {name: secondName, exact: true}).click();
+ await page.waitForFunction(expected => document.title.startsWith(expected), secondName, {timeout: 30000});
+ const saved = JSON.parse(await fs.readFile(path.join(fixture.userData, 'drafts', receipt.test_project_uuid + '.json')));
+ assert.equal(saved.value.route.page, 'activity');
+ await page.getByRole('navigation', {name: 'Research projects'}).getByRole('button', {name: projectName, exact: true}).click();
+ await page.waitForFunction(expected => document.title.startsWith(expected) && !document.title.includes(' second'), projectName, {timeout: 30000});
+ await page.waitForFunction(() => [...document.querySelectorAll('.nav-item.active')].some(item => item.textContent.includes('Activity & logs')));
+ return {warm_document_handoff: true, route_persisted: 'activity', elapsed_ms: Date.now() - navigationStart};
+ });
+ await check('missing saved view opens normally without corrupt-draft recovery', async () => {
+ const missing = path.join(fixture.userData, 'drafts', receipt.second_project_uuid + '.json');
+ await fs.unlink(missing);
+ const secondName = projectName + ' second';
+ await page.getByRole('navigation', {name: 'Research projects'}).getByRole('button', {name: secondName, exact: true}).click();
+ await page.waitForFunction(expected => document.title.startsWith(expected), secondName, {timeout: 30000});
+ await page.waitForFunction(() => [...document.querySelectorAll('.nav-item.active')].some(item => item.textContent.includes('Project overview')));
+ assert.equal(await page.getByRole('heading', {name: 'Saved view needs recovery', exact: true}).count(), 0);
+ await page.getByRole('navigation', {name: 'Research projects'}).getByRole('button', {name: projectName, exact: true}).click();
+ await page.waitForFunction(expected => document.title.startsWith(expected) && !document.title.includes(' second'), projectName, {timeout: 30000});
+ return {missing_draft_treated_as_new_view: true};
+ });
+ if (process.env.RIEKE_E2E_H5) await check('actual active import defers close without stopping any writer', async () => {
+ const copied = path.join(fixture.root, 'scientific-fixture.h5'); await fs.copyFile(process.env.RIEKE_E2E_H5, copied);
+ const job = await page.evaluate(async source_path => {
+ const response = await fetch('/api/imports', {method: 'POST', headers: {'Content-Type': 'application/json', 'X-Workspace-Request': '1'}, body: JSON.stringify({source_path})});
+ const value = await response.json(); if (response.status !== 202) throw new Error(value.error || 'Import was not accepted'); return value;
+ }, copied);
+ const before = await ownedControl(fixture, 'health');
+ let status;
+ for (let index = 0; index < 80; index++) {
+ status = await page.evaluate(async uuid => (await fetch('/api/jobs').then(response => response.json())).jobs.find(job => job.job_uuid === uuid), job.job_uuid);
+ if (status && !['queued', 'waiting'].includes(status.status)) break;
+ await delay(250);
+ }
+ assert.ok(status && !['complete', 'completed', 'failed', 'cancelled'].includes(status.status));
+ const result = await page.evaluate(() => window.riekeDesktop.quit()); assert.equal(result.ready, false);
+ assert.equal((await ownedControl(fixture, 'health')).pid, before.pid); assert.equal(page.isClosed(), false);
+ await application.evaluate(({BrowserWindow}) => BrowserWindow.getAllWindows()[0].close());
+ await delay(500);
+ assert.equal(page.isClosed(), false); assert.equal((await ownedControl(fixture, 'health')).pid, before.pid);
+ for (let index = 0; index < 240; index++) {
+ status = await page.evaluate(async uuid => (await fetch('/api/jobs').then(response => response.json())).jobs.find(job => job.job_uuid === uuid), job.job_uuid);
+ if (['complete', 'completed', 'failed', 'cancelled'].includes(status?.status)) break;
+ await delay(500);
+ }
+ assert.ok(['complete', 'completed'].includes(status?.status), 'Import must finish successfully before test shutdown');
+ // Completion is observed by the UI on its own polling cycle. Close its
+ // automatic review explicitly after it appears, rather than sending Escape
+ // before the dialog has been mounted.
+ const review=page.getByRole('dialog',{name:'Review imported data',exact:true});
+ if(await review.waitFor({state:'visible',timeout:10000}).then(()=>true,()=>false)) {
+ await review.getByRole('button',{name:'Close import review',exact:true}).click();
+ await review.waitFor({state:'hidden'});
+ }
+ await page.keyboard.press('Escape');
+ return {real_import: true, bridge_quit_deferred: true, native_window_close_deferred: true, root_pid_preserved: true, completed: true};
+ });
+ if(process.env.RIEKE_E2E_H5) await check('real protocol inspection uses combined epoch and cell pages with registered metadata and a plotted response',async()=>{
+ await page.keyboard.press('Escape');
+ const overview=await page.evaluate(()=>fetch('/api/overview').then(response=>response.json()));
+ const protocol=overview.protocols?.[0];assert.ok(protocol?.protocol_uuid,'Imported protocols must be available');
+ let combined=null,inspectedProtocolUuid=null;const pending=[];
+ const observe=response=>{
+ const url=new URL(response.url());
+ if(/^\/api\/protocols\/[^/]+\/epochs$/.test(url.pathname)&&url.searchParams.get('include_cells')==='true'&&response.status()===200)
+ pending.push(response.json().then(value=>{combined=value;inspectedProtocolUuid=url.pathname.split('/')[3];}));
+ };
+ page.on('response',observe);
+ try{
+ await page.getByRole('button',{name:'Project overview',exact:true}).click();
+ await page.locator('.ov-protocol-row:visible').first().click({timeout:90000});
+ await page.getByRole('button',{name:'Open inspection',exact:true}).click();
+ const tree=page.locator('.inspection-cell-tree');await tree.waitFor({state:'visible',timeout:90000});
+ await tree.locator('.cell-tree-date>summary').first().click();
+ await tree.locator('.cell-tree-cell>summary').first().click();
+ await tree.getByRole('button',{name:/^Inspect .* epoch 1$/}).first().click();
+ await page.getByRole('region',{name:'Recorded response viewer',exact:true}).waitFor({timeout:90000});
+ await page.waitForFunction(()=>{const canvas=document.querySelector('.tv-base');return canvas?.width>0&&!document.querySelector('.tv-loading')&&!document.querySelector('.tv-error');},undefined,{timeout:90000});
+ const details=page.getByRole('button',{name:'Details',exact:true});
+ if(await details.getAttribute('aria-pressed')!=='true')await details.click();
+ await page.getByRole('heading',{name:'Epoch details',exact:true}).waitFor();
+ await page.getByRole('button',{name:'All fields',exact:true}).click();
+ assert.ok(await page.locator('.metadata-panel-section').count()>0);
+ const fields=await page.evaluate(()=>fetch('/api/metadata/fields').then(response=>response.json()));
+ assert.ok(Array.isArray(fields.fields)&&fields.fields.length>0,'Registered metadata fields must match the bundled frontend');
+ await Promise.all(pending);assert.ok(combined?.epochs?.length>0&&combined?.cells?.length>0,'Actual UI must receive combined epochs and cells');
+ assert.ok(combined.query_revision);assert.ok(Number.isSafeInteger(combined.expected_binding_version));
+ assert.ok(!await page.getByText('Predicate field catalog unavailable.',{exact:false}).count());
+ return {real_epoch_inspection:true,trace_canvas_loaded:true,combined_epoch_count:combined.epochs.length,combined_cell_count:combined.cells.length,
+ metadata_field_count:fields.fields.length,metadata_panel_visible:true,protocol_uuid:inspectedProtocolUuid};
+ }finally{page.off('response',observe);}
+ });
+ await check('durable real renderer draft survives orderly close and cold restart', async () => {
+ await page.getByRole('button', {name: 'Project files', exact: true}).click();
+ await gracefulQuit(application, page); application = null; page = null;
+ const saved = JSON.parse(await fs.readFile(path.join(fixture.userData, 'drafts', receipt.test_project_uuid + '.json')));
+ assert.equal(saved.format, 'rieke-renderer-draft'); assert.equal(saved.value.route.page, 'files');
+ await start(); await page.getByRole('button', {name: new RegExp('^Open ' + projectName + ',')}).click();
+ await page.getByRole('button', {name: 'Project files', exact: true}).waitFor({timeout: 90000});
+ await chooseTestProfile();
+ await page.waitForFunction(() => [...document.querySelectorAll('.nav-item.active')].some(item => item.textContent.includes('Project files')), undefined, {timeout: 10000});
+ });
+ await check('project draft identity mismatch requires visible preserved recovery', async () => {
+ await gracefulQuit(application, page); application = null; page = null;
+ const filename = path.join(fixture.userData, 'drafts', receipt.test_project_uuid + '.json');
+ const saved = JSON.parse(await fs.readFile(filename, 'utf8'));
+ const corrupted = JSON.stringify({...saved, projectId: 'launcher'}); await fs.writeFile(filename, corrupted);
+ await start(); await page.getByRole('button', {name: new RegExp('^Open ' + projectName + ',')}).click();
+ await page.getByRole('heading', {name: 'Saved view needs recovery', exact: true}).waitFor({timeout: 90000});
+ assert.equal((await page.evaluate(() => window.riekeDesktop.quit())).ready, false);
+ assert.equal(await fs.readFile(filename, 'utf8'), corrupted);
+ await page.getByRole('button', {name: 'Start with a new view', exact: true}).click();
+ await page.getByRole('button', {name: 'Project overview', exact: true}).waitFor({timeout: 30000});
+ await chooseTestProfile();
+ const backups = (await fs.readdir(path.dirname(filename))).filter(name => name.startsWith(receipt.test_project_uuid + '.corrupt-'));
+ assert.ok((await Promise.all(backups.map(name => fs.readFile(path.join(path.dirname(filename), name), 'utf8')))).includes(corrupted));
+ return {project_identity_validated: true, mismatched_bytes_preserved: true, explicit_reset: true,
+ preserved_sha256: createHash('sha256').update(corrupted).digest('hex')};
+ });
+ await check('renderer crash enters recovery, preserves backend and requires recovery before quit', async () => {
+ const before = await ownedControl(fixture, 'health');
+ deliberateCrash = true;
+ receipt.crash_test_method = 'Actual renderer crash; original stale Playwright target replaced by fresh CDP connection to the same isolated app; real recovery UI, bridge and backend health assertions';
+ await application.evaluate(({BrowserWindow}) => {const target = BrowserWindow.getAllWindows()[0]; setTimeout(() => target.webContents.forcefullyCrashRenderer(), 100);});
+ await delay(300);
+ // Attach a fresh software-test session to this isolated app's existing CDP
+ // endpoint. Pinned Playwright cannot revive its original crashed Page.
+ const port = Number((await fs.readFile(path.join(fixture.userData, 'DevToolsActivePort'), 'utf8')).split('\n')[0]);
+ recoveredBrowser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
+ page = recoveredBrowser.contexts()[0].pages()[0];
+ page.on('pageerror', error => receipt.failures.push({name: 'recovered-renderer-pageerror', message: error.message}));
+ await page.getByRole('heading', {name: 'Rieke OS recovery', exact: true}).waitFor({timeout: 15000});
+ assert.equal((await ownedControl(fixture, 'health')).pid, before.pid);
+ const result = await page.evaluate(() => window.riekeDesktop.quit()); assert.equal(result.ready, false);
+ await page.screenshot({path: path.join(output, 'renderer-recovery.png')});
+ await page.getByRole('button', {name: 'Retry startup', exact: true}).click();
+ await page.getByRole('heading', {name: 'Your projects', exact: true}).waitFor({timeout: 30000});
+ assert.equal((await ownedControl(fixture, 'health')).pid, before.pid);
+ deliberateCrash = false;
+ });
+ await check('final orderly quit and packaged resource immutability', async () => {
+ await gracefulQuit(application, page); application = null; page = null;
+ await assert.rejects(fs.stat(path.join(fixture.userData, 'desktop-service.json')), {code: 'ENOENT'});
+ await verifyResources(runtime, manifest.resources);
+ assert.equal(createHash('sha256').update(await fs.readFile(path.join(runtime, 'runtime-manifest.json'))).digest('hex'), receipt.manifest_sha256);
+ assert.equal(receipt.failures.length, 0, 'Unexpected renderer errors: ' + receipt.failures.map(item => item.message).join('; '));
+ });
+ receipt.completed = true; await writeReceipt(); console.log('Packaged UI E2E completed');
+}
+main().catch(async error => {
+ process.exitCode = 1; await writeReceipt();
+ if (page) await page.screenshot({path: path.join(output, 'failure.png')}).catch(() => {});
+ if (application && page) { try {await gracefulQuit(application, page);} catch {console.error('Isolated app retained because orderly closure was not acknowledged.');} }
+ console.error(error.message);
+});
diff --git a/desktop/e2e/testing-upgrade.e2e.cjs b/desktop/e2e/testing-upgrade.e2e.cjs
new file mode 100644
index 0000000..bae0fac
--- /dev/null
+++ b/desktop/e2e/testing-upgrade.e2e.cjs
@@ -0,0 +1,139 @@
+'use strict';
+// Real packaged updater/helper; only GitHub transport and macOS open are mapped
+// to owned test boundaries. The source app, distribution and user app are read-only.
+const assert=require('node:assert/strict'),fs=require('node:fs/promises'),nativeFs=require('node:fs'),path=require('node:path'),http=require('node:http');
+const {spawn}=require('node:child_process'),{createHash}=require('node:crypto'),asar=require('@electron/asar');
+const {createFixture,launch,gracefulQuit,ownedControl,run}=require('./helpers.cjs');
+const {bundleDigest}=require('../bootstrap.cjs');
+const {verifyResources}=require('../updater-validation.cjs');
+const output=path.resolve(__dirname,'../../docs/dev/desktop-testing-upgrade-e2e.json');
+const receipt={format:'rieke-packaged-testing-native-upgrade-e2e',version:1,production_ready:false,checks:[],failures:[],
+ seams:['Official GitHub HTTPS transport mapped to owned loopback server serving exact final ZIP/descriptor.',
+ 'Native helper macOS open boundary recorded instead of OS launch; installed app then starts with real Electron/WSGI in isolated HOME/profile.'],
+ limits:['Synthetic version-only prior app, not an authentic previously published 0.1.2 desktop release.','Unsigned local host only; no signed update or clean-machine qualification.'],user_app_untouched:true};
+let fixture,server,application;const requests={api:0,descriptor:0,archive:0};
+const delay=ms=>new Promise(resolve=>setTimeout(resolve,ms));
+async function write(){await fs.mkdir(path.dirname(output),{recursive:true});await fs.writeFile(output,JSON.stringify({...receipt,requests},null,2)+'\n');}
+async function check(name,fn){const start=Date.now();try{const evidence=await fn();receipt.checks.push({name,passed:true,elapsed_ms:Date.now()-start,evidence});console.log('PASS '+name);}catch(error){receipt.failures.push({name,message:error.message});await write();throw error;}await write();}
+const WRAPPER=String.raw`
+'use strict';
+const fs=require('node:fs'),path=require('node:path'),{promisify}=require('node:util');
+const realRun=promisify(require('node:child_process').execFile);
+const config=JSON.parse(fs.readFileSync(process.argv[2],'utf8'));
+const helper=require(path.join(config.bundle,'Contents/Resources/app.asar/testing-install.cjs'));
+let opens=0;
+helper.applyTestingInstall({receiptPath:process.argv[3],run:async(exe,args,options)=>{
+ if(exe!=='/usr/bin/open')return realRun(exe,args,options);
+ opens++;fs.appendFileSync(config.openLog,JSON.stringify({phase:config.phase,args})+'\n',{mode:0o600});
+ if(config.fail_first_open&&opens===1)throw new Error('Owned test simulates macOS refusing the candidate launch');
+ return {stdout:'',stderr:''};
+}}).catch(error=>{fs.writeFileSync(process.argv[3]+'.result.json',JSON.stringify({state:'Deferred',error_type:error.name,message:error.message}));process.exitCode=1;});
+`;
+const DRIVER=String.raw`
+'use strict';
+const fs=require('node:fs'),path=require('node:path'),http=require('node:http'),{spawn}=require('node:child_process');
+const config=JSON.parse(fs.readFileSync(process.argv[2],'utf8'));
+const manifest=JSON.parse(fs.readFileSync(path.join(config.bundle,'Contents/Resources/runtime/runtime-manifest.json'),'utf8'));
+const packaged=path.join(config.bundle,'Contents/Resources/app.asar');
+const helper=require(path.join(packaged,'testing-install.cjs'));
+const {createTestingUpdateCoordinator}=require(path.join(packaged,'testing-updater.cjs'));
+const statuses=[];let coordinator,handoff,authorized=false;
+const app={isPackaged:true,getPath:name=>name==='exe'?process.execPath:config.userData,quit(){
+ coordinator?.stop();setTimeout(()=>{fs.writeFileSync(config.driverResult,JSON.stringify({phase:config.phase,authorized,statuses,handoff}),{mode:0o600});process.exit(0);},50);
+}};
+const transport=url=>{
+ const route=url==='https://api.github.com/repos/maxwellsdm1867/Rieke-OS/releases?per_page=100&page=1'?'/api':
+ url===config.descriptorURL?'/descriptor':url===config.archiveURL?'/archive':null;
+ if(!route)return Promise.reject(new Error('Unexpected official release URL'));
+ return new Promise((resolve,reject)=>http.get(config.base+route,response=>resolve({statusCode:response.statusCode,headers:response.headers,body:response})).on('error',reject));
+};
+const spawnHelper=(exe,args,options)=>spawn(exe,[config.wrapper,process.argv[2],args[2]],options);
+(async()=>{
+ const authorizeQuit=()=>{authorized=true;};
+ if(config.phase==='restore'){
+ handoff=await helper.restoreTestingPriorBundle({app,manifest,prepareQuit:async()=>({ready:true}),authorizeQuit,spawnHelper});
+ }else{
+ coordinator=createTestingUpdateCoordinator({app,manifest,distribution:require(path.join(packaged,'distribution.json')),transport,
+ publishStatus:status=>{if(statuses.at(-1)!==status.state)statuses.push(status.state);},prepareQuit:async()=>({ready:true}),authorizeQuit,
+ installHelper:async options=>{handoff=await helper.launchTestingInstall({...options,spawnHelper});return handoff;}});
+ await coordinator.start();if(!['Available','Ready'].includes(coordinator.getStatus().state))throw new Error('Official fixture did not offer update');
+ await coordinator.download();if(coordinator.getStatus().state!=='Ready')throw new Error('Real candidate validation did not become Ready');
+ const result=await coordinator.installPrepared();if(!result.installing)throw new Error(result.reason||'Native install did not hand off');
+ }
+})().catch(error=>{coordinator?.stop();fs.writeFileSync(config.driverResult,JSON.stringify({error:error.message,statuses}),{mode:0o600});process.exitCode=1;});
+`;
+async function versionPrior(){
+ const runtime=path.join(fixture.bundle,'Contents/Resources/runtime'),file=path.join(runtime,'runtime-manifest.json');
+ const manifest=JSON.parse(await fs.readFile(file));manifest.application_version='0.1.2';
+ for(const name of Object.keys(manifest.resources).filter(name=>name.endsWith('/rieke-release.json'))){
+ const target=path.join(runtime,name),release=JSON.parse(await fs.readFile(target));release.version='0.1.2';const data=Buffer.from(JSON.stringify(release,null,2)+'\n');await fs.writeFile(target,data);
+ manifest.resources[name]={...manifest.resources[name],sha256:createHash('sha256').update(data).digest('hex'),size:data.length};
+ }
+ await fs.writeFile(file,JSON.stringify(manifest,null,2)+'\n');
+ const archive=path.join(fixture.bundle,'Contents/Resources/app.asar'),directory=path.join(fixture.root,'prior-asar');asar.extractAll(archive,directory);
+ const packageFile=path.join(directory,'package.json'),pack=JSON.parse(await fs.readFile(packageFile));pack.version='0.1.2';await fs.writeFile(packageFile,JSON.stringify(pack,null,2)+'\n');
+ await asar.createPackage(directory,archive);asar.uncacheAll();
+ const plist=path.join(fixture.bundle,'Contents/Info.plist');
+ for(const field of ['CFBundleShortVersionString','CFBundleVersion'])await run('/usr/libexec/PlistBuddy',['-c',`Set :${field} 0.1.2`,plist]);
+ await run('/usr/libexec/PlistBuddy',['-c',`Set :ElectronAsarIntegrity:Resources/app.asar:hash ${createHash('sha256').update(asar.getRawHeader(archive).headerString).digest('hex')}`,plist]);
+ await run('/usr/bin/codesign',['--force','--sign','-','--entitlements',path.resolve(__dirname,'../entitlements.mac.plist'),fixture.bundle]);
+ await run('/usr/bin/codesign',['--verify','--deep','--strict',fixture.bundle]);await verifyResources(runtime,manifest.resources);
+}
+async function phase(phase,config){
+ const configFile=path.join(fixture.root,phase+'.json'),driverResult=path.join(fixture.root,phase+'-driver.json');
+ await fs.writeFile(configFile,JSON.stringify({...config,phase,driverResult,fail_first_open:phase==='rollback'}),{mode:0o600});
+ const log=await fs.open(path.join(fixture.root,phase+'.log'),'w');
+ const child=spawn(fixture.executable,[config.driver,configFile],{cwd:fixture.root,env:{HOME:fixture.home,TMPDIR:fixture.root,PATH:'/usr/bin:/bin',LANG:'en_US.UTF-8',ELECTRON_RUN_AS_NODE:'1',PYTHONDONTWRITEBYTECODE:'1'},stdio:['ignore',log.fd,log.fd]});
+ const exit=await new Promise((resolve,reject)=>{child.once('error',reject);child.once('exit',resolve);});await log.close();
+ const driver=JSON.parse(await fs.readFile(driverResult));assert.equal(exit,0,JSON.stringify(driver));assert.equal(driver.authorized,true);assert.ok(driver.handoff?.resultPath);
+ let outcome;for(let n=0;n<600;n++){try{outcome=JSON.parse(await fs.readFile(driver.handoff.resultPath));break;}catch{await delay(500);}}
+ assert.ok(outcome,'Native helper result missing after exact current process exit');
+ return {driver,outcome};
+}
+async function startup(version){
+ const launched=await launch(fixture);application=launched.application;
+ await launched.page.getByRole('heading',{name:'Your projects',exact:true}).waitFor({timeout:90000});
+ const health=await ownedControl(fixture,'health');assert.equal(health.application_version,version);
+ await gracefulQuit(application,launched.page);application=null;
+ return{application_version:health.application_version,owned_wsgi_ready:true,real_electron_started:true,isolated_home_and_profile:true,orderly_shutdown:true};
+}
+async function main(){
+ fixture=await createFixture({reuse:false});
+ const published=path.resolve(__dirname,'../dist/mac-arm64/Rieke OS.app'),zip=path.resolve(__dirname,'../dist/Rieke-OS-0.1.3-arm64.zip');
+ const sourceManifest=await fs.readFile(path.join(published,'Contents/Resources/runtime/runtime-manifest.json'));
+ assert.equal(JSON.parse(sourceManifest).application_version,'0.1.3');receipt.runtime_manifest_sha256=createHash('sha256').update(sourceManifest).digest('hex');
+ receipt.asar_sha256=createHash('sha256').update(await fs.readFile(path.join(published,'Contents/Resources/app.asar'))).digest('hex');
+ const descriptorBytes=await fs.readFile(path.resolve(__dirname,'../dist/desktop-release.json')),descriptor=JSON.parse(descriptorBytes);receipt.archive_sha256=descriptor.archive.sha256;
+ await versionPrior();const priorDigest=await bundleDigest(fixture.bundle);receipt.prior_fixture_bundle_sha256=priorDigest;
+ const tag='desktop-test-v0.1.3',baseURL=`https://github.com/maxwellsdm1867/Rieke-OS/releases/download/${tag}/`;
+ const releases=Buffer.from(JSON.stringify([{draft:false,prerelease:true,tag_name:tag,assets:[{name:'desktop-release.json',size:descriptorBytes.length,browser_download_url:baseURL+'desktop-release.json'},{name:descriptor.archive.filename,size:descriptor.archive.size,browser_download_url:baseURL+descriptor.archive.filename}]}]));
+ server=http.createServer((request,response)=>{
+ const resource=request.url==='/api'?releases:request.url==='/descriptor'?descriptorBytes:null;
+ if(resource){requests[request.url==='/api'?'api':'descriptor']++;response.writeHead(200,{'content-length':resource.length});response.end(resource);}
+ else if(request.url==='/archive'){requests.archive++;response.writeHead(200,{'content-length':descriptor.archive.size});nativeFs.createReadStream(zip).pipe(response);}
+ else{response.writeHead(404);response.end();}
+ });await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
+ const driver=path.join(fixture.root,'driver.cjs'),wrapper=path.join(fixture.root,'owned-open-wrapper.cjs'),openLog=path.join(fixture.root,'open.jsonl');await fs.writeFile(driver,DRIVER);await fs.writeFile(wrapper,WRAPPER);
+ const config={bundle:fixture.bundle,userData:fixture.userData,driver,wrapper,openLog,base:`http://127.0.0.1:${server.address().port}`,descriptorURL:baseURL+'desktop-release.json',archiveURL:baseURL+descriptor.archive.filename};
+ await check('real ZIP updater validates and native helper waits current PID exit before complete 0.1.3 installation',async()=>{
+ const {driver,outcome}=await phase('update',config);assert.equal(outcome.state,'Installed');assert.equal(outcome.version,'0.1.3');
+ assert.equal(await bundleDigest(fixture.bundle),await bundleDigest(published));assert.equal(await bundleDigest(path.join(path.dirname(fixture.bundle),'.Rieke OS.previous.app')),priorDigest);
+ return{current_process_ready_handshake:true,authorized_only_after_helper_ready:true,exact_current_process_exited:true,complete_target_matches_final_bundle:true,previous_complete_bundle_retained:true,statuses:driver.statuses};
+ });
+ await check('actual updated Electron and WSGI start in preserved isolated profile and stop cleanly',()=>startup('0.1.3'));
+ await check('controlled Restore installs only verified retained prior bundle without archive download',async()=>{
+ const before=requests.archive,{outcome}=await phase('restore',config);assert.equal(outcome.state,'Restored');assert.equal(outcome.version,'0.1.2');assert.equal(await bundleDigest(fixture.bundle),priorDigest);assert.equal(requests.archive,before);
+ return{verified_previous_restored:true,archive_download_not_required:true,profile_preserved:true};
+ });
+ await check('macOS launch refusal triggers complete previous-app rollback with profile unchanged',async()=>{
+ const {outcome}=await phase('rollback',config);assert.equal(outcome.state,'Restored');assert.equal(outcome.version,'0.1.2');assert.equal(await bundleDigest(fixture.bundle),priorDigest);
+ return{candidate_launch_refusal_simulated_at_only_open_boundary:true,exact_previous_restored:true,failed_candidate_retained:true};
+ });
+ await check('restored Electron and WSGI still start and stop without dependency installation',()=>startup('0.1.2'));
+ const opens=(await fs.readFile(openLog,'utf8')).trim().split('\n').map(line=>JSON.parse(line));
+ assert.ok(opens.every(entry=>entry.args.includes(fixture.bundle)&&entry.args.includes(`--user-data-dir=${fixture.userData}`)));receipt.open_boundary_calls=opens.length;receipt.profile_continuity_verified=true;
+ assert.equal(createHash('sha256').update(await fs.readFile(path.join(published,'Contents/Resources/runtime/runtime-manifest.json'))).digest('hex'),receipt.runtime_manifest_sha256);
+ assert.equal(createHash('sha256').update(await fs.readFile(path.join(published,'Contents/Resources/app.asar'))).digest('hex'),receipt.asar_sha256);
+ receipt.passed=true;await write();await new Promise(resolve=>server.close(resolve));server=null;await fs.rm(fixture.root,{recursive:true,force:true});console.log('Native testing upgrade receipt: '+output);
+}
+main().catch(async error=>{console.error(error.stack);receipt.failures.push({name:'native-testing-upgrade-suite',message:error.message});receipt.passed=false;await write();server?.close();if(fixture)console.error('Owned diagnostics retained: '+fixture.root);process.exitCode=1;});
diff --git a/desktop/e2e/toolbar-preview.cjs b/desktop/e2e/toolbar-preview.cjs
new file mode 100644
index 0000000..7a113b9
--- /dev/null
+++ b/desktop/e2e/toolbar-preview.cjs
@@ -0,0 +1,50 @@
+'use strict';
+const assert = require('node:assert/strict');
+const fs = require('node:fs/promises'), path = require('node:path');
+const {createHash} = require('node:crypto');
+const {createFixture, launch, gracefulQuit} = require('./helpers.cjs');
+const {verifyResources} = require('../updater-validation.cjs');
+const output = path.resolve(__dirname, '../../docs/dev/desktop-ui-e2e');
+(async () => {
+ const fixture = await createFixture(); let application, page;
+ const manifestBytes = await fs.readFile(path.join(fixture.bundle, 'Contents/Resources/runtime/runtime-manifest.json'));
+ const manifest = JSON.parse(manifestBytes);
+ try {
+ ({application,page} = await launch(fixture));
+ await page.getByRole('heading', {name:'Your projects', exact:true}).waitFor({timeout:90000});
+ const previous = JSON.parse(await fs.readFile(path.join(output, 'before-toolbar-fit-fix.json')));
+ await page.getByRole('button', {name:new RegExp('^Open Electron lifecycle fixture ' + previous.run_id + ',')}).click();
+ await page.getByRole('button', {name:'Project overview',exact:true}).waitFor({timeout:90000});
+ const profile = page.getByRole('dialog', {name:'Tag author',exact:true});
+ if(await profile.waitFor({state:'visible',timeout:3000}).then(()=>true).catch(()=>false)) {
+ await profile.getByRole('button', {name:'Electron E2E',exact:true}).first().click(); await profile.waitFor({state:'hidden'});
+ }
+ const source = await fs.readFile(path.resolve(__dirname,'../../workspace-app/src/styles.css'),'utf8');
+ const marker = '/* Keep the app mark visible as the scientific toolbar gets narrower. */';
+ assert.ok(source.includes(marker)); const css = source.slice(source.indexOf(marker));
+ // The packaged CSP explicitly allows inline styles. This preview leaves
+ // that policy, the app bundle and all backend resources unchanged.
+ assert.ok(await page.evaluate(css => {const style=document.createElement('style');style.textContent=css;document.head.append(style);return style.sheet?.cssRules.length>0;},css));
+ const measurements = [];
+ for(const width of [1440,960]) {
+ await application.evaluate(({BrowserWindow},width)=>BrowserWindow.getAllWindows()[0].setContentSize(width,900),width);
+ await page.waitForFunction(width=>innerWidth===width,width);
+ const geometry = await page.evaluate(()=>{
+ const icon=document.querySelector('.header-app-icon'),header=document.querySelector('.app-header');
+ const rect=node=>{const r=node.getBoundingClientRect();return {left:r.left,right:r.right,top:r.top,bottom:r.bottom};};
+ return {width:innerWidth,naturalWidth:icon.naturalWidth,icon:rect(icon),header:rect(header),controls:[...header.children]
+ .filter(node=>node!==icon&&!node.classList.contains('spacer')&&node.getClientRects().length).map(node=>({className:node.className,...rect(node)}))};
+ });
+ await page.screenshot({path:path.join(output,`toolbar-preview-${width}.png`)});
+ assert.ok(geometry.naturalWidth>0); assert.ok(geometry.icon.right<=geometry.header.right&&geometry.icon.right<=width);
+ for(const c of geometry.controls) assert.ok(!(c.leftgeometry.icon.left&&c.topgeometry.icon.top));
+ measurements.push(geometry);
+ }
+ await gracefulQuit(application,page); application=null;page=null;
+ await verifyResources(path.join(fixture.bundle,'Contents/Resources/runtime'),manifest.resources);
+ const receipt={format:'rieke-toolbar-css-preview',version:1,passed:true,final_packaged_qualification:false,
+ method:'Exact prior disposable packaged fixture; DOM-only inline CSS allowed by existing CSP; no bundle or backend mutation',
+ source_manifest_sha256:createHash('sha256').update(manifestBytes).digest('hex'),preview_css_sha256:createHash('sha256').update(css).digest('hex'),measurements};
+ await fs.writeFile(path.join(output,'toolbar-preview.json'),JSON.stringify(receipt,null,2)+'\n');console.log(JSON.stringify(receipt));
+ } finally {if(application&&page) await gracefulQuit(application,page);}
+})().catch(error=>{console.error(error.message);process.exitCode=1;});
diff --git a/desktop/entitlements.mac.plist b/desktop/entitlements.mac.plist
new file mode 100644
index 0000000..bda591d
--- /dev/null
+++ b/desktop/entitlements.mac.plist
@@ -0,0 +1,5 @@
+
+
+
+com.apple.security.cs.allow-jit
+
diff --git a/desktop/icon.png b/desktop/icon.png
new file mode 100644
index 0000000..6685dbf
Binary files /dev/null and b/desktop/icon.png differ
diff --git a/desktop/main.cjs b/desktop/main.cjs
new file mode 100644
index 0000000..d341725
--- /dev/null
+++ b/desktop/main.cjs
@@ -0,0 +1,222 @@
+'use strict';
+const {app, BrowserWindow, ipcMain, dialog, session, shell, Menu, powerMonitor} = require('electron');
+const path = require('node:path');
+const os = require('node:os');
+const {execFile} = require('node:child_process');
+const {promisify} = require('node:util');
+const {ServiceSupervisor, matchesHealth} = require('./supervisor.cjs');
+const {validateSender, approvedReleaseURL, isOwnedURL} = require('./security.cjs');
+const {enclosingApp, installCompleteBundle} = require('./bootstrap.cjs');
+const {DraftBarrier} = require('./draft-barrier.cjs');
+const {DraftStore} = require('./draft-store.cjs');
+const {distributionPolicy} = require('./distribution.cjs');
+const distribution = distributionPolicy(require('./distribution.json'));
+app.enableSandbox();
+app.setName('Rieke OS');
+const windows = new Set();
+const scientificWindows = new Set();
+const recoveryPage = path.join(__dirname, 'recovery.html');
+const sourceApp = enclosingApp(process.execPath);
+const destination = path.join(os.homedir(), 'Applications', 'Rieke OS.app');
+const bootstrap = app.isPackaged && process.platform === 'darwin' && sourceApp !== destination;
+const installedUserData = app.getPath('userData');
+if (bootstrap) {
+ // A downloaded installer must be able to explain why a running installed app
+ // blocks replacement instead of silently forwarding its launch to that app.
+ const installerState = path.join(app.getPath('userData'), 'installer');
+ require('node:fs').mkdirSync(installerState, {recursive:true,mode:0o700});
+ app.setPath('userData', installerState);
+}
+let supervisor, coordinator, quitAuthorized = false, quitInProgress = false, startupInProgress = false;
+let draftStore;
+let lifecycleStatus = {state: 'Starting', title: 'Starting Rieke OS', message: 'Verifying the complete app and its private runtime.'};
+const draftBarrier = new DraftBarrier();
+function broadcast(value) {
+ for (const window of windows) if (!window.isDestroyed()) window.webContents.send('desktop:status-changed', value);
+}
+function status() { return lifecycleStatus.state === 'Running' ? (coordinator?.getStatus() || {state: 'Current', installed_version: app.getVersion()}) : lifecycleStatus; }
+function recovery(message, detail = '') {
+ lifecycleStatus = {state: 'Recovery', channel:distribution.channel, title: 'Rieke OS recovery', message, detail}; broadcast(lifecycleStatus);
+ for (const window of windows) if (!window.isDestroyed()) window.loadFile(recoveryPage).catch(() => {});
+}
+async function acknowledgeDrafts() {
+ return draftBarrier.prepare(scientificWindows);
+}
+async function prepareQuit() {
+ const drafts = await acknowledgeDrafts(); if (!drafts.ready) return drafts;
+ return supervisor ? supervisor.drain() : {ready: true};
+}
+async function orderlyQuit() {
+ if (quitInProgress) return {ready: false, reason: 'Quit preparation is already in progress'};
+ quitInProgress = true;
+ try {
+ if (distribution.channel !== 'unsigned-testing' && coordinator?.getStatus().state === 'Ready') {
+ const result = await coordinator.installPrepared();
+ if (result.installing) return result;
+ if (!result.ready) { broadcast({...status(), message: result.reason}); return result; }
+ }
+ const result = await prepareQuit();
+ if (!result.ready) { broadcast({...status(), message: result.reason}); return result; }
+ quitAuthorized = true; coordinator?.stop(); app.quit(); return result;
+ } finally { quitInProgress = false; }
+}
+function createWindow() {
+ const window = new BrowserWindow({width: 1440, height: 960, minWidth: 960, minHeight: 650,
+ title: 'Rieke OS', backgroundColor: '#f4f5f3', show: false,
+ webPreferences: {preload: path.join(__dirname, 'preload.cjs'), contextIsolation: true,
+ sandbox: true, nodeIntegration: false, nodeIntegrationInWorker: false,
+ webSecurity: true, allowRunningInsecureContent: false, webviewTag: false, spellcheck: false,
+ partition: 'rieke-desktop'}});
+ windows.add(window);
+ window.once('ready-to-show', () => window.show());
+ window.webContents.setWindowOpenHandler(() => ({action: 'deny'}));
+ window.webContents.on('will-attach-webview', event => event.preventDefault());
+ window.webContents.on('will-navigate', (event, url) => {
+ if (isOwnedURL(url, null, [recoveryPage])) return;
+ event.preventDefault();
+ if (supervisor?.ready) supervisor.authorizeProjectURL(url).then(allowed => {
+ if (allowed && !window.isDestroyed()) window.loadURL(url);
+ }).catch(() => recovery('A project service failed ownership verification.'));
+ });
+ window.webContents.on('will-redirect', (event, url) => {
+ if (!isOwnedURL(url, supervisor?.origins, [recoveryPage])) event.preventDefault();
+ });
+ window.webContents.on('render-process-gone', () => { window.draftUnavailable = scientificWindows.has(window); recovery('The scientific window stopped. Restore the window to acknowledge drafts before shutdown.'); });
+ window.on('close', event => { if (!quitAuthorized) { event.preventDefault(); void orderlyQuit(); } });
+ window.on('closed', () => { windows.delete(window); scientificWindows.delete(window); });
+ window.loadFile(recoveryPage); return window;
+}
+function configureSession() {
+ const ownedSession = session.fromPartition('rieke-desktop');
+ ownedSession.setPermissionRequestHandler((_contents, _permission, callback) => callback(false));
+ ownedSession.setPermissionCheckHandler(() => false);
+ ownedSession.webRequest.onBeforeRequest((details, callback) => {
+ const owned = [...windows].some(win => !win.isDestroyed() && win.webContents.id === details.webContentsId);
+ const localAsset = details.url.startsWith('file:') && (() => {
+ try { return require('node:url').fileURLToPath(details.url).startsWith(__dirname + path.sep); } catch { return false; }
+ })();
+ callback({cancel: !owned || (!localAsset && !isOwnedURL(details.url, supervisor?.origins))});
+ });
+ ownedSession.webRequest.onBeforeSendHeaders((details, callback) => {
+ const headers = {...details.requestHeaders};
+ for (const key of Object.keys(headers)) if (['x-rieke-desktop-capability', 'x-rieke-desktop-session'].includes(key.toLowerCase())) delete headers[key];
+ if (supervisor && isOwnedURL(details.url, supervisor.origins) && [...windows].some(win => !win.isDestroyed() && win.webContents.id === details.webContentsId))
+ headers['X-Rieke-Desktop-Session'] = supervisor.rendererCapability;
+ callback({requestHeaders: headers});
+ });
+ ownedSession.webRequest.onHeadersReceived((details, callback) => {
+ const headers = {...details.responseHeaders};
+ if (isOwnedURL(details.url, supervisor?.origins)) {
+ headers['Content-Security-Policy'] = ["default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'; font-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'"];
+ headers['X-Content-Type-Options'] = ['nosniff'];
+ }
+ callback({responseHeaders: headers});
+ });
+ ownedSession.on('will-download', (event, item, contents) => {
+ if (![...windows].some(win => win.webContents === contents) || !isOwnedURL(item.getURL(), supervisor?.origins)) { event.preventDefault(); return; }
+ const filename = path.basename(item.getFilename()).replace(/[\x00-\x1f]/g, '_');
+ item.setSaveDialogOptions({title: 'Save export', defaultPath: filename});
+ });
+}
+async function startScientificUI() {
+ if (startupInProgress) return;
+ startupInProgress = true;
+ try {
+ let origin;
+ if (supervisor.child && !supervisor.exited) {
+ const health = await supervisor.api('/api/desktop/health');
+ if (!matchesHealth(health, supervisor.expectedHealth())) throw new Error('Existing backend is not ready for recovery');
+ origin = supervisor.origin; supervisor.ready = true; supervisor.origins.add(origin);
+ } else origin = await supervisor.start();
+ if (!coordinator) {
+ const createUpdateCoordinator = distribution.channel === 'unsigned-testing'
+ ? require('./testing-updater.cjs').createTestingUpdateCoordinator
+ : require('./updater.cjs').createUpdateCoordinator;
+ coordinator = createUpdateCoordinator({app, publishStatus: broadcast, prepareQuit,
+ authorizeQuit: () => { quitAuthorized = true; }, revokeQuit: () => { quitAuthorized = false; },
+ onInstallationFailure: () => { scientificWindows.clear(); recovery('Native update installation failed. Restart the installed backend or restore the verified previous app.'); },
+ manifest: supervisor.manifest, distribution});
+ coordinator.start().catch(() => broadcast({state:'Deferred',channel:distribution.channel,message:'Update check unavailable. The installed app remains usable.'}));
+ }
+ lifecycleStatus = {state: 'Running'};
+ for (const window of windows) {
+ await window.loadURL(origin); window.draftUnavailable = false; scientificWindows.add(window);
+ }
+ broadcast(status());
+ } catch (error) { recovery('The packaged scientific backend could not start. Projects have not been opened.', error.message); }
+ finally { startupInProgress = false; }
+}
+function registerIPC() {
+ const handle = (channel, action) => ipcMain.handle(channel, async (event, payload) => {
+ const window = validateSender(event, windows, supervisor?.origins, [recoveryPage]); return action(payload, window);
+ });
+ const noPayload = (channel, action) => handle(channel, (payload, window) => {
+ if (payload !== undefined) throw new TypeError('This desktop operation accepts no payload');
+ return action(window);
+ });
+ noPayload('desktop:status', () => status());
+ noPayload('desktop:check-updates', () => coordinator ? coordinator.check() : status());
+ noPayload('desktop:download-update', () => {
+ if (!coordinator?.download) throw new Error('Update download is unavailable for this installation.');
+ return coordinator.download();
+ });
+ noPayload('desktop:restart-to-update', () => {
+ if (!coordinator) throw new Error('No prepared update is available.');
+ return coordinator.installPrepared();
+ });
+ noPayload('desktop:quit', () => orderlyQuit());
+ noPayload('desktop:retry-startup', () => { if (bootstrap) throw new Error('Install the complete app before starting projects'); return startScientificUI(); });
+ handle('desktop:restore-previous', async payload => {
+ if (payload !== undefined || lifecycleStatus.state !== 'Recovery') throw new Error('Verified restoration is available only from recovery');
+ const restorePriorBundle = distribution.channel === 'unsigned-testing'
+ ? require('./testing-install.cjs').restoreTestingPriorBundle
+ : require('./update-recovery.cjs').restorePriorBundle;
+ const manifest = supervisor.manifest || await supervisor.loadManifest();
+ return restorePriorBundle({app, prepareQuit, authorizeQuit: () => { quitAuthorized = true; }, manifest});
+ });
+ handle('desktop:drafts-ack', (payload, window) => {
+ return draftBarrier.acknowledge(payload, window);
+ });
+ handle('desktop:save-draft', async payload => {
+ return draftStore.save(payload);
+ });
+ handle('desktop:load-draft', async projectId => {
+ return draftStore.load(projectId);
+ });
+ handle('desktop:reset-draft', projectId => draftStore.reset(projectId));
+ noPayload('desktop:choose-project-folder', async window => {
+ const result = await dialog.showOpenDialog(window, {properties: ['openDirectory', 'createDirectory'], title: 'Choose project folder'});
+ return result.canceled ? null : result.filePaths[0];
+ });
+ handle('desktop:open-release-notes', async url => {
+ if (!approvedReleaseURL(url)) throw new TypeError('Only official HTTPS release notes may be opened');
+ await shell.openExternal(url); return {opened: true};
+ });
+ noPayload('desktop:install-and-open', async () => {
+ if (!bootstrap || supervisor?.child) throw new Error('Install action is available only before backend startup');
+ const result = await installCompleteBundle({source: sourceApp, destination, distribution});
+ app.releaseSingleInstanceLock();
+ await promisify(execFile)('/usr/bin/open', ['-n', '-a', result.destination,
+ '--env', `HOME=${os.homedir()}`, '--args', `--user-data-dir=${installedUserData}`]);
+ quitAuthorized = true; app.quit(); return {installed: true};
+ });
+}
+if (!app.requestSingleInstanceLock()) app.quit();
+else {
+ app.on('second-instance', () => { const window = [...windows][0]; if (window) { if (window.isMinimized()) window.restore(); window.show(); window.focus(); } });
+ app.on('before-quit', event => { if (!quitAuthorized) { event.preventDefault(); void orderlyQuit(); } });
+ app.on('window-all-closed', () => { if (quitAuthorized) app.quit(); });
+ app.whenReady().then(async () => {
+ draftStore = new DraftStore(app.getPath('userData'));
+ supervisor = new ServiceSupervisor({resourcesPath: app.isPackaged ? process.resourcesPath : path.join(__dirname, 'build'),
+ userData: app.getPath('userData'), appVersion: app.getVersion(), onFailure: recovery});
+ configureSession(); registerIPC(); createWindow();
+ powerMonitor.on('shutdown', event => { if (!quitAuthorized) { event.preventDefault(); void orderlyQuit(); } });
+ Menu.setApplicationMenu(Menu.buildFromTemplate([{label: 'Rieke OS', submenu: [{role: 'about'}, {type: 'separator'},
+ {label: 'Check for Updates', click: () => coordinator?.check()}, {type: 'separator'}, {label: 'Quit Rieke OS', accelerator: 'CommandOrControl+Q', click: () => orderlyQuit()}]},
+ {label: 'Edit', submenu: [{role: 'undo'}, {role: 'redo'}, {type: 'separator'}, {role: 'cut'}, {role: 'copy'}, {role: 'paste'}, {role: 'selectAll'}]},
+ {label: 'Window', submenu: [{role: 'minimize'}, {role: 'zoom'}]}]));
+ if (bootstrap) { lifecycleStatus = {state: 'Bootstrap', channel:distribution.channel, title: 'Install Rieke OS', message: 'Install this complete app in your Applications folder and open it.', detail: distribution.channel === 'unsigned-testing' ? 'Unsigned testing release. Install a copy downloaded from the official Rieke OS GitHub release. macOS may require a one-time Open Anyway approval in Privacy & Security. Existing projects stay in their selected folders.' : 'The downloaded app and installed copy must pass Developer ID signature verification. Existing projects stay in their selected folders.'}; broadcast(lifecycleStatus); }
+ else await startScientificUI();
+ }).catch(error => { console.error('Desktop startup failed:', error.name); app.exit(1); });
+}
diff --git a/desktop/native-license-sources.json b/desktop/native-license-sources.json
new file mode 100644
index 0000000..514afc2
--- /dev/null
+++ b/desktop/native-license-sources.json
@@ -0,0 +1,42 @@
+{
+ "format": "rieke-native-license-sources",
+ "version": 1,
+ "sources": [
+ {
+ "package": "mysql-server",
+ "applies_to": ["mysql-server", "mysql-client", "mysql-common"],
+ "version": "8.4.2",
+ "url": "https://raw.githubusercontent.com/mysql/mysql-server/mysql-8.4.2/LICENSE",
+ "file": "mysql-8.4.2-LICENSE.txt",
+ "sha256": "2717175c705374f2a95d4dba0f99382e5a4a0190d3de769626c72b9351b18132"
+ },
+ {
+ "package": "libabseil",
+ "version": "20240116.2",
+ "url": "https://raw.githubusercontent.com/abseil/abseil-cpp/20240116.2/LICENSE",
+ "file": "abseil-20240116.2-LICENSE.txt",
+ "sha256": "c79a7fea0e3cac04cd43f20e7b648e5a0ff8fa5344e644b0ee09ca1162b62747"
+ },
+ {
+ "package": "libprotobuf",
+ "version": "4.25.3",
+ "url": "https://raw.githubusercontent.com/protocolbuffers/protobuf/v25.3/LICENSE",
+ "file": "protobuf-25.3-LICENSE.txt",
+ "sha256": "6e5e117324afd944dcf67f36cf329843bc1a92229a8cd9bb573d7a83130fea7d"
+ },
+ {
+ "package": "lz4-c",
+ "version": "1.9.4",
+ "url": "https://raw.githubusercontent.com/lz4/lz4/v1.9.4/lib/LICENSE",
+ "file": "lz4-1.9.4-library-LICENSE.txt",
+ "sha256": "8b58c446121a109ccf32edc094bba3010a3d85e4ee3702950db55e4d3e87736c"
+ },
+ {
+ "package": "lz4-c",
+ "version": "1.9.4",
+ "url": "https://raw.githubusercontent.com/lz4/lz4/v1.9.4/programs/COPYING",
+ "file": "lz4-1.9.4-programs-COPYING.txt",
+ "sha256": "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643"
+ }
+ ]
+}
diff --git a/desktop/package-lock.json b/desktop/package-lock.json
new file mode 100644
index 0000000..f1ddf78
--- /dev/null
+++ b/desktop/package-lock.json
@@ -0,0 +1,3659 @@
+{
+ "name": "rieke-os-desktop",
+ "version": "0.1.3",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "rieke-os-desktop",
+ "version": "0.1.3",
+ "dependencies": {
+ "electron-updater": "6.8.9"
+ },
+ "devDependencies": {
+ "electron": "44.5.0",
+ "electron-builder": "26.15.3",
+ "playwright": "1.63.0"
+ }
+ },
+ "node_modules/@electron-internal/extract-zip": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz",
+ "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
+ "node_modules/@electron/asar": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
+ "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^5.0.0",
+ "glob": "^7.1.6",
+ "minimatch": "^3.0.4"
+ },
+ "bin": {
+ "asar": "bin/asar.js"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/@electron/asar/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@electron/asar/node_modules/brace-expansion": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
+ "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@electron/asar/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@electron/fuses": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz",
+ "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.1",
+ "fs-extra": "^9.0.1",
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "electron-fuses": "dist/bin.js"
+ }
+ },
+ "node_modules/@electron/fuses/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@electron/get": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz",
+ "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^3.0.0",
+ "graceful-fs": "^4.2.11",
+ "progress": "^2.0.3",
+ "semver": "^7.6.3",
+ "sumchecker": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "optionalDependencies": {
+ "undici": "^7.24.4"
+ }
+ },
+ "node_modules/@electron/notarize": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz",
+ "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.1",
+ "promise-retry": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@electron/osx-sign": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz",
+ "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "compare-version": "^0.1.2",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.0.0",
+ "isbinaryfile": "^4.0.8",
+ "minimist": "^1.2.6",
+ "plist": "^3.0.5"
+ },
+ "bin": {
+ "electron-osx-flat": "bin/electron-osx-flat.js",
+ "electron-osx-sign": "bin/electron-osx-sign.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/isbinaryfile": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
+ "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/@electron/rebuild": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz",
+ "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@malept/cross-spawn-promise": "^2.0.0",
+ "debug": "^4.1.1",
+ "node-abi": "^4.2.0",
+ "node-api-version": "^0.2.1",
+ "node-gyp": "^12.2.0",
+ "read-binary-file-arch": "^1.0.6"
+ },
+ "bin": {
+ "electron-rebuild": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
+ "node_modules/@electron/universal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz",
+ "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/asar": "^3.3.1",
+ "@malept/cross-spawn-promise": "^2.0.0",
+ "debug": "^4.3.1",
+ "dir-compare": "^4.2.0",
+ "fs-extra": "^11.1.1",
+ "minimatch": "^9.0.3",
+ "plist": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=16.4"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@electron/universal/node_modules/brace-expansion": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz",
+ "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/fs-extra": {
+ "version": "11.4.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz",
+ "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@electron/windows-sign": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
+ "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "dependencies": {
+ "cross-dirname": "^0.1.0",
+ "debug": "^4.3.4",
+ "fs-extra": "^11.1.1",
+ "minimist": "^1.2.8",
+ "postject": "^1.0.0-alpha.6"
+ },
+ "bin": {
+ "electron-windows-sign": "bin/electron-windows-sign.js"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/@electron/windows-sign/node_modules/fs-extra": {
+ "version": "11.4.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz",
+ "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@malept/cross-spawn-promise": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
+ "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/malept"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
+ }
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "engines": {
+ "node": ">= 12.13.0"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz",
+ "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.0",
+ "lodash": "^4.17.15",
+ "tmp-promise": "^3.0.2"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz",
+ "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@peculiar/asn1-schema": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.10.0.tgz",
+ "integrity": "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/utils": "^2.0.2",
+ "asn1js": "^3.0.10",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@peculiar/json-schema": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz",
+ "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@peculiar/utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz",
+ "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/webcrypto": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz",
+ "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/json-schema": "^1.1.12",
+ "@peculiar/utils": "^2.0.2",
+ "tslib": "^2.8.1",
+ "webcrypto-core": "^1.9.2"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+ "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+ "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defer-to-connect": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@types/cacheable-request": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+ "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "*",
+ "@types/keyv": "^3.1.4",
+ "@types/node": "*",
+ "@types/responselike": "^1.0.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/fs-extra": {
+ "version": "9.0.13",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
+ "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+ "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/keyv": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+ "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.19.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.0.tgz",
+ "integrity": "sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": ">=7.24.0 <7.24.7"
+ }
+ },
+ "node_modules/@types/responselike": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
+ "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.15",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz",
+ "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/abbrev": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz",
+ "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/app-builder-lib": {
+ "version": "26.15.3",
+ "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz",
+ "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/asar": "3.4.1",
+ "@electron/fuses": "^1.8.0",
+ "@electron/get": "^3.0.0",
+ "@electron/notarize": "2.5.0",
+ "@electron/osx-sign": "1.3.3",
+ "@electron/rebuild": "^4.0.4",
+ "@electron/universal": "2.0.3",
+ "@malept/flatpak-bundler": "^0.4.0",
+ "@noble/hashes": "^2.2.0",
+ "@peculiar/webcrypto": "^1.7.1",
+ "@types/fs-extra": "9.0.13",
+ "ajv": "^8.18.0",
+ "asn1js": "^3.0.10",
+ "async-exit-hook": "^2.0.1",
+ "builder-util": "26.15.3",
+ "builder-util-runtime": "9.7.0",
+ "chromium-pickle-js": "^0.2.0",
+ "ci-info": "4.3.1",
+ "debug": "^4.3.4",
+ "dotenv": "^16.4.5",
+ "dotenv-expand": "^11.0.6",
+ "ejs": "^3.1.8",
+ "electron-publish": "26.15.3",
+ "fs-extra": "^10.1.0",
+ "hosted-git-info": "^4.1.0",
+ "isbinaryfile": "^5.0.0",
+ "jiti": "^2.4.2",
+ "js-yaml": "^4.1.0",
+ "json5": "^2.2.3",
+ "lazy-val": "^1.0.5",
+ "minimatch": "^10.2.5",
+ "pkijs": "^3.4.0",
+ "plist": "3.1.0",
+ "proper-lockfile": "^4.1.2",
+ "resedit": "^1.7.0",
+ "semver": "~7.7.3",
+ "tar": "^7.5.7",
+ "temp-file": "^3.4.0",
+ "tiny-async-pool": "1.3.0",
+ "unzipper": "^0.12.3",
+ "which": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "dmg-builder": "26.15.3",
+ "electron-builder-squirrel-windows": "26.15.3"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/@electron/get": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz",
+ "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^8.1.0",
+ "got": "^11.8.5",
+ "progress": "^2.0.3",
+ "semver": "^6.2.0",
+ "sumchecker": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "optionalDependencies": {
+ "global-agent": "^3.0.0"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/ci-info": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
+ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/asn1js": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz",
+ "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "pvtsutils": "^1.3.6",
+ "pvutils": "^1.1.5",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async-exit-hook": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz",
+ "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/aws4": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
+ "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/boolean": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
+ "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/builder-util": {
+ "version": "26.15.3",
+ "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz",
+ "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.1.6",
+ "builder-util-runtime": "9.7.0",
+ "chalk": "^4.1.2",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.1.0",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.0",
+ "js-yaml": "^4.1.0",
+ "sanitize-filename": "^1.6.3",
+ "source-map-support": "^0.5.19",
+ "stat-mode": "^1.0.0",
+ "temp-file": "^3.4.0",
+ "tiny-async-pool": "1.3.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/builder-util-runtime": {
+ "version": "9.7.0",
+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz",
+ "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "sax": "^1.2.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/bytestreamjs": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
+ "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+ "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.6.0"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+ "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^4.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^6.0.1",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chromium-pickle-js": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz",
+ "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ci-info": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
+ "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/clone-response": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+ "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/compare-version": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
+ "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-dirname": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz",
+ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cross-spawn/node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/cross-spawn/node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/dir-compare": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz",
+ "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimatch": "^3.0.5",
+ "p-limit": "^3.1.0 "
+ }
+ },
+ "node_modules/dir-compare/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/dir-compare/node_modules/brace-expansion": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
+ "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/dir-compare/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dmg-builder": {
+ "version": "26.15.3",
+ "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz",
+ "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "app-builder-lib": "26.15.3",
+ "builder-util": "26.15.3",
+ "fs-extra": "^10.1.0",
+ "js-yaml": "^4.1.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "11.0.7",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
+ "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dotenv": "^16.4.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/duplexer2": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/electron": {
+ "version": "44.5.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-44.5.0.tgz",
+ "integrity": "sha512-gVUJ6DZcRY80Alh/krc7uwjjCfxjZ3T2suvpBmHsN6zE4ShAc4vzG14kbMgT+ZteaPLddoKg4Pie0CjDdIDdyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron-internal/extract-zip": "^1.0.1",
+ "@electron/get": "^5.0.0",
+ "@types/node": "^24.9.0"
+ },
+ "bin": {
+ "electron": "cli.js",
+ "install-electron": "install.js"
+ },
+ "engines": {
+ "node": ">= 22.12.0"
+ }
+ },
+ "node_modules/electron-builder": {
+ "version": "26.15.3",
+ "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz",
+ "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-builder-lib": "26.15.3",
+ "builder-util": "26.15.3",
+ "builder-util-runtime": "9.7.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "dmg-builder": "26.15.3",
+ "fs-extra": "^10.1.0",
+ "lazy-val": "^1.0.5",
+ "simple-update-notifier": "2.0.0",
+ "yargs": "^17.6.2"
+ },
+ "bin": {
+ "electron-builder": "cli.js",
+ "install-app-deps": "install-app-deps.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows": {
+ "version": "26.15.3",
+ "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz",
+ "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "app-builder-lib": "26.15.3",
+ "builder-util": "26.15.3",
+ "electron-winstaller": "5.4.0"
+ }
+ },
+ "node_modules/electron-publish": {
+ "version": "26.15.3",
+ "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz",
+ "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/fs-extra": "^9.0.11",
+ "aws4": "^1.13.2",
+ "builder-util": "26.15.3",
+ "builder-util-runtime": "9.7.0",
+ "chalk": "^4.1.2",
+ "form-data": "^4.0.5",
+ "fs-extra": "^10.1.0",
+ "lazy-val": "^1.0.5",
+ "mime": "^2.5.2"
+ }
+ },
+ "node_modules/electron-updater": {
+ "version": "6.8.9",
+ "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz",
+ "integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==",
+ "license": "MIT",
+ "dependencies": {
+ "builder-util-runtime": "9.7.0",
+ "fs-extra": "^10.1.0",
+ "js-yaml": "^4.1.0",
+ "lazy-val": "^1.0.5",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.isequal": "^4.5.0",
+ "semver": "~7.7.3",
+ "tiny-typed-emitter": "^2.1.0"
+ }
+ },
+ "node_modules/electron-updater/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/electron-winstaller": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
+ "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/asar": "^3.2.1",
+ "debug": "^4.1.1",
+ "fs-extra": "^7.0.1",
+ "lodash": "^4.17.21",
+ "temp": "^0.9.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@electron/windows-sign": "^1.1.2"
+ }
+ },
+ "node_modules/electron-winstaller/node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/electron-winstaller/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-winstaller/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/err-code": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/exponential-backoff": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
+ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/filelist": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
+ "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filelist/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/filelist/node_modules/brace-expansion": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz",
+ "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/filelist/node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
+ "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/global-agent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "es6-error": "^4.1.1",
+ "matcher": "^3.0.0",
+ "roarr": "^2.15.3",
+ "semver": "^7.3.2",
+ "serialize-error": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "11.8.6",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+ "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/http2-wrapper": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+ "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isbinaryfile": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
+ "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
+ "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/jake": {
+ "version": "10.9.4",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+ "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.6",
+ "filelist": "^1.0.4",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
+ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/lazy-val": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
+ "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
+ "license": "MIT"
+ },
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
+ "license": "MIT"
+ },
+ "node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/matcher": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.8"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/node-abi": {
+ "version": "4.35.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz",
+ "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
+ "node_modules/node-api-version": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz",
+ "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ }
+ },
+ "node_modules/node-gyp": {
+ "version": "12.4.0",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz",
+ "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.0",
+ "exponential-backoff": "^3.1.1",
+ "graceful-fs": "^4.2.6",
+ "nopt": "^9.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.3.5",
+ "tar": "^7.5.4",
+ "tinyglobby": "^0.2.12",
+ "undici": "^6.25.0",
+ "which": "^6.0.0"
+ },
+ "bin": {
+ "node-gyp": "bin/node-gyp.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/node-gyp/node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/node-gyp/node_modules/isexe": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz",
+ "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/node-gyp/node_modules/undici": {
+ "version": "6.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz",
+ "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.17"
+ }
+ },
+ "node_modules/node-gyp/node_modules/which": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz",
+ "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^4.0.0"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nopt": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz",
+ "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "^4.0.0"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-cancelable": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+ "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pe-library": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
+ "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jet2jet"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkijs": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz",
+ "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "workspaces": [
+ "website"
+ ],
+ "dependencies": {
+ "@noble/hashes": "1.8.0",
+ "asn1js": "^3.0.6",
+ "bytestreamjs": "^2.0.1",
+ "pvtsutils": "^1.3.6",
+ "pvutils": "^1.1.3",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/pkijs/node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
+ "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
+ "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/plist": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
+ "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
+ },
+ "engines": {
+ "node": ">=10.4.0"
+ }
+ },
+ "node_modules/postject": {
+ "version": "1.0.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz",
+ "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "commander": "^9.4.0"
+ },
+ "bin": {
+ "postject": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/postject/node_modules/commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": "^12.20.0 || >=14"
+ }
+ },
+ "node_modules/proc-log": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/promise-retry": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/pvtsutils": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
+ "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/pvutils": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz",
+ "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-binary-file-arch": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
+ "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "bin": {
+ "read-binary-file-arch": "cli.js"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resedit": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz",
+ "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pe-library": "^0.4.1"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jet2jet"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/responselike": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+ "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lowercase-keys": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/roarr": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
+ "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "detect-node": "^2.0.4",
+ "globalthis": "^1.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "semver-compare": "^1.0.0",
+ "sprintf-js": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sanitize-filename": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz",
+ "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==",
+ "dev": true,
+ "license": "WTFPL OR ISC",
+ "dependencies": {
+ "truncate-utf8-bytes": "^1.0.0"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
+ "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/serialize-error": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
+ "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "type-fest": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/stat-mode": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz",
+ "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sumchecker": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
+ "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.5.22",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz",
+ "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/temp": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
+ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mkdirp": "^0.5.1",
+ "rimraf": "~2.6.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/temp-file": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz",
+ "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-exit-hook": "^2.0.1",
+ "fs-extra": "^10.0.0"
+ }
+ },
+ "node_modules/tiny-async-pool": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz",
+ "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^5.5.0"
+ }
+ },
+ "node_modules/tiny-async-pool/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/tiny-typed-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
+ "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tmp": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz",
+ "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/tmp-promise": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz",
+ "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tmp": "^0.2.0"
+ }
+ },
+ "node_modules/truncate-utf8-bytes": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
+ "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
+ "dev": true,
+ "license": "WTFPL",
+ "dependencies": {
+ "utf8-byte-length": "^1.0.1"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/type-fest": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/undici": {
+ "version": "7.30.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz",
+ "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.24.6",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unzipper": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz",
+ "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "~3.7.2",
+ "duplexer2": "~0.1.4",
+ "fs-extra": "11.3.1",
+ "graceful-fs": "^4.2.2",
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/unzipper/node_modules/fs-extra": {
+ "version": "11.3.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
+ "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/utf8-byte-length": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
+ "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
+ "dev": true,
+ "license": "(WTFPL OR MIT)"
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/webcrypto-core": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz",
+ "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/json-schema": "^1.1.12",
+ "@peculiar/utils": "^2.0.2",
+ "asn1js": "^3.0.10",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/which": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",
+ "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^3.1.1"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/xmlbuilder": {
+ "version": "15.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
+ "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.3",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",
+ "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/desktop/package.json b/desktop/package.json
new file mode 100644
index 0000000..c7a1383
--- /dev/null
+++ b/desktop/package.json
@@ -0,0 +1,104 @@
+{
+ "name": "rieke-os-desktop",
+ "version": "0.1.3",
+ "private": true,
+ "description": "Rieke OS self-contained desktop application",
+ "author": "Rieke OS contributors",
+ "main": "main.cjs",
+ "scripts": {
+ "start": "electron .",
+ "test": "node --test tests/*.test.cjs test/*.test.cjs",
+ "test:e2e": "node e2e/packaged.e2e.cjs",
+ "test:e2e:startup-failure": "node e2e/backend-failure.e2e.cjs",
+ "test:e2e:updater": "electron tests/e2e/updater-client.cjs",
+ "pack": "electron-builder --mac --arm64 --dir",
+ "dist": "electron-builder --mac dmg zip --arm64 --publish never",
+ "test:e2e:bootstrap": "node e2e/bootstrap.e2e.cjs",
+ "test:e2e:github-updates": "node e2e/github-updates.e2e.cjs"
+ },
+ "dependencies": {
+ "electron-updater": "6.8.9"
+ },
+ "devDependencies": {
+ "electron": "44.5.0",
+ "electron-builder": "26.15.3",
+ "playwright": "1.63.0"
+ },
+ "build": {
+ "appId": "org.riekeos.desktop",
+ "productName": "Rieke OS",
+ "artifactName": "Rieke-OS-${version}-${arch}.${ext}",
+ "asar": true,
+ "directories": {
+ "output": "dist",
+ "buildResources": "assets"
+ },
+ "files": [
+ "*.cjs",
+ "*.html",
+ "*.css",
+ "recovery.js",
+ "icon.png",
+ "package.json",
+ "distribution.json"
+ ],
+ "extraResources": [
+ {
+ "from": "build/runtime",
+ "to": "runtime",
+ "filter": [
+ "**/*"
+ ]
+ }
+ ],
+ "mac": {
+ "target": [
+ {
+ "target": "dmg",
+ "arch": [
+ "arm64"
+ ]
+ },
+ {
+ "target": "zip",
+ "arch": [
+ "arm64"
+ ]
+ }
+ ],
+ "category": "public.app-category.education",
+ "icon": "icon.png",
+ "minimumSystemVersion": "14.0",
+ "hardenedRuntime": true,
+ "gatekeeperAssess": false,
+ "entitlements": "entitlements.mac.plist",
+ "entitlementsInherit": "entitlements.mac.plist",
+ "notarize": true,
+ "sign": "./sign-runtime.cjs"
+ },
+ "dmg": {
+ "sign": false,
+ "title": "Open Rieke OS to install",
+ "backgroundColor": "#f7f4fa",
+ "window": {
+ "width": 560,
+ "height": 400
+ },
+ "contents": [
+ {
+ "x": 280,
+ "y": 180,
+ "type": "file",
+ "name": "Rieke OS.app"
+ }
+ ]
+ },
+ "publish": {
+ "provider": "github",
+ "owner": "maxwellsdm1867",
+ "repo": "Rieke-OS",
+ "releaseType": "draft"
+ },
+ "afterPack": "./seal-testing.cjs"
+ }
+}
diff --git a/desktop/physical-fs.cjs b/desktop/physical-fs.cjs
new file mode 100644
index 0000000..9f541a9
--- /dev/null
+++ b/desktop/physical-fs.cjs
@@ -0,0 +1,5 @@
+'use strict';
+// Bundle validation and replacement operate on physical files. Electron's
+// ordinary fs presents an ASAR archive as a virtual directory for module loads.
+// Node-based tooling uses the same physical filesystem API without Electron.
+module.exports = process.versions.electron ? require('original-fs') : require('node:fs');
diff --git a/desktop/preload.cjs b/desktop/preload.cjs
new file mode 100644
index 0000000..5676ddb
--- /dev/null
+++ b/desktop/preload.cjs
@@ -0,0 +1,27 @@
+'use strict';
+const {contextBridge, ipcRenderer} = require('electron');
+function subscribe(channel, callback) {
+ if (typeof callback !== 'function') throw new TypeError('Callback required');
+ const listener = (_event, value) => callback(value);
+ ipcRenderer.on(channel, listener);
+ return () => ipcRenderer.removeListener(channel, listener);
+}
+contextBridge.exposeInMainWorld('riekeDesktop', Object.freeze({
+ protocolVersion: 1,
+ status: () => ipcRenderer.invoke('desktop:status'),
+ checkForUpdates: () => ipcRenderer.invoke('desktop:check-updates'),
+ downloadUpdate: () => ipcRenderer.invoke('desktop:download-update'),
+ restartToUpdate: () => ipcRenderer.invoke('desktop:restart-to-update'),
+ onStatus: callback => subscribe('desktop:status-changed', callback),
+ onPrepareClose: callback => subscribe('desktop:prepare-close', callback),
+ acknowledgeDrafts: (requestId, result) => ipcRenderer.invoke('desktop:drafts-ack', {requestId, ok: result?.ok === true}),
+ chooseProjectFolder: () => ipcRenderer.invoke('desktop:choose-project-folder'),
+ saveDraft: payload => ipcRenderer.invoke('desktop:save-draft', payload),
+ loadDraft: projectId => ipcRenderer.invoke('desktop:load-draft', projectId),
+ resetDraft: projectId => ipcRenderer.invoke('desktop:reset-draft', projectId),
+ openReleaseNotes: url => ipcRenderer.invoke('desktop:open-release-notes', url),
+ installAndOpen: () => ipcRenderer.invoke('desktop:install-and-open'),
+ retryStartup: () => ipcRenderer.invoke('desktop:retry-startup'),
+ restorePreviousVersion: () => ipcRenderer.invoke('desktop:restore-previous'),
+ quit: () => ipcRenderer.invoke('desktop:quit')
+}));
diff --git a/desktop/python-runtime.json b/desktop/python-runtime.json
new file mode 100644
index 0000000..c46d7da
--- /dev/null
+++ b/desktop/python-runtime.json
@@ -0,0 +1,10 @@
+{
+ "format": "rieke-standalone-python",
+ "version": 1,
+ "python_version": "3.11.13",
+ "build": "20251007",
+ "platform": "darwin",
+ "architecture": "arm64",
+ "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20251007/cpython-3.11.13%2B20251007-aarch64-apple-darwin-install_only_stripped.tar.gz",
+ "sha256": "78bc6defdc1dac5bf6765c8f938e6849383dbed831ea1e2d11576a4683fb1e8c"
+}
diff --git a/desktop/recovery-install.cjs b/desktop/recovery-install.cjs
new file mode 100644
index 0000000..d3be0ef
--- /dev/null
+++ b/desktop/recovery-install.cjs
@@ -0,0 +1,29 @@
+'use strict';
+// Dedicated, app-owned recovery entry point. The parent has already drained all
+// services; this helper additionally waits for the main process to actually exit.
+const path = require('node:path');
+const os = require('node:os');
+const fs = require('./physical-fs.cjs').promises;
+const {promisify} = require('node:util');
+const run = promisify(require('node:child_process').execFile);
+const {installCompleteBundle, signatureIdentity, readBundleManifest, enclosingApp} = require('./bootstrap.cjs');
+async function main() {
+ const [pidText, previous, destination] = process.argv.slice(2);
+ const pid = Number(pidText);
+ const expected = path.join(os.homedir(), 'Applications', 'Rieke OS.app');
+ if (!Number.isSafeInteger(pid) || pid <= 0 || destination !== expected || enclosingApp(process.execPath) !== destination || !previous.endsWith(path.join('updates', 'previous', 'Rieke OS.app'))) throw new Error('Invalid recovery request.');
+ const current = await signatureIdentity(destination), prior = await signatureIdentity(previous);
+ if (current.team !== prior.team) throw new Error('Recovery signature differs.');
+ const deadline = Date.now() + 60000;
+ for (;;) {
+ let alive = true;
+ try { process.kill(pid, 0); } catch (error) { if (error.code === 'ESRCH') alive = false; else throw error; }
+ if (!alive) break;
+ if (Date.now() >= deadline) throw new Error('App exit was not acknowledged; recovery deferred.');
+ await new Promise(resolve => setTimeout(resolve, 200));
+ }
+ await installCompleteBundle({source: previous, destination, allowRollback: true, ignorePid: process.pid});
+ await run('/usr/bin/open', ['-n', destination]);
+}
+if (require.main === module) main().catch(() => { process.exitCode = 1; });
+module.exports = {main};
diff --git a/desktop/recovery.css b/desktop/recovery.css
new file mode 100644
index 0000000..4f0ba5d
--- /dev/null
+++ b/desktop/recovery.css
@@ -0,0 +1 @@
+:root{font-family:-apple-system,BlinkMacSystemFont,sans-serif;color:#202d32;background:#f4f5f3}body{margin:0;display:grid;place-items:center;min-height:100vh}main{width:min(520px,80vw);padding:48px;background:white;border-radius:18px;box-shadow:0 14px 55px #17363a14}h1{font-size:26px;font-weight:600}p{line-height:1.55;color:#566567}#channel{font-size:13px;font-weight:600;color:#72508a}#detail,#gatekeeper{font-size:13px;white-space:pre-wrap}button{font:inherit;border:1px solid #bdcccb;border-radius:7px;padding:10px 16px;margin:10px 8px 0 0;background:#f6f8f7;cursor:pointer}#install{background:#186f69;color:white;border-color:#186f69}button:disabled{opacity:.55;cursor:wait}
diff --git a/desktop/recovery.html b/desktop/recovery.html
new file mode 100644
index 0000000..1ea6940
--- /dev/null
+++ b/desktop/recovery.html
@@ -0,0 +1 @@
+Rieke OS
Starting Rieke OS
Verifying the complete app and its private runtime.
diff --git a/desktop/recovery.js b/desktop/recovery.js
new file mode 100644
index 0000000..30f85cc
--- /dev/null
+++ b/desktop/recovery.js
@@ -0,0 +1,23 @@
+'use strict';
+const bridge = window.riekeDesktop;
+function display(status) {
+ const testing = status.channel === 'unsigned-testing';
+ document.getElementById('title').textContent = status.title || 'Rieke OS recovery';
+ document.getElementById('message').textContent = status.message || 'Startup has not completed.';
+ document.getElementById('detail').textContent = status.detail || '';
+ document.getElementById('channel').textContent = 'Unsigned testing';
+ document.getElementById('channel').hidden = !testing;
+ document.getElementById('gatekeeper').textContent = 'If macOS blocks this unsigned testing app on its first open, choose Open Anyway in System Settings → Privacy & Security.';
+ document.getElementById('gatekeeper').hidden = !testing || status.state !== 'Bootstrap' || (status.detail || '').includes('Open Anyway');
+ document.getElementById('install').hidden = status.state !== 'Bootstrap';
+ document.getElementById('retry').hidden = status.state !== 'Recovery';
+ document.getElementById('restore').hidden = status.state !== 'Recovery';
+}
+bridge.status().then(display); bridge.onStatus(display);
+for (const [id, action] of [['install', () => bridge.installAndOpen()], ['retry', () => bridge.retryStartup()], ['restore', () => bridge.restorePreviousVersion()], ['quit', () => bridge.quit()]]) {
+ document.getElementById(id).onclick = async event => {
+ event.target.disabled = true;
+ try { const result = await action(); if (result?.ready === false) document.getElementById('detail').textContent = result.reason || 'The app is waiting for current work to finish.'; } catch (error) { document.getElementById('detail').textContent = error.message; }
+ finally { event.target.disabled = false; }
+ };
+}
diff --git a/desktop/requirements.lock b/desktop/requirements.lock
new file mode 100644
index 0000000..30617d4
--- /dev/null
+++ b/desktop/requirements.lock
@@ -0,0 +1,3 @@
+# Production WSGI service; install alongside python/workspace-runtime.lock.
+waitress==3.0.2 \
+ --hash=sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e
diff --git a/desktop/seal-testing.cjs b/desktop/seal-testing.cjs
new file mode 100644
index 0000000..2d0b471
--- /dev/null
+++ b/desktop/seal-testing.cjs
@@ -0,0 +1,19 @@
+'use strict';
+// A local ad-hoc seal makes the complete bundle structurally valid. It provides
+// no Developer ID identity, notarization, or Gatekeeper approval.
+const path=require('node:path');
+const {execFile}=require('node:child_process');
+const {promisify}=require('node:util');
+const {distributionPolicy}=require('./distribution.cjs');
+module.exports=async context=>{
+ const distribution=distributionPolicy(require('./distribution.json'));
+ if(distribution.channel!=='unsigned-testing'||context.electronPlatformName!=='darwin')return;
+ const app=path.join(context.appOutDir,`${context.packager.appInfo.productFilename}.app`);
+ const runtime=path.join(app,'Contents/Resources/runtime');
+ const {signAsync}=require('@electron/osx-sign');
+ await signAsync({app,identity:'-',identityValidation:false,platform:'darwin',type:'development',
+ preAutoEntitlements:false,preEmbedProvisioningProfile:false,gatekeeperAssess:false,
+ optionsForFile:()=>({hardenedRuntime:false,timestamp:'none',entitlements:path.join(__dirname,'entitlements.mac.plist')}),
+ ignore:filename=>filename===runtime||filename.startsWith(runtime+path.sep)});
+ await promisify(execFile)('/usr/bin/codesign',['--verify','--deep','--strict',app]);
+};
diff --git a/desktop/security.cjs b/desktop/security.cjs
new file mode 100644
index 0000000..55dce93
--- /dev/null
+++ b/desktop/security.cjs
@@ -0,0 +1,34 @@
+'use strict';
+const path = require('node:path');
+const {fileURLToPath} = require('node:url');
+const PROJECT_ID = /^(?:launcher|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
+function isOwnedURL(url, origin, localPages = []) {
+ try {
+ const parsed = new URL(url);
+ if (parsed.username || parsed.password) return false;
+ if (origin && (typeof origin === 'string' ? parsed.origin === origin : origin.has(parsed.origin)) && parsed.protocol === 'http:') return true;
+ return parsed.protocol === 'file:' && localPages.includes(path.resolve(fileURLToPath(parsed)));
+ } catch { return false; }
+}
+function validateSender(event, windows, origin, localPages) {
+ const window = [...windows].find(win => !win.isDestroyed() && win.webContents === event.sender);
+ if (!window || !event.senderFrame || event.senderFrame !== event.sender.mainFrame ||
+ !isOwnedURL(event.senderFrame.url, origin, localPages)) throw new Error('Unauthorized desktop frame');
+ return window;
+}
+function validateProjectId(value) {
+ if (typeof value !== 'string' || !PROJECT_ID.test(value)) throw new TypeError('Invalid project identity');
+ return value.toLowerCase();
+}
+function validateDraft(payload) {
+ if (!payload || Object.keys(payload).sort().join(',') !== 'projectId,value') throw new TypeError('Invalid draft payload');
+ const projectId = validateProjectId(payload.projectId);
+ const serialized = JSON.stringify(payload.value);
+ if (serialized === undefined || Buffer.byteLength(serialized) > 2 * 1024 * 1024) throw new TypeError('Draft exceeds size limit');
+ return {projectId, serialized};
+}
+function approvedReleaseURL(value) {
+ try { const url = new URL(value); return url.protocol === 'https:' && url.hostname === 'github.com' &&
+ !url.username && !url.password && /^\/maxwellsdm1867\/Rieke-OS\/releases(?:\/|$)/.test(url.pathname); } catch { return false; }
+}
+module.exports = {isOwnedURL, validateSender, validateDraft, validateProjectId, approvedReleaseURL};
diff --git a/desktop/sign-runtime.cjs b/desktop/sign-runtime.cjs
new file mode 100644
index 0000000..eb2b039
--- /dev/null
+++ b/desktop/sign-runtime.cjs
@@ -0,0 +1,64 @@
+'use strict';
+// Pinned electron-builder 26.15.3 mac.sign(opts, packager) hook. Finish native
+// signing first; hash final runtime bytes; then seal the outer application once.
+const fs = require('node:fs/promises');
+const {createReadStream} = require('node:fs');
+const path = require('node:path');
+const {createHash} = require('node:crypto');
+const {execFile} = require('node:child_process');
+const {promisify} = require('node:util');
+const run = promisify(execFile);
+const MACH_O = new Set([0xfeedface, 0xfeedfacf, 0xcefaedfe, 0xcffaedfe, 0xcafebabe, 0xbebafeca, 0xcafebabf, 0xbfbafeca]);
+async function sha256(filename) {
+ const digest = createHash('sha256');
+ for await (const chunk of createReadStream(filename)) digest.update(chunk);
+ return digest.digest('hex');
+}
+async function inventory(root) {
+ const entries = [];
+ async function visit(directory) {
+ for (const entry of await fs.readdir(directory, {withFileTypes: true})) {
+ const absolute = path.join(directory, entry.name);
+ if (entry.isDirectory()) await visit(absolute);
+ else entries.push({absolute, relative: path.relative(root, absolute).split(path.sep).join('/'), symbolic: entry.isSymbolicLink()});
+ }
+ }
+ await visit(root); return entries.sort((a, b) => a.relative.localeCompare(b.relative));
+}
+async function resealRuntimeManifest(root) {
+ const manifestPath = path.join(root, 'runtime-manifest.json');
+ const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
+ const resources = {};
+ for (const item of await inventory(root)) {
+ if (['runtime-manifest.json', 'runtime-audit.json'].includes(item.relative)) continue;
+ if (item.symbolic) resources[item.relative] = {symlink: await fs.readlink(item.absolute)};
+ else { const stat = await fs.stat(item.absolute); resources[item.relative] = {sha256: await sha256(item.absolute), size: stat.size, executable: Boolean(stat.mode & 0o111)}; }
+ }
+ manifest.resources = resources;
+ await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
+ return manifest;
+}
+async function signRuntime(options) {
+ if (!options.identity || options.identity === '-') throw new Error('Production runtime signing requires a Developer ID identity');
+ const runtime = path.join(options.app, 'Contents', 'Resources', 'runtime');
+ // No signature mutation may follow this inventory seal.
+ for (const item of await inventory(runtime)) {
+ if (item.symbolic) continue;
+ const handle = await fs.open(item.absolute, 'r');
+ const magic = Buffer.alloc(4); let bytesRead;
+ try { ({bytesRead} = await handle.read(magic, 0, 4, 0)); } finally { await handle.close(); }
+ if (bytesRead !== 4 || !MACH_O.has(magic.readUInt32BE())) continue;
+ const args = ['--force', '--sign', options.identity, '--timestamp', '--options', 'runtime'];
+ if (options.keychain) args.push('--keychain', options.keychain);
+ if (item.relative === 'python/bin/python3.11') args.push('--entitlements', path.join(__dirname, 'entitlements.mac.plist'));
+ args.push(item.absolute); await run('/usr/bin/codesign', args);
+ }
+ await resealRuntimeManifest(runtime);
+ const {signAsync} = require('@electron/osx-sign');
+ const previousIgnore = options.ignore == null ? [] : Array.isArray(options.ignore) ? options.ignore : [options.ignore];
+ await signAsync({...options, ignore: [...previousIgnore, filename => filename === runtime || filename.startsWith(runtime + path.sep)]});
+ await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', options.app]);
+}
+module.exports = signRuntime;
+module.exports.resealRuntimeManifest = resealRuntimeManifest;
+module.exports.inventory = inventory;
diff --git a/desktop/supervisor.cjs b/desktop/supervisor.cjs
new file mode 100644
index 0000000..9062909
--- /dev/null
+++ b/desktop/supervisor.cjs
@@ -0,0 +1,178 @@
+'use strict';
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const net = require('node:net');
+const {spawn, execFile} = require('node:child_process');
+const {promisify} = require('node:util');
+const {randomUUID, randomBytes, createHash} = require('node:crypto');
+const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
+async function inspectExecutable(pid) {
+ try {
+ const {stdout} = await promisify(execFile)('/bin/ps', ['-p', String(pid), '-o', 'comm=']);
+ const executable = stdout.trim(); return executable ? await fs.realpath(executable) : null;
+ } catch (error) { if (error.code === 1 || error.code === 'ENOENT') return null; throw error; }
+}
+async function atomicJSON(filename, value) {
+ await fs.mkdir(path.dirname(filename), {recursive: true, mode: 0o700});
+ const temporary = `${filename}.${randomUUID()}.tmp`;
+ await fs.writeFile(temporary, JSON.stringify(value, null, 2), {mode: 0o600});
+ await fs.rename(temporary, filename);
+}
+async function availablePort() {
+ const server = net.createServer();
+ await new Promise((resolve, reject) => server.once('error', reject).listen(0, '127.0.0.1', resolve));
+ const port = server.address().port;
+ await new Promise(resolve => server.close(resolve));
+ return port;
+}
+function matchesHealth(health, expected) {
+ return health?.ready === true && Object.entries(expected).every(([key, value]) =>
+ JSON.stringify(health[key]) === JSON.stringify(value));
+}
+class ServiceSupervisor {
+ constructor({resourcesPath, userData, appVersion, spawnProcess = spawn, request = fetch, inspectProcess = inspectExecutable, startupTimeout = 90000, drainTimeout = 30000, onFailure = () => {}}) {
+ Object.assign(this, {resourcesPath, userData, appVersion, spawnProcess, request, inspectProcess, startupTimeout, drainTimeout, onFailure});
+ this.capability = randomBytes(32).toString('hex'); this.sessionId = randomUUID();
+ this.rendererCapability = createHash('sha256').update(this.capability + ':renderer').digest('hex');
+ this.origins = new Set(); this.child = null; this.exited = true; this.ready = false;
+ this.registryPath = path.join(userData, 'desktop-service.json');
+ }
+ async loadManifest() {
+ const runtime = path.join(this.resourcesPath, 'runtime');
+ const manifestPath = path.join(runtime, 'runtime-manifest.json');
+ this.manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
+ if (this.manifest.format !== 'rieke-desktop-runtime' || this.manifest.version !== 1 ||
+ this.manifest.platform !== process.platform || this.manifest.architecture !== process.arch ||
+ this.manifest.application_version !== this.appVersion || !this.manifest.source_commit ||
+ !Array.isArray(this.manifest.workspace_formats) || !Number.isInteger(this.manifest.database_compatibility))
+ throw new Error('Packaged runtime identity or compatibility does not match this application');
+ this.executable = await fs.realpath(path.join(runtime, 'python', 'bin', 'python3.11'));
+ this.entry = await fs.realpath(path.join(runtime, 'application', 'python', 'workspace_desktop.py'));
+ const root = await fs.realpath(runtime);
+ if (![this.executable, this.entry].every(item => item.startsWith(root + path.sep))) throw new Error('Runtime entry escapes packaged resources');
+ return this.manifest;
+ }
+ async start() {
+ if (this.child && !this.exited) throw new Error('Previous backend is still running; replacement is deferred');
+ await this.loadManifest();
+ try {
+ const previous = JSON.parse(await fs.readFile(this.registryPath, 'utf8'));
+ if (!Number.isSafeInteger(previous.pid) || previous.pid <= 0 || !path.isAbsolute(previous.executable || '') || !previous.session_id)
+ throw new Error('Previous service registry is malformed; resolve recovery before opening projects');
+ const actualExecutable = await this.inspectProcess(previous.pid);
+ if (actualExecutable === previous.executable) throw new Error('A prior matching service is still active; resolve recovery before opening projects');
+ for (const record of previous.services || []) {
+ if (await this.inspectProcess(record.pid) === previous.executable)
+ throw new Error('A prior project service is still active; resolve recovery before opening projects');
+ }
+ // PID reuse is never treated as ownership and never authorizes termination.
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
+ const port = await availablePort(); this.origin = `http://127.0.0.1:${port}`;
+ const backendState = path.join(this.userData, 'backend');
+ await fs.mkdir(backendState, {recursive: true, mode: 0o700});
+ const env = {HOME: process.env.HOME, TMPDIR: process.env.TMPDIR, LANG: 'en_US.UTF-8',
+ PYTHONDONTWRITEBYTECODE: '1', PYTHONNOUSERSITE: '1', RIEKE_DESKTOP_CAPABILITY: this.capability,
+ RIEKE_PARSER_CONFIG: path.join(backendState, 'parser', 'config.ini')};
+ this.child = this.spawnProcess(this.executable, ['-B', this.entry, '--host', '127.0.0.1', '--port', String(port),
+ '--resources', this.resourcesPath, '--user-state', backendState,
+ '--manifest', path.join(this.resourcesPath, 'runtime', 'runtime-manifest.json'), '--session-id', this.sessionId],
+ {cwd: backendState, env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true});
+ this.exited = false; this.exitCode = null; this.abnormalExit = false; this.stopping = false; this.bound = false;
+ const expectedBound = {pid: this.child.pid, session_id: this.sessionId, port,
+ application_version: this.appVersion, source_commit: this.manifest.source_commit};
+ let finishBound;
+ const binding = new Promise(resolve => { finishBound = resolve; });
+ let stdoutPending = '';
+ this.child.stdout?.on('data', chunk => {
+ stdoutPending += chunk.toString('utf8');
+ if (stdoutPending.length > 65536) { stdoutPending = ''; return; }
+ let index;
+ while ((index = stdoutPending.indexOf('\n')) >= 0) {
+ const line = stdoutPending.slice(0, index); stdoutPending = stdoutPending.slice(index + 1);
+ if (!line.startsWith('RIEKE_DESKTOP_BOUND=')) continue;
+ try {
+ const value = JSON.parse(line.slice('RIEKE_DESKTOP_BOUND='.length));
+ if (Object.entries(expectedBound).every(([key, expected]) => value[key] === expected)) {
+ this.bound = true; finishBound(true);
+ }
+ } catch {}
+ }
+ });
+ this.child.once('error', () => { this.exited = true; this.exitCode = 'spawn'; finishBound(false); });
+ this.child.once('exit', code => { this.exited = true; this.exitCode = code; this.abnormalExit = !this.stopping;
+ finishBound(false); if (this.ready && !this.stopping) { this.origins.clear(); this.onFailure('The owned backend stopped unexpectedly. Scientific projects are closed until recovery.'); } });
+ // Backend output may contain scientific paths or credentials. Do not persist or expose it.
+ this.child.stdout?.resume(); this.child.stderr?.resume();
+ this.registry = {pid: this.child.pid, executable: this.executable, entry: this.entry,
+ session_id: this.sessionId, source_commit: this.manifest.source_commit, application_version: this.appVersion, port};
+ await atomicJSON(this.registryPath, this.registry);
+ const expected = this.expectedHealth(); const deadline = Date.now() + this.startupTimeout;
+ let boundTimer;
+ const bound = await Promise.race([binding, new Promise(resolve => { boundTimer = setTimeout(() => resolve(false), this.startupTimeout); })]);
+ clearTimeout(boundTimer);
+ if (!bound) throw new Error(this.exited ? `Packaged backend exited before owning its listener (${this.exitCode})` : 'Packaged backend listener deadline exceeded; no desktop capability was sent');
+ while (Date.now() < deadline && !this.exited) {
+ try {
+ const health = await this.api('/api/desktop/health');
+ if (!matchesHealth(health, expected)) throw new Error('Owned backend identity mismatch');
+ this.origins.add(this.origin); this.ready = true; return this.origin;
+ } catch (error) { this.lastStartupError = error.message; await delay(200); }
+ }
+ throw new Error(this.exited ? `Packaged backend exited before readiness (${this.exitCode})` : 'Packaged backend readiness deadline exceeded; backend retained for safe recovery');
+ }
+ expectedHealth() { return {pid: this.child.pid, session_id: this.sessionId,
+ application_version: this.manifest.application_version, source_commit: this.manifest.source_commit,
+ workspace_formats: this.manifest.workspace_formats, database_compatibility: this.manifest.database_compatibility}; }
+ async api(route, {origin = this.origin, method = 'GET', timeout = 3000, body = {}} = {}) {
+ if (!this.bound) throw new Error('Owned backend has not proved listener ownership');
+ if (origin !== this.origin || !/^\/api\/desktop\/[a-z-]+$/.test(route)) throw new Error('Desktop controls target only the exact owned root service');
+ const response = await this.request(`${origin}${route}`, {method,
+ headers: {'X-Rieke-Desktop-Capability': this.capability, 'X-Workspace-Request': '1', 'Content-Type': 'application/json'},
+ signal: AbortSignal.timeout(timeout), ...(method === 'POST' ? {body: JSON.stringify(body)} : {})});
+ let value; try { value = await response.json(); } catch { throw new Error('Malformed desktop service response'); }
+ if (!response.ok) throw new Error(value.error || (response.status === 409 ? 'Scientific writes are still active' : 'Desktop service request failed'));
+ return value;
+ }
+ async authorizeProjectURL(value) {
+ const url = new URL(value);
+ if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1' || url.username || url.password) return false;
+ const rootHealth = await this.api('/api/desktop/health');
+ if (!matchesHealth(rootHealth, this.expectedHealth())) return false;
+ if (url.origin === this.origin) return true;
+ this.registry.services = rootHealth.services || [];
+ await atomicJSON(this.registryPath, this.registry);
+ const record = rootHealth.services?.find(item => item.port === Number(url.port));
+ if (!record || record.bound !== true || record.session_id !== this.sessionId || !record.project_uuid || !record.project_path) { this.origins.delete(url.origin); return false; }
+ // Root owns the child's private bind receipt and exact process creation time.
+ // It performs the native process check before sending control capabilities;
+ // main never sends its capability to a possibly reused child port.
+ const authorization = await this.api('/api/desktop/authorize-project', {method: 'POST', body: {port: Number(url.port)}});
+ if (!authorization.record || !Object.entries(record).every(([key, value]) => JSON.stringify(authorization.record[key]) === JSON.stringify(value))) return false;
+ const childHealth = authorization.health;
+ if (!matchesHealth(childHealth, {pid: record.pid, session_id: this.sessionId,
+ application_version: this.appVersion, source_commit: this.manifest.source_commit,
+ workspace_formats: this.manifest.workspace_formats, database_compatibility: this.manifest.database_compatibility,
+ project_uuid: record.project_uuid, project_path: record.project_path})) return false;
+ this.origins.add(url.origin); return true;
+ }
+ async drain() {
+ if (this.abnormalExit) return {ready: false, reason: 'Backend exited without a drain acknowledgement; project service exit must be recovered before replacement'};
+ if (!this.child || this.exited) { this.ready = false; await fs.rm(this.registryPath, {force: true}); return {ready: true}; }
+ try {
+ const result = await this.api('/api/desktop/drain', {method: 'POST', timeout: this.drainTimeout});
+ if (result.ready !== true) throw new Error('Backend did not acknowledge drain');
+ // Ack only starts the exit phase; process exit is independent required evidence.
+ this.stopping = true;
+ await this.api('/api/desktop/stop', {method: 'POST', timeout: 3000});
+ const deadline = Date.now() + this.drainTimeout; this.ready = false;
+ while (!this.exited && Date.now() < deadline) await delay(50);
+ if (!this.exited) return {ready: false, reason: 'Backend has not acknowledged process exit; replacement is deferred'};
+ this.origins.clear(); await fs.rm(this.registryPath, {force: true}); return {ready: true};
+ } catch (error) {
+ this.stopping = false;
+ try { await this.api('/api/desktop/resume', {method: 'POST'}); } catch {}
+ return {ready: false, reason: error.message};
+ }
+ }
+}
+module.exports = {ServiceSupervisor, matchesHealth, atomicJSON, availablePort, inspectExecutable};
diff --git a/desktop/test/testing-updater.test.cjs b/desktop/test/testing-updater.test.cjs
new file mode 100644
index 0000000..e5c8c39
--- /dev/null
+++ b/desktop/test/testing-updater.test.cjs
@@ -0,0 +1,216 @@
+'use strict';
+const test=require('node:test'),assert=require('node:assert/strict');
+const fs=require('node:fs/promises'),path=require('node:path'),os=require('node:os'),crypto=require('node:crypto'),http=require('node:http');
+const {Readable}=require('node:stream');
+const {validateDescriptor,approvedURL,inspectTestingBundle,validateTestingCandidate,revalidateTestingCandidate}=require('../testing-update-validation.cjs');
+const {createTestingUpdateCoordinator}=require('../testing-updater.cjs');
+const current={application_version:'0.1.2',platform:'darwin',architecture:'arm64',mysql_version:'8.4.2',database_compatibility:1,workspace_formats:[1]};
+const bytes=Buffer.from('candidate archive bytes');
+const descriptor={format:'rieke-desktop-test-release',version:1,channel:'unsigned-testing',repository:'maxwellsdm1867/Rieke-OS',application_version:'0.1.3',platform:'darwin',architecture:'arm64',mysql_version:'8.4.2',database_compatibility:1,workspace_formats:[1],minimum_macos_version:'14.0',archive:{filename:'Rieke-OS-0.1.3-arm64.zip',size:bytes.length,sha256:crypto.createHash('sha256').update(bytes).digest('hex'),sha512:crypto.createHash('sha512').update(bytes).digest('base64')},asar_sha256:'a'.repeat(64),runtime_manifest_sha256:'b'.repeat(64)};
+const distribution={format:'rieke-desktop-distribution',version:1,channel:'unsigned-testing',repository:'maxwellsdm1867/Rieke-OS'};
+const base='https://github.com/maxwellsdm1867/Rieke-OS/releases/download/desktop-test-v0.1.3/';
+test('testing descriptor and URL boundaries reject foreign provenance, migration and nonstable versions',()=>{
+ assert.equal(validateDescriptor(descriptor,current,'14.2'),descriptor);
+ for(const patch of [{channel:'signed'},{repository:'foreign/repo'},{application_version:'0.1.2'},{application_version:'0.1.3-beta'},{architecture:'x64'},{database_compatibility:2},{workspace_formats:[2]},{minimum_macos_version:'15.0'},{archive:{...descriptor.archive,filename:'../bad.zip'}},{archive:{...descriptor.archive,size:0}},{archive:{...descriptor.archive,sha512:'bad'}}])assert.throws(()=>validateDescriptor({...descriptor,...patch},current,'14.2'));
+ assert.equal(approvedURL(base+descriptor.archive.filename,'asset'),true);
+ for(const url of ['http://github.com/maxwellsdm1867/Rieke-OS/releases/download/v0.1.3/a.zip','https://github.com/foreign/repo/releases/download/v1/a.zip','https://github.com.evil.invalid/maxwellsdm1867/Rieke-OS/releases/download/v1/a.zip','https://user:secret@github.com/maxwellsdm1867/Rieke-OS/releases/download/v1/a.zip','https://api.github.com/repos/foreign/repo/releases','https://127.0.0.1/releases'])assert.throws(()=>approvedURL(url,'asset'));
+});
+async function fixture(t,options={}){
+ const root=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-testing-update-'));
+ const release={draft:false,prerelease:true,tag_name:'desktop-test-v0.1.3',html_url:'https://github.com/maxwellsdm1867/Rieke-OS/releases/tag/desktop-test-v0.1.3',assets:[{name:'desktop-release.json',size:1000,browser_download_url:base+'desktop-release.json'},{name:descriptor.archive.filename,size:bytes.length,browser_download_url:base+descriptor.archive.filename}]};
+ let archiveCalls=0,helperCalls=0,drains=0,validationCalls=0,failDownload=false,failMetadata=false,metadataCalls=0;
+ let archiveBytes=bytes;
+ const server=http.createServer((request,response)=>{
+ const url=new URL(request.url,'http://fixture');
+ if(url.pathname.endsWith('/releases')){metadataCalls++;if(failMetadata){response.writeHead(503);response.end('offline');}else response.end(JSON.stringify([release]));}
+ else if(url.pathname.endsWith('/desktop-release.json'))response.end(JSON.stringify(descriptor));
+ else {archiveCalls++;if(failDownload){response.writeHead(200,{'Content-Length':bytes.length+10});response.end(bytes.subarray(0,2));}else response.end(archiveBytes);}
+ });
+ await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
+ const transport=url=>new Promise((resolve,reject)=>{
+ const official=new URL(url);
+ http.get({hostname:'127.0.0.1',port:server.address().port,path:official.pathname+official.search},response=>resolve({statusCode:response.statusCode,headers:response.headers,body:response})).on('error',reject);
+ });
+ const app={isPackaged:true,getPath:name=>name==='exe'?path.join(root,'Installed.app/Contents/MacOS/Rieke OS'):root};
+ await fs.mkdir(path.join(root,'Installed.app/Contents/Resources/runtime'),{recursive:true});
+ await fs.writeFile(path.join(root,'Installed.app/Contents/Resources/runtime/runtime-manifest.json'),JSON.stringify(current));
+ const coordinator=createTestingUpdateCoordinator({app,manifest:current,distribution,transport,hostVersion:'14.2',
+ timers:{setTimeout:()=>({unref(){}}),clearTimeout(){}},
+ verifyCandidate:async args=>{validationCalls++;const directory=await fs.mkdtemp(path.join(args.cacheDirectory,'candidate-'));const bundle=path.join(directory,'Rieke OS.app');await fs.mkdir(bundle);return {version:args.descriptor.application_version,downloadedFile:args.downloadedFile,bundle_path:bundle,candidate_directory:directory,bundle_sha256:'c'.repeat(64),archive_sha256:descriptor.archive.sha256,runtime_manifest_sha256:descriptor.runtime_manifest_sha256,validated:true};},
+ revalidateCandidate:async()=>{},
+ prepareQuit:async()=>{drains++;return{ready:true};},
+ processIdentity:async()=> 'fixture-process-start',
+ installHelper:async()=>{helperCalls++;},...options});
+ t.after(async()=>{coordinator.stop();await coordinator.flushReceipts();await new Promise(resolve=>server.close(resolve));await fs.rm(root,{recursive:true,force:true});});
+ return{root,app,transport,coordinator,archiveCalls:()=>archiveCalls,helperCalls:()=>helperCalls,drains:()=>drains,validationCalls:()=>validationCalls,metadataCalls:()=>metadataCalls,setMetadataFailure:()=>{failMetadata=true;},setDownloadFailure:()=>{failDownload=true;},retryDownload:()=>{failDownload=false;},corruptDownload:()=>{archiveBytes=Buffer.from('corrupt');}};
+}
+test('startup metadata notice never downloads; explicit download prepares and orderly install runs once',async t=>{
+ const f=await fixture(t);await f.coordinator.start();
+ assert.equal(f.coordinator.getStatus().state,'Available');assert.equal(f.archiveCalls(),0);
+ await f.coordinator.download();assert.equal(f.coordinator.getStatus().state,'Ready');assert.equal(f.archiveCalls(),1);
+ const result=await Promise.all([f.coordinator.installPrepared(),f.coordinator.installPrepared()]);
+ assert.ok(result.every(r=>r.installing));assert.equal(f.helperCalls(),1);assert.equal(f.drains(),1);
+});
+test('truncated and checksum-failed downloads never validate and permit a fresh explicit retry',async t=>{
+ const f=await fixture(t);await f.coordinator.start();f.setDownloadFailure();
+ await f.coordinator.download();assert.notEqual(f.coordinator.getStatus().state,'Ready');assert.equal(f.validationCalls(),0);
+ f.retryDownload();await f.coordinator.download();assert.equal(f.coordinator.getStatus().state,'Ready');
+ const bad=await fixture(t);await bad.coordinator.start();bad.corruptDownload();await bad.coordinator.download();
+ assert.notEqual(bad.coordinator.getStatus().state,'Ready');assert.equal(bad.validationCalls(),0);
+});
+test('cached archive corruption during drain revokes install authority',async t=>{
+ let target;
+ const f=await fixture(t,{prepareQuit:async()=>{await fs.appendFile(target,'changed');return{ready:true};}});
+ await f.coordinator.start();await f.coordinator.download();
+ const directories=await fs.readdir(path.join(f.root,'updates/unsigned-testing'));
+ const candidate=directories.find(x=>x.startsWith('download-'));
+ target=path.join(f.root,'updates/unsigned-testing',candidate,descriptor.archive.filename);
+ assert.equal((await f.coordinator.installPrepared()).ready,false);assert.equal(f.helperCalls(),0);
+});
+test('background metadata retry preserves available notice and never consumes the download choice',async t=>{
+ let hourly;
+ const f=await fixture(t,{timers:{setTimeout:callback=>{hourly=callback;return{unref(){}};},clearTimeout(){}}});
+ await f.coordinator.start();assert.equal(f.coordinator.getStatus().can_download,true);f.setMetadataFailure();
+ await hourly();assert.equal(f.coordinator.getStatus().state,'Available');assert.equal(f.coordinator.getStatus().available,'0.1.3');
+ assert.equal(f.coordinator.getStatus().can_download,true);assert.equal(f.archiveCalls(),0);
+ await f.coordinator.download();assert.equal(f.coordinator.getStatus().state,'Ready');assert.equal(f.coordinator.getStatus().can_restart,true);
+ const calls=f.metadataCalls();await f.coordinator.check();assert.equal(f.metadataCalls(),calls);assert.equal(f.coordinator.getStatus().state,'Ready');
+});
+test('cache corruption before drain offers retry without shutting down current services',async t=>{
+ const f=await fixture(t);await f.coordinator.start();await f.coordinator.download();
+ const directory=(await fs.readdir(path.join(f.root,'updates/unsigned-testing'))).find(x=>x.startsWith('download-'));
+ await fs.appendFile(path.join(f.root,'updates/unsigned-testing',directory,descriptor.archive.filename),'changed');
+ assert.equal((await f.coordinator.installPrepared()).ready,false);assert.equal(f.drains(),0);assert.equal(f.helperCalls(),0);
+ assert.match(f.coordinator.getStatus().message,/Retry the download/);assert.equal(f.coordinator.getStatus().can_download,true);
+});
+test('a persisted Ready hint cannot authorize installation after restart or a failed metadata check',async t=>{
+ const f=await fixture(t);
+ const cache=path.join(f.root,'updates/unsigned-testing');await fs.mkdir(cache,{recursive:true});
+ await fs.writeFile(path.join(cache,'status.json'),JSON.stringify({format:'rieke-desktop-testing-update-status',version:1,installed:'0.1.2',available:'0.1.3',state:'Ready'}));
+ f.setMetadataFailure();await f.coordinator.start();
+ assert.equal((await f.coordinator.installPrepared()).ready,false);assert.equal(f.helperCalls(),0);
+ assert.equal(f.coordinator.getStatus().can_download,false);assert.equal(f.coordinator.getStatus().can_restart,false);
+});
+test('ordinary quit preserves downloaded choice; cold start requires fresh official metadata and complete revalidation',async t=>{
+ const f=await fixture(t);await f.coordinator.start();await f.coordinator.download();
+ f.coordinator.stop();await f.coordinator.flushReceipts();assert.equal(f.helperCalls(),0);
+ let validations=0;
+ const resumed=createTestingUpdateCoordinator({app:f.app,manifest:current,distribution,transport:f.transport,hostVersion:'14.2',
+ timers:{setTimeout:()=>({unref(){}}),clearTimeout(){}},
+ revalidateCandidate:async({candidate,descriptor:live})=>{validations++;assert.equal(live.archive.sha256,descriptor.archive.sha256);assert.equal(candidate.bundle_sha256,'c'.repeat(64));return{source_dirty:true};},
+ prepareQuit:async()=>({ready:false})});
+ const metadataBefore=f.metadataCalls();await resumed.start();assert.ok(f.metadataCalls()>metadataBefore);
+ assert.equal(resumed.getStatus().state,'Ready');assert.equal(resumed.getStatus().source_dirty,true);assert.equal(validations,1);
+ assert.equal(f.archiveCalls(),1);assert.equal(f.helperCalls(),0);
+ resumed.stop();await resumed.flushReceipts();
+});
+test('a changed cached archive or linked resume hint offers a fresh download without install authority',async t=>{
+ const f=await fixture(t);await f.coordinator.start();await f.coordinator.download();f.coordinator.stop();await f.coordinator.flushReceipts();
+ const cache=path.join(f.root,'updates/unsigned-testing'),hint=JSON.parse(await fs.readFile(path.join(cache,'prepared.json'),'utf8'));
+ await fs.appendFile(path.join(cache,hint.archive_relative_path),'changed');
+ let revalidated=0;
+ const options={app:f.app,manifest:current,distribution,transport:f.transport,hostVersion:'14.2',timers:{setTimeout:()=>1,clearTimeout(){}},revalidateCandidate:async()=>{revalidated++;}};
+ const resumed=createTestingUpdateCoordinator(options);await resumed.start();assert.equal(resumed.getStatus().state,'Available');assert.equal(resumed.getStatus().can_download,true);
+ assert.equal(revalidated,0);assert.equal((await resumed.installPrepared()).ready,false);resumed.stop();await resumed.flushReceipts();
+ const unrelated=path.join(f.root,'outside-hint.json');await fs.writeFile(unrelated,JSON.stringify(hint));await fs.symlink(unrelated,path.join(cache,'prepared.json'));
+ const linked=createTestingUpdateCoordinator(options);await linked.start();assert.equal(linked.getStatus().state,'Available');assert.equal((await linked.installPrepared()).ready,false);
+ assert.equal(await fs.readFile(unrelated,'utf8'),JSON.stringify(hint));linked.stop();await linked.flushReceipts();
+});
+test('obsolete-cache cleanup removes only old owned generated directories and preserves links/unrelated files',async t=>{
+ const f=await fixture(t);await f.coordinator.start();
+ const cache=path.join(f.root,'updates/unsigned-testing'),old=new Date(Date.now()-48*3600000);
+ for(const name of ['download-old000','candidate-old111','unrelated-user-data']){await fs.mkdir(path.join(cache,name));await fs.writeFile(path.join(cache,name,'retained'),'fixture');await fs.utimes(path.join(cache,name),old,old);}
+ const outside=path.join(f.root,'outside');await fs.mkdir(outside);await fs.writeFile(path.join(outside,'keep'),'safe');
+ await fs.symlink(outside,path.join(cache,'candidate-link00'));
+ await f.coordinator.download();assert.equal(f.coordinator.getStatus().state,'Ready');
+ await assert.rejects(fs.access(path.join(cache,'download-old000')));await assert.rejects(fs.access(path.join(cache,'candidate-old111')));
+ assert.equal(await fs.readFile(path.join(outside,'keep'),'utf8'),'safe');assert.ok((await fs.lstat(path.join(cache,'candidate-link00'))).isSymbolicLink());
+ assert.equal(await fs.readFile(path.join(cache,'unrelated-user-data/retained'),'utf8'),'fixture');
+});
+test('drain deferral preserves candidate; stopping during acknowledged drain prevents handoff',async t=>{
+ const blocked=await fixture(t,{prepareQuit:async()=>({ready:false,reason:'writer active'})});
+ await blocked.coordinator.start();await blocked.coordinator.download();assert.equal((await blocked.coordinator.installPrepared()).ready,false);
+ assert.equal(blocked.coordinator.getStatus().state,'Ready');assert.equal(blocked.helperCalls(),0);
+ let coordinator;
+ const stopped=await fixture(t,{prepareQuit:async()=>{coordinator.stop();return{ready:true};}});coordinator=stopped.coordinator;
+ await coordinator.start();await coordinator.download();assert.equal((await coordinator.installPrepared()).ready,false);assert.equal(stopped.helperCalls(),0);
+});
+test('symlinked cache root and foreign explicit distribution never gain update authority',async t=>{
+ const root=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-test-cache-link-'));t.after(()=>fs.rm(root,{recursive:true,force:true}));
+ await fs.mkdir(path.join(root,'outside'));await fs.symlink(path.join(root,'outside'),path.join(root,'updates'));
+ const f=await fixture(t,{app:{isPackaged:true,getPath:()=>root}});await f.coordinator.start();await f.coordinator.download();
+ assert.notEqual(f.coordinator.getStatus().state,'Ready');assert.equal(f.validationCalls(),0);
+ const wrong=await fixture(t,{distribution:{...distribution,repository:'foreign/repo'}});await wrong.coordinator.start();
+ assert.equal(wrong.coordinator.getStatus().state,'Deferred');assert.equal(wrong.archiveCalls(),0);
+});
+async function bundleFixture(t){
+ const root=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-testing-bundle-'));
+ t.after(()=>fs.rm(root,{recursive:true,force:true}));
+ const bundle=path.join(root,'Rieke OS.app'),runtime=path.join(bundle,'Contents/Resources/runtime');
+ const data={
+ 'python/bin/python3.11':'candidate code must never execute',
+ 'mysql/bin/mysqld':'candidate native code must never execute',
+ 'application/python/workspace_desktop.py':'raise Exception("candidate executed")',
+ 'frontend/index.html':'frontend',
+ 'application/rieke-release.json':JSON.stringify({version:'0.1.3',workspace_formats:[1],database_compatibility:1}),
+ 'application/python/workspace-source.json':JSON.stringify({commit:'b'.repeat(40),python:'3.11.13'})};
+ const resources={};
+ for(const [name,text]of Object.entries(data)){
+ const file=path.join(runtime,name);await fs.mkdir(path.dirname(file),{recursive:true});await fs.writeFile(file,text,{mode:name.includes('/bin/')?0o755:0o644});
+ resources[name]={sha256:crypto.createHash('sha256').update(text).digest('hex'),size:Buffer.byteLength(text),executable:name.includes('/bin/')};
+ }
+ const candidate={...current,format:'rieke-desktop-runtime',version:1,application_version:'0.1.3',minimum_macos_version:'14.0',source_dirty:true,source_commit:'a'.repeat(40),parser_commit:'b'.repeat(40),python_version:'3.11.13',resources};
+ const manifestFile=path.join(runtime,'runtime-manifest.json');await fs.writeFile(manifestFile,JSON.stringify(candidate));
+ await fs.mkdir(path.join(bundle,'Contents/MacOS'));await fs.writeFile(path.join(bundle,'Contents/MacOS/Rieke OS'),'do not execute',{mode:0o755});
+ const plist='CFBundleIdentifierorg.riekeos.desktopCFBundleShortVersionString0.1.3LSMinimumSystemVersion14.0';
+ await fs.writeFile(path.join(bundle,'Contents/Info.plist'),plist);
+ await fs.writeFile(path.join(bundle,'Contents/Resources/app.asar'),'test asar');
+ const next={...descriptor,asar_sha256:crypto.createHash('sha256').update('test asar').digest('hex'),runtime_manifest_sha256:crypto.createHash('sha256').update(JSON.stringify(candidate)).digest('hex')};
+ const bundleDigest=require('../bootstrap.cjs').bundleDigest;
+ const execute=require('node:util').promisify(require('node:child_process').execFile);
+ // Tiny fixture is not Mach-O; all boundaries except structural seal checking
+ // use the actual OS tools. Packaged E2E checks the real seal without this seam.
+ const run=async(exe,args,options)=>exe==='/usr/bin/codesign'?{stdout:'',stderr:''}:execute(exe,args,options);
+ return{root,bundle,runtime,descriptor:next,candidate,bundleDigest,run};
+}
+test('actual extracted testing bundle permits declared dirty provenance and rejects changed resources/control links',{skip:process.platform!=='darwin'},async t=>{
+ const f=await bundleFixture(t);
+ const options={bundle:f.bundle,descriptor:f.descriptor,manifest:current,hostVersion:'14.2',bundleDigest:f.bundleDigest,run:f.run};
+ const result=await inspectTestingBundle(options);assert.equal(result.source_dirty,true);assert.equal(result.developer_id_verified,false);
+ await assert.rejects(inspectTestingBundle({...options,run:async(exe,args,config)=>{if(exe==='/usr/bin/codesign')throw new Error('damaged structural seal');return f.run(exe,args,config);}}),/damaged structural seal/);
+ const file=path.join(f.runtime,'frontend/index.html');await fs.appendFile(file,'corrupt');await assert.rejects(inspectTestingBundle(options),/metadata|checksum/);await fs.writeFile(file,'frontend');
+ const asar=path.join(f.bundle,'Contents/Resources/app.asar');await fs.rename(asar,asar+'.copy');await fs.symlink('app.asar.copy',asar);
+ await assert.rejects(inspectTestingBundle(options),/regular contained/);
+});
+test('real ZIP preflight/extraction uses installed interpreter and never executes candidate code',{skip:process.platform!=='darwin'},async t=>{
+ const f=await bundleFixture(t);
+ const {promisify}=require('node:util'),run=promisify(require('node:child_process').execFile);
+ let installedBundle=path.resolve(__dirname,'../dist/mac-arm64/Rieke OS.app');
+ try{await fs.access(path.join(installedBundle,'Contents/Resources/runtime/python/bin/python3.11'));}
+ catch{
+ // Small source unit-test fixture only. Packaged E2E always uses real bundled Python.
+ installedBundle=path.join(f.root,'Current.app');
+ const hostPython=(await run('/usr/bin/which',['python3'])).stdout.trim();
+ const bin=path.join(installedBundle,'Contents/Resources/runtime/python/bin');await fs.mkdir(bin,{recursive:true});
+ await fs.symlink(hostPython,path.join(bin,'python3.11'));
+ }
+ const python=path.join(installedBundle,'Contents/Resources/runtime/python/bin/python3.11');
+ const archive=path.join(f.root,'Rieke-OS-0.1.3-arm64.zip');
+ const generate=String.raw`import os,sys,zipfile
+from pathlib import Path
+root=Path(sys.argv[1])
+with zipfile.ZipFile(sys.argv[2],'w',zipfile.ZIP_DEFLATED) as z:
+ for file in root.rglob('*'):
+ if file.is_file():z.write(file,str(file.relative_to(root.parent)))
+`;
+ await run(python,['-I','-B','-c',generate,f.bundle,archive]);
+ const body=await fs.readFile(archive);
+ const next={...f.descriptor,archive:{...descriptor.archive,size:body.length,sha256:crypto.createHash('sha256').update(body).digest('hex'),sha512:crypto.createHash('sha512').update(body).digest('base64')}};
+ const executed=[];
+ const validated=await validateTestingCandidate({downloadedFile:archive,descriptor:next,manifest:current,hostVersion:'14.2',cacheDirectory:f.root,installedBundle,bundleDigest:f.bundleDigest,run:async(exe,args,options)=>{executed.push(exe);return f.run(exe,args,options);}});
+ assert.equal(validated.validated,true);assert.equal(validated.bundle_sha256,await f.bundleDigest(f.bundle));
+ assert.ok(executed.every(exe=>exe===python||exe==='/usr/bin/ditto'||exe==='/usr/libexec/PlistBuddy'||exe==='/usr/bin/codesign'));
+ assert.ok(!executed.some(exe=>exe.startsWith(f.bundle)));
+ await fs.writeFile(path.join(validated.bundle_path,'extra-after-validation'),'changed full app closure');
+ await assert.rejects(revalidateTestingCandidate({candidate:validated,descriptor:next,manifest:current,hostVersion:'14.2',run:f.run}),/Prepared app bundle changed/);
+});
diff --git a/desktop/test/updater.test.cjs b/desktop/test/updater.test.cjs
new file mode 100644
index 0000000..36e04a9
--- /dev/null
+++ b/desktop/test/updater.test.cjs
@@ -0,0 +1,121 @@
+'use strict';
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const {EventEmitter} = require('node:events');
+const fs = require('node:fs/promises');
+const os = require('node:os');
+const path = require('node:path');
+const {createUpdateCoordinator} = require('../updater.cjs');
+const {compatibleCandidate, compatibleMacMinimum, safeResource, compareVersions, verifyResources, ARCHIVE_CHECK} = require('../updater-validation.cjs');
+const manifest = {format: 'rieke-desktop-runtime', version: 1, source_dirty:false,source_commit:'a'.repeat(40),parser_commit:'b'.repeat(40),application_version: '1.0.0', platform: 'darwin', architecture: 'arm64', mysql_version: '8.4.2', workspace_formats: [1], database_compatibility: 1, resources: {'file': {sha256: 'a'.repeat(64), size: 0}}};
+test('candidate rejects migrations, downgrades, prereleases and foreign platforms', () => {
+ const next = {...manifest, application_version: '1.1.0'};
+ assert.equal(compatibleCandidate(next, manifest, '1.1.0'), next);
+ for (const patch of [{application_version: '0.9.0'}, {application_version: '1.1.0-beta'}, {mysql_version: '9.0.0'}, {database_compatibility: 2}, {workspace_formats: [2]}, {architecture: 'x64'}, {resources: {}}]) assert.throws(() => compatibleCandidate({...next, ...patch}, manifest, patch.application_version || '1.1.0'));
+ assert.equal(compareVersions('1.10.0', '1.9.9'), 1);
+ assert.equal(compatibleMacMinimum('14.0','14.2.1'),true);
+ assert.throws(()=>compatibleMacMinimum('15.0','14.2.1'));
+ for (const unsafe of ['../outside', '/absolute', 'a/../../outside', 'a\\b', 'a//b', './a']) assert.throws(() => safeResource('/tmp/runtime', unsafe));
+});
+test('resource verification rejects a symlink which resolves outside the bundle', async t => {
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rieke-resource-test-'));
+ t.after(() => fs.rm(root, {recursive: true, force: true}));
+ await fs.symlink('/etc/hosts', path.join(root, 'escape'));
+ await assert.rejects(verifyResources(root, {escape: {symlink: '/etc/hosts'}}), /escapes/);
+});
+test('archive preflight rejects traversal and escaping symlinks before extraction', async t => {
+ const {spawnSync} = require('node:child_process');
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rieke-archive-test-'));
+ t.after(() => fs.rm(root, {recursive: true, force: true}));
+ const bundledPython = path.resolve(__dirname, '../build/runtime/python/bin/python3.11');
+ const python = require('node:fs').existsSync(bundledPython) ? bundledPython : 'python3';
+ const generate = String.raw`
+import sys,zipfile,stat
+with zipfile.ZipFile(sys.argv[1],'w') as archive:
+ kind=sys.argv[2]
+ if kind=='traversal': archive.writestr('../escape','data')
+ elif kind=='link':
+ info=zipfile.ZipInfo('Rieke OS.app/escape');info.create_system=3;info.external_attr=(stat.S_IFLNK|0o777)<<16
+ archive.writestr(info,'../../escape');archive.writestr('Rieke OS.app/escape/file','data')
+ elif kind=='safe':
+ archive.writestr('Rieke OS.app/Contents/file','data')
+ info=zipfile.ZipInfo('Rieke OS.app/Contents/link');info.create_system=3;info.external_attr=(stat.S_IFLNK|0o777)<<16
+ archive.writestr(info,'file')
+`;
+ for (const kind of ['traversal','link','safe']) {
+ const filename = path.join(root, kind+'.zip');
+ assert.equal(spawnSync(python,['-I','-B','-c',generate,filename,kind],{encoding:'utf8'}).status,0);
+ const result=spawnSync(python,['-I','-B','-c',ARCHIVE_CHECK,filename],{encoding:'utf8'});
+ assert.equal(result.status,kind==='safe'?0:1);
+ }
+});
+async function fixture(t, options = {}) {
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'rieke-updater-test-'));
+ // Receipt writes are serialized; remove after they have had a turn to settle.
+ t.after(async () => {coordinator.stop(); await new Promise(resolve => setTimeout(resolve, 30)); await fs.rm(dir, {recursive: true, force: true});});
+ const updater = new EventEmitter();
+ updater.setFeedURL = value => {updater.feed = value;};
+ updater.checkForUpdates = async () => {};
+ let installs = 0, drains = 0;
+ updater.quitAndInstall = () => {installs++;};
+ const coordinator = createUpdateCoordinator({app: {isPackaged: true, getPath: () => dir}, manifest, updater,
+ verifyInstalled: async () => ({team: 'TESTTEAM', identifier: 'org.riekeos.desktop'}),
+ verifyCandidate: async ({version}) => ({version, validated: true}),
+ retainPrevious: async () => {},
+ prepareQuit: async () => {drains++; return {ready: false};},
+ timers: {setTimeout: () => 1, clearTimeout: () => {}}, ...options});
+ await coordinator.start();
+ return {coordinator, updater, installs: () => installs, drains: () => drains};
+}
+test('source and unsigned builds have no install authority', async t => {
+ const {coordinator, updater} = await fixture(t, {enabled: false});
+ assert.equal(coordinator.getStatus().state, 'Deferred');
+ assert.equal(updater.feed, undefined);
+ assert.equal((await coordinator.installPrepared()).ready, false);
+});
+test('quiet downloads require validation and every drain acknowledgment before install', {skip: process.platform !== 'darwin' || process.arch !== 'arm64'}, async t => {
+ let allowDrain = false;
+ const {coordinator, updater, installs} = await fixture(t, {prepareQuit: async () => ({ready: allowDrain})});
+ assert.equal(updater.autoInstallOnAppQuit, false);
+ assert.equal(updater.allowDowngrade, false);
+ assert.equal(updater.allowPrerelease, false);
+ assert.equal(updater.feed.repo, 'Rieke-OS');
+ updater.emit('update-downloaded', {version: '1.1.0', downloadedFile: '/cache/update.zip'});
+ await new Promise(resolve => setImmediate(resolve));
+ assert.equal(coordinator.getStatus().state, 'Ready');
+ assert.equal(coordinator.getStatus().native_staging_verified, false);
+ assert.equal((await coordinator.installPrepared()).ready, false);
+ assert.equal(installs(), 0);
+ assert.equal(coordinator.getStatus().state, 'Ready');
+ updater.emit('error', new Error('Network failure with secret URL'));
+ assert.equal(coordinator.getStatus().state, 'Ready');
+ assert.equal(coordinator.getStatus().available, '1.1.0');
+ assert.ok(!JSON.stringify(coordinator.getStatus()).includes('secret'));
+ allowDrain = true;
+ const results = await Promise.all([coordinator.installPrepared(), coordinator.installPrepared()]);
+ assert.ok(results.every(result => result.installing));
+ assert.equal(installs(), 1);
+});
+test('rejected candidate never becomes ready or installs', {skip: process.platform !== 'darwin' || process.arch !== 'arm64'}, async t => {
+ const {coordinator, updater, installs} = await fixture(t, {verifyCandidate: async () => {throw new Error('invalid signature');}});
+ updater.emit('update-downloaded', {version: '1.1.0'});
+ await new Promise(resolve => setImmediate(resolve));
+ assert.equal(coordinator.getStatus().state, 'Deferred');
+ assert.equal((await coordinator.installPrepared()).ready, false);
+ assert.equal(installs(), 0);
+});
+test('cache mutation during real drain cannot authorize the native installer', {skip: process.platform !== 'darwin' || process.arch !== 'arm64'}, async t => {
+ const crypto=require('node:crypto');
+ const directory=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-drain-cache-test-'));
+ t.after(()=>fs.rm(directory,{recursive:true,force:true}));
+ const downloadedFile=path.join(directory,'candidate.zip');await fs.writeFile(downloadedFile,'verified fixture');
+ const archive_sha256=crypto.createHash('sha256').update('verified fixture').digest('hex');
+ let recovered=false;
+ const {coordinator,updater,installs}=await fixture(t,{
+ verifyCandidate:async({version})=>({version,downloadedFile,archive_sha256}),
+ prepareQuit:async()=>{await fs.writeFile(downloadedFile,'changed during drain');return {ready:true};},
+ onInstallationFailure:()=>{recovered=true;}});
+ updater.emit('update-downloaded',{version:'1.1.0'});await new Promise(resolve=>setImmediate(resolve));
+ assert.equal((await coordinator.installPrepared()).ready,false);
+ assert.equal(installs(),0);assert.equal(recovered,true);assert.equal(coordinator.getStatus().state,'Deferred');
+});
diff --git a/desktop/testing-install.cjs b/desktop/testing-install.cjs
new file mode 100644
index 0000000..1b49612
--- /dev/null
+++ b/desktop/testing-install.cjs
@@ -0,0 +1,189 @@
+'use strict';
+// Explicit unsigned-testing distribution only. Integrity is checked locally;
+// macOS quarantine and Gatekeeper decisions are left intact.
+const fs=require('./physical-fs.cjs').promises;
+const path=require('node:path');
+const os=require('node:os');
+const {promisify}=require('node:util');
+const {execFile,spawn}=require('node:child_process');
+const {createHash,randomUUID}=require('node:crypto');
+const runFile=promisify(execFile);
+const {APP_ID,enclosingApp,bundleDigest,installCompleteBundle,readBundleManifest,compatibleManifest,assertNotRunning,verifyTestingBundle}=require('./bootstrap.cjs');
+const {compareVersions,stableVersion,verifyResources}=require('./updater-validation.cjs');
+const READY='RIEKE_TESTING_INSTALL_READY=';
+async function sha256(file){
+ const digest=createHash('sha256'),handle=await fs.open(file,'r');
+ try{for await(const chunk of handle.createReadStream())digest.update(chunk);}finally{await handle.close();}
+ return digest.digest('hex');
+}
+async function privateReceipt(receiptPath,{previous=false}={}){
+ const info=await fs.lstat(receiptPath);
+ if(!info.isFile()||info.isSymbolicLink()||info.uid!==process.getuid()||(info.mode&0o077)||info.size>65536)throw new Error('Update receipt must be a private current-user-owned regular file');
+ const parent=await fs.lstat(path.dirname(receiptPath));
+ if(!parent.isDirectory()||parent.isSymbolicLink()||parent.uid!==process.getuid())throw new Error('Update receipt directory must be current-user owned');
+ const value=JSON.parse(await fs.readFile(receiptPath,'utf8'));
+ if(value.format!==(previous?'rieke-unsigned-testing-previous':'rieke-unsigned-testing-update')||value.version!==1||value.channel!=='unsigned-testing'||value.identifier!==APP_ID||(!previous&&value.validated!==true))throw new Error('Unsigned testing update receipt is invalid');
+ return value;
+}
+async function processCreationIdentity(pid,currentExecutable,run=runFile){
+ if(!Number.isSafeInteger(pid)||pid<=0)throw new Error('Current app process identity is invalid');
+ const bundle=enclosingApp(currentExecutable);
+ if(!bundle)throw new Error('Current app executable must be an installed complete bundle');
+ const python=path.join(bundle,'Contents/Resources/runtime/python/bin/python3.11');
+ const code='import json,os,psutil,sys\ntry:\n p=psutil.Process(int(sys.argv[1])); value={"pid":p.pid,"created_at":p.create_time(),"executable":os.path.realpath(p.exe()),"alive":p.status()!=psutil.STATUS_ZOMBIE}\nexcept psutil.NoSuchProcess: value={"pid":int(sys.argv[1]),"alive":False}\nprint("RIEKE_PROCESS_IDENTITY="+json.dumps(value))';
+ const result=await run(python,['-I','-B','-c',code,String(pid)],{timeout:15000,env:{...process.env,PYTHONDONTWRITEBYTECODE:'1',PYTHONNOUSERSITE:'1'}});
+ const line=result.stdout.split('\n').find(value=>value.startsWith('RIEKE_PROCESS_IDENTITY='));
+ if(!line)throw new Error('Current app process identity is unavailable');
+ const identity=JSON.parse(line.slice('RIEKE_PROCESS_IDENTITY='.length));
+ if(identity.pid!==pid||typeof identity.alive!=='boolean'||(identity.alive&&(!Number.isFinite(identity.created_at)||typeof identity.executable!=='string')))throw new Error('Current app process identity is invalid');
+ return identity;
+}
+async function ownedCachePath(file,cache,{directory=false}={}){
+ const info=await fs.lstat(file),physical=await fs.realpath(file);
+ if(info.uid!==process.getuid()||info.isSymbolicLink()||!(directory?info.isDirectory():info.isFile())||!physical.startsWith(cache+path.sep))throw new Error('Prepared update must remain in its owned receipt cache');
+ return physical;
+}
+async function atomicPrivateJSON(file,value){
+ const temporary=file+'.'+randomUUID();
+ const handle=await fs.open(temporary,'wx',0o600);
+ try{await handle.writeFile(JSON.stringify(value,null,2)+'\n');await handle.sync();}finally{await handle.close();}
+ try{await fs.rename(temporary,file);const directory=await fs.open(path.dirname(file),'r');try{await directory.sync();}finally{await directory.close();}}
+ finally{await fs.rm(temporary,{force:true});}
+}
+async function validatePrepared({receiptPath,currentExecutable,run=runFile,requireParent=true}){
+ const receipt=await privateReceipt(receiptPath);
+ const restoring=receipt.operation==='restore';
+ if(receipt.operation!==undefined&&!['update','restore'].includes(receipt.operation))throw new Error('Unsigned testing operation is invalid');
+ const executable=await fs.realpath(currentExecutable),installed=enclosingApp(executable);
+ if(!installed||await fs.realpath(receipt.install_path)!==installed||await fs.realpath(receipt.current_executable)!==executable||!Number.isSafeInteger(receipt.current_pid)||receipt.current_pid<=0||!Number.isFinite(receipt.current_created_at))throw new Error('Update does not belong to this exact installed app');
+ for(const field of [...(restoring?[]:['archive_sha256']),'bundle_sha256','runtime_manifest_sha256','current_manifest_sha256'])if(!/^[a-f0-9]{64}$/.test(receipt[field]||''))throw new Error('Update receipt checksum is invalid');
+ const cache=await fs.realpath(path.dirname(receiptPath));
+ if(path.basename(cache)!=='unsigned-testing'||path.basename(path.dirname(cache))!=='updates')throw new Error('Update cache does not belong to the current application profile');
+ const userData=path.dirname(path.dirname(cache)),profile=await fs.lstat(userData);
+ if(profile.isSymbolicLink()||!profile.isDirectory()||profile.uid!==process.getuid())throw new Error('Current application profile ownership differs');
+ let bundle;
+ if(restoring){
+ const prior=await privateReceipt(path.join(cache,'previous.json'),{previous:true});
+ const expected=path.join(path.dirname(installed),'.Rieke OS.previous.app');
+ const info=await fs.lstat(expected);
+ if(info.isSymbolicLink()||!info.isDirectory()||info.uid!==process.getuid()||await fs.realpath(receipt.bundle_path)!==expected||prior.previous_path!==expected||prior.install_path!==installed||prior.current_version!==receipt.current_version||prior.previous_version!==receipt.target_version||prior.previous_bundle_sha256!==receipt.bundle_sha256||prior.previous_manifest_sha256!==receipt.runtime_manifest_sha256)throw new Error('Restore does not identify the retained verified previous app');
+ bundle=expected;
+ }else{
+ const archive=await ownedCachePath(receipt.archive_path,cache);
+ bundle=await ownedCachePath(receipt.bundle_path,cache,{directory:true});
+ if(await sha256(archive)!==receipt.archive_sha256)throw new Error('Prepared update archive or bundle checksum differs');
+ }
+ if(await bundleDigest(bundle)!==receipt.bundle_sha256)throw new Error('Prepared update archive or bundle checksum differs');
+ const manifestPath=path.join(bundle,'Contents/Resources/runtime/runtime-manifest.json');
+ const currentPath=path.join(installed,'Contents/Resources/runtime/runtime-manifest.json');
+ if(await sha256(manifestPath)!==receipt.runtime_manifest_sha256||await sha256(currentPath)!==receipt.current_manifest_sha256)throw new Error('Prepared update or installed runtime manifest differs');
+ const current=await readBundleManifest(installed),candidate=await readBundleManifest(bundle);
+ stableVersion(receipt.target_version);
+ if(current.application_version!==receipt.current_version||candidate.application_version!==receipt.target_version||(!restoring&&compareVersions(receipt.target_version,receipt.current_version)<=0)||!compatibleManifest(candidate,current))throw new Error('Unsigned update version or data compatibility differs');
+ await verifyResources(path.join(installed,'Contents/Resources/runtime'),current.resources);
+ await verifyResources(path.join(bundle,'Contents/Resources/runtime'),candidate.resources);
+ await verifyTestingBundle(bundle,run);
+ if(requireParent){
+ const identity=await processCreationIdentity(receipt.current_pid,executable,run);
+ if(!identity.alive||identity.created_at!==receipt.current_created_at||await fs.realpath(identity.executable)!==executable)throw new Error('Current app process ownership changed');
+ }
+ return {receipt,installed,executable,bundle,cache,current,candidate,userData};
+}
+async function applyTestingInstall({receiptPath,currentExecutable=process.execPath,run=runFile,publishReady=packet=>process.stdout.write(READY+JSON.stringify(packet)+'\n'),timeoutMs=90000,pollMs=200}){
+ const prepared=await validatePrepared({receiptPath,currentExecutable,run});
+ const {receipt,installed,executable,bundle,cache,current,userData}=prepared;
+ const openArguments=['-n','-a',installed,'--env','HOME='+os.homedir(),'--args','--user-data-dir='+userData];
+ publishReady({pid:process.pid,current_pid:receipt.current_pid,version:receipt.target_version,archive_sha256:receipt.archive_sha256});
+ const deadline=Date.now()+timeoutMs;
+ for(;;){
+ const identity=await processCreationIdentity(receipt.current_pid,executable,run);
+ if(!identity.alive)break;
+ if(identity.created_at!==receipt.current_created_at||await fs.realpath(identity.executable)!==executable)throw new Error('Current app process ownership changed while waiting for exit');
+ if(Date.now()>=deadline)throw new Error('Current app exit was not acknowledged; installation deferred');
+ await new Promise(resolve=>setTimeout(resolve,pollMs));
+ }
+ // Cached data is checked again after actual exit, before touching installation.
+ await validatePrepared({receiptPath,currentExecutable,run,requireParent:false});
+ const priorDigest=await bundleDigest(installed);
+ const result=await installCompleteBundle({source:bundle,destination:installed,distribution:{channel:'unsigned-testing'},expectedBundleSha256:receipt.bundle_sha256,run,allowRollback:true,ignorePid:process.pid});
+ await atomicPrivateJSON(path.join(cache,'previous.json'),{format:'rieke-unsigned-testing-previous',version:1,channel:'unsigned-testing',identifier:APP_ID,
+ install_path:installed,previous_path:result.previous,previous_version:current.application_version,previous_bundle_sha256:priorDigest,
+ previous_manifest_sha256:receipt.current_manifest_sha256,current_version:receipt.target_version});
+ try{await run('/usr/bin/open',openArguments);}
+ catch(error){
+ // A refused launch may be Gatekeeper. Preserve its attributes and return to
+ // the verified previous app; never clear quarantine or suppress OS approval.
+ if(!result.previous||await bundleDigest(result.previous)!==priorDigest)throw error;
+ await assertNotRunning(installed,run,process.pid);
+ const failed=path.join(cache,'failed-candidate-'+randomUUID()+'.app');
+ await fs.rename(installed,failed);
+ try{await fs.rename(result.previous,installed);}catch(rollbackError){await fs.rename(failed,installed);throw rollbackError;}
+ if(await bundleDigest(installed)!==priorDigest)throw new Error('Restored previous application checksum differs');
+ await fs.rm(path.join(cache,'previous.json'),{force:true});
+ await run('/usr/bin/open',openArguments);
+ const outcome={state:'Restored',destination:installed,version:current.application_version,failedCandidateRetained:true};
+ await atomicPrivateJSON(receiptPath+'.result.json',outcome);return outcome;
+ }
+ const outcome={state:receipt.operation==='restore'?'Restored':'Installed',destination:installed,previous:result.previous,version:receipt.target_version};
+ await atomicPrivateJSON(receiptPath+'.result.json',outcome);
+ return outcome;
+}
+async function launchTestingInstall({receiptPath,currentExecutable=process.execPath,currentPid=process.pid,run=runFile,spawnHelper=spawn,readyTimeoutMs=150000}){
+ const prepared=await validatePrepared({receiptPath,currentExecutable,run});
+ if(prepared.receipt.current_pid!==currentPid||!path.resolve(__filename).startsWith(prepared.installed+path.sep))throw new Error('Install helper must originate from this exact current app');
+ const script=path.join(__dirname,'testing-install.cjs');
+ const child=spawnHelper(prepared.executable,[script,'--apply',receiptPath],{detached:true,stdio:['ignore','pipe','pipe'],cwd:prepared.cache,
+ env:{HOME:process.env.HOME,TMPDIR:process.env.TMPDIR,LANG:process.env.LANG||'en_US.UTF-8',PATH:'/usr/bin:/bin',ELECTRON_RUN_AS_NODE:'1'}});
+ const resultPath=receiptPath+'.result.json';
+ await new Promise((resolve,reject)=>{
+ let buffer='',done=false;
+ const timer=setTimeout(()=>finish(new Error('Install helper readiness was not acknowledged; current app stays open')),readyTimeoutMs);
+ const finish=error=>{if(done)return;done=true;clearTimeout(timer);if(error)reject(error);else resolve();};
+ child.once('error',finish);child.once('exit',()=>finish(new Error('Install helper exited before readiness acknowledgement')));
+ child.stderr?.on('data',()=>{});
+ child.stdout.on('data',chunk=>{
+ buffer+=chunk.toString('utf8');if(buffer.length>65536)return finish(new Error('Install helper readiness packet exceeded bounds'));
+ let end;
+ while((end=buffer.indexOf('\n'))>=0){
+ const line=buffer.slice(0,end);buffer=buffer.slice(end+1);
+ if(!line.startsWith(READY))continue;
+ try{const packet=JSON.parse(line.slice(READY.length));
+ if(packet.pid!==child.pid||packet.current_pid!==currentPid||packet.version!==prepared.receipt.target_version||packet.archive_sha256!==prepared.receipt.archive_sha256)throw new Error('Install helper readiness identity differs');
+ finish();
+ }catch(error){finish(error);}
+ }
+ });
+ });
+ child.stdout.unref?.();child.stderr.unref?.();child.unref();
+ return {ready:true,helperPid:child.pid,resultPath};
+}
+async function restoreTestingPriorBundle({app,manifest,prepareQuit,authorizeQuit,run=runFile,spawnHelper=spawn}){
+ if(!app?.isPackaged)throw new Error('Restore requires the installed unsigned testing application');
+ const executable=await fs.realpath(app.getPath('exe')),installed=enclosingApp(executable);
+ const cache=path.join(app.getPath('userData'),'updates','unsigned-testing');
+ const prior=await privateReceipt(path.join(cache,'previous.json'),{previous:true});
+ const currentPath=path.join(installed,'Contents/Resources/runtime/runtime-manifest.json');
+ if(prior.install_path!==installed||prior.current_version!==manifest.application_version)throw new Error('Retained previous app does not belong to this installed version');
+ const identity=await processCreationIdentity(process.pid,executable,run);
+ if(!identity.alive||await fs.realpath(identity.executable)!==executable)throw new Error('Current app process identity differs');
+ const receiptPath=path.join(cache,'restore-'+randomUUID()+'.json');
+ await atomicPrivateJSON(receiptPath,{format:'rieke-unsigned-testing-update',version:1,channel:'unsigned-testing',identifier:APP_ID,validated:true,operation:'restore',
+ install_path:installed,current_executable:executable,current_pid:process.pid,current_created_at:identity.created_at,
+ current_version:manifest.application_version,target_version:prior.previous_version,current_manifest_sha256:await sha256(currentPath),
+ runtime_manifest_sha256:prior.previous_manifest_sha256,bundle_path:prior.previous_path,bundle_sha256:prior.previous_bundle_sha256,archive_path:null,archive_sha256:null});
+ await validatePrepared({receiptPath,currentExecutable:executable,run});
+ const result=await prepareQuit();if(result?.ready!==true)return result;
+ const prepared=await launchTestingInstall({receiptPath,currentExecutable:executable,currentPid:process.pid,run,spawnHelper});
+ authorizeQuit();app.quit();return {...prepared,restoring:true};
+}
+async function main(){
+ if(process.env.ELECTRON_RUN_AS_NODE!=='1'||process.argv[2]!=='--apply'||process.argv.length!==4)throw new Error('Install helper invocation is invalid');
+ const receiptPath=process.argv[3];
+ try{await applyTestingInstall({receiptPath});}
+ catch(error){
+ try{await privateReceipt(receiptPath);await atomicPrivateJSON(receiptPath+'.result.json',{state:'Deferred',error:'Testing update could not complete; installation and macOS protections were preserved.',error_type:error.name});}catch{}
+ process.exitCode=1;
+ }
+}
+if(require.main===module)main().catch(()=>{process.exitCode=1;});
+module.exports={processCreationIdentity,applyTestingInstall,launchTestingInstall,restoreTestingPriorBundle,sha256,bundleDigest};
diff --git a/desktop/testing-update-validation.cjs b/desktop/testing-update-validation.cjs
new file mode 100644
index 0000000..c209b1e
--- /dev/null
+++ b/desktop/testing-update-validation.cjs
@@ -0,0 +1,127 @@
+'use strict';
+const physicalFS=require('./physical-fs.cjs');
+const fs=physicalFS.promises,path=require('node:path'),crypto=require('node:crypto');
+const {constants}=physicalFS;
+const {promisify}=require('node:util');
+const runFile=promisify(require('node:child_process').execFile);
+const {compareVersions,compatibleMacMinimum,verifyResources,ARCHIVE_CHECK}=require('./updater-validation.cjs');
+const REPOSITORY='maxwellsdm1867/Rieke-OS';
+function approvedURL(value,kind='asset',redirect=false){
+ const url=new URL(value);
+ if(url.protocol!=='https:'||url.username||url.password||url.hash||(url.port&&url.port!=='443'))throw new Error('Only public GitHub HTTPS downloads are accepted.');
+ if(kind==='api'){
+ if(url.hostname!=='api.github.com'||url.pathname!==`/repos/${REPOSITORY}/releases`)throw new Error('Unexpected release API.');
+ }else if(url.hostname==='github.com'){
+ if(!url.pathname.startsWith(`/${REPOSITORY}/releases/download/`)||url.search||decodeURIComponent(url.pathname).split('/').some(p=>p==='..'||p==='.')||/%2f|%5c/i.test(url.pathname))throw new Error('Unexpected release asset.');
+ }else if(!redirect||!['release-assets.githubusercontent.com','objects.githubusercontent.com','github-releases.githubusercontent.com'].includes(url.hostname))throw new Error('Unexpected asset redirect.');
+ return true;
+}
+function validateDescriptor(value,current,hostVersion){
+ if(!value||value.format!=='rieke-desktop-test-release'||value.version!==1||value.channel!=='unsigned-testing'||value.repository!==REPOSITORY)throw new Error('Unexpected testing release provenance.');
+ if(compareVersions(value.application_version,current.application_version)<=0)throw new Error('Testing update must be newer than the installed app.');
+ if(value.platform!=='darwin'||value.architecture!=='arm64'||current.platform!==value.platform||current.architecture!==value.architecture)throw new Error('Unsupported testing platform.');
+ if(value.mysql_version!==current.mysql_version||value.database_compatibility!==current.database_compatibility||!Array.isArray(value.workspace_formats)||!Array.isArray(current.workspace_formats)||!current.workspace_formats.every(f=>value.workspace_formats.includes(f)))throw new Error('Testing update requires a data migration.');
+ compatibleMacMinimum(value.minimum_macos_version,hostVersion);
+ const archive=value.archive;
+ if(!archive||archive.filename!==`Rieke-OS-${value.application_version}-arm64.zip`||!Number.isSafeInteger(archive.size)||archive.size<=0||archive.size>2*1024**3||!/^[a-f0-9]{64}$/.test(archive.sha256||'')||!/^[A-Za-z0-9+/]{86}==$/.test(archive.sha512||'')||Buffer.from(archive.sha512,'base64').length!==64)throw new Error('Invalid testing archive metadata.');
+ if(!/^[a-f0-9]{64}$/.test(value.asar_sha256||'')||!/^[a-f0-9]{64}$/.test(value.runtime_manifest_sha256||''))throw new Error('Incomplete testing app checksums.');
+ return value;
+}
+async function hashFile(file,algorithm='sha256'){
+ const handle=await fs.open(file,constants.O_RDONLY|constants.O_NOFOLLOW);
+ try{
+ const stat=await handle.stat();if(!stat.isFile())throw new Error('Expected a regular file.');
+ const hash=crypto.createHash(algorithm);for await(const chunk of handle.createReadStream({autoClose:false}))hash.update(chunk);
+ return hash.digest(algorithm==='sha512'?'base64':'hex');
+ }finally{await handle.close();}
+}
+async function verifyArchive(file,descriptor){
+ const info=await fs.lstat(file);
+ if(!info.isFile()||info.isSymbolicLink()||info.uid!==process.getuid()||info.size!==descriptor.archive.size)throw new Error('Prepared archive size or ownership changed.');
+ if(await hashFile(file)!==descriptor.archive.sha256||await hashFile(file,'sha512')!==descriptor.archive.sha512)throw new Error('Testing archive checksum failed.');
+}
+async function ensurePrivateCache(userData){
+ await fs.mkdir(userData,{recursive:true,mode:0o700});
+ let directory=userData;
+ for(const part of ['', 'updates','unsigned-testing']){
+ if(part)directory=path.join(directory,part);
+ await fs.mkdir(directory,{recursive:true,mode:0o700});
+ const stat=await fs.lstat(directory);
+ if(!stat.isDirectory()||stat.isSymbolicLink()||stat.uid!==process.getuid())throw new Error('Update cache must be owned and contain no directory links.');
+ if(part)await fs.chmod(directory,0o700);
+ }
+ return directory;
+}
+async function regularContained(bundle,relative){
+ const file=path.join(bundle,relative),stat=await fs.lstat(file),resolved=await fs.realpath(file),root=await fs.realpath(bundle);
+ if(!stat.isFile()||stat.isSymbolicLink()||!resolved.startsWith(root+path.sep))throw new Error('App control files must be regular contained files.');
+ let parent=path.dirname(file);
+ while(parent!==bundle){if((await fs.lstat(parent)).isSymbolicLink())throw new Error('App control directories cannot be links.');parent=path.dirname(parent);}
+ return file;
+}
+async function inspectTestingBundle({bundle,descriptor,manifest,hostVersion,run=runFile,bundleDigest}){
+ validateDescriptor(descriptor,manifest,hostVersion);
+ const root=await fs.realpath(bundle);
+ if((await fs.lstat(bundle)).isSymbolicLink())throw new Error('Candidate app cannot be a symlink.');
+ async function links(directory){
+ for(const entry of await fs.readdir(directory,{withFileTypes:true})){
+ const file=path.join(directory,entry.name);
+ if(entry.isSymbolicLink()){
+ const target=await fs.readlink(file),physical=await fs.realpath(file);
+ if(path.isAbsolute(target)||!physical.startsWith(root+path.sep))throw new Error('App has an escaping link.');
+ }else if(entry.isDirectory())await links(file);
+ else if(!entry.isFile())throw new Error('App has a special filesystem node.');
+ }
+ }
+ await links(bundle);
+ const plist=await regularContained(bundle,'Contents/Info.plist');
+ const entry=await regularContained(bundle,'Contents/MacOS/Rieke OS');
+ if(!((await fs.stat(entry)).mode&0o111))throw new Error('App entry is not executable.');
+ const asar=await regularContained(bundle,'Contents/Resources/app.asar');
+ const manifestPath=await regularContained(bundle,'Contents/Resources/runtime/runtime-manifest.json');
+ if(await hashFile(asar)!==descriptor.asar_sha256||await hashFile(manifestPath)!==descriptor.runtime_manifest_sha256)throw new Error('Testing app identity checksums differ.');
+ const candidate=JSON.parse(await fs.readFile(manifestPath,'utf8'));
+ if(candidate.format!=='rieke-desktop-runtime'||candidate.version!==1||candidate.application_version!==descriptor.application_version||candidate.platform!==descriptor.platform||candidate.architecture!==descriptor.architecture||candidate.mysql_version!==descriptor.mysql_version||candidate.database_compatibility!==descriptor.database_compatibility||JSON.stringify(candidate.workspace_formats)!==JSON.stringify(descriptor.workspace_formats)||candidate.minimum_macos_version!==descriptor.minimum_macos_version||!/^[a-f0-9]{40}$/.test(candidate.source_commit||'')||!/^[a-f0-9]{40}$/.test(candidate.parser_commit||'')||typeof candidate.source_dirty!=='boolean'||!candidate.resources||typeof candidate.resources!=='object'||Array.isArray(candidate.resources)||!Object.keys(candidate.resources).length)throw new Error('Testing runtime differs from its descriptor.');
+ for(const [key,expected] of [['CFBundleIdentifier','org.riekeos.desktop'],['CFBundleShortVersionString',descriptor.application_version],['LSMinimumSystemVersion',descriptor.minimum_macos_version]]){
+ const result=await run('/usr/libexec/PlistBuddy',['-c',`Print :${key}`,plist]);
+ if(result.stdout.trim()!==expected)throw new Error('App property list differs from testing metadata.');
+ }
+ const runtime=path.join(bundle,'Contents/Resources/runtime');
+ for(const relative of ['python/bin/python3.11','mysql/bin/mysqld','application/python/workspace_desktop.py','frontend/index.html']){
+ if(!candidate.resources[relative])throw new Error('Required private runtime entry is absent.');
+ await regularContained(bundle,'Contents/Resources/runtime/'+relative);
+ }
+ for(const relative of ['python/bin/python3.11','mysql/bin/mysqld'])if(candidate.resources[relative].executable!==true)throw new Error('Private native runtime entry is not executable.');
+ const releasePath=await regularContained(bundle,'Contents/Resources/runtime/application/rieke-release.json');
+ const sourcePath=await regularContained(bundle,'Contents/Resources/runtime/application/python/workspace-source.json');
+ const release=JSON.parse(await fs.readFile(releasePath,'utf8')),source=JSON.parse(await fs.readFile(sourcePath,'utf8'));
+ if(release.version!==candidate.application_version||release.database_compatibility!==candidate.database_compatibility||JSON.stringify(release.workspace_formats)!==JSON.stringify(candidate.workspace_formats)||source.commit!==candidate.parser_commit||source.python!==candidate.python_version)throw new Error('Runtime compatibility differs from application declarations.');
+ await verifyResources(runtime,candidate.resources);
+ // Structural integrity accepts an ad-hoc testing seal; this is not a
+ // Developer ID, notarization, or Gatekeeper authorization assertion.
+ await run('/usr/bin/codesign',['--verify','--deep','--strict',bundle],{timeout:180000});
+ bundleDigest||=require('./testing-install.cjs').bundleDigest;
+ return {version:descriptor.application_version,bundle_path:bundle,bundle_sha256:await bundleDigest(bundle),runtime_manifest_sha256:descriptor.runtime_manifest_sha256,validated:true,source_dirty:candidate.source_dirty,trust:'official-repository-https-checksums',developer_id_verified:false,native_staging_verified:false,startup_health_verified:false};
+}
+async function validateTestingCandidate({downloadedFile,descriptor,manifest,cacheDirectory,installedBundle,hostVersion,run=runFile,bundleDigest}){
+ await verifyArchive(downloadedFile,descriptor);
+ const temporary=await fs.mkdtemp(path.join(cacheDirectory,'candidate-'));
+ try{
+ const python=path.join(installedBundle,'Contents/Resources/runtime/python/bin/python3.11');
+ // Only the current trusted Python parses the ZIP. No candidate code executes.
+ await run(python,['-I','-B','-c',ARCHIVE_CHECK,downloadedFile],{timeout:30000});
+ await run('/usr/bin/ditto',['-x','-k',downloadedFile,temporary],{timeout:180000});
+ const entries=await fs.readdir(temporary);
+ if(entries.length!==1||entries[0]!=='Rieke OS.app')throw new Error('Testing archive must contain exactly Rieke OS.app.');
+ const validated=await inspectTestingBundle({bundle:path.join(temporary,entries[0]),descriptor,manifest,hostVersion,run,bundleDigest});
+ await verifyArchive(downloadedFile,descriptor);
+ return {...validated,downloadedFile,archive_sha256:descriptor.archive.sha256,candidate_directory:temporary};
+ }catch(error){await fs.rm(temporary,{recursive:true,force:true});throw error;}
+}
+async function revalidateTestingCandidate({candidate,descriptor,manifest,hostVersion,run=runFile,bundleDigest}){
+ await verifyArchive(candidate.downloadedFile,descriptor);
+ const result=await inspectTestingBundle({bundle:candidate.bundle_path,descriptor,manifest,hostVersion,run,bundleDigest});
+ if(result.bundle_sha256!==candidate.bundle_sha256)throw new Error('Prepared app bundle changed.');
+ return result;
+}
+module.exports={REPOSITORY,approvedURL,validateDescriptor,hashFile,verifyArchive,ensurePrivateCache,inspectTestingBundle,validateTestingCandidate,revalidateTestingCandidate};
diff --git a/desktop/testing-updater.cjs b/desktop/testing-updater.cjs
new file mode 100644
index 0000000..59314fd
--- /dev/null
+++ b/desktop/testing-updater.cjs
@@ -0,0 +1,250 @@
+'use strict';
+const physicalFS=require('./physical-fs.cjs');
+const fs=physicalFS.promises,path=require('node:path'),crypto=require('node:crypto'),https=require('node:https');
+const {constants}=physicalFS;
+const {promisify}=require('node:util');
+const runFile=promisify(require('node:child_process').execFile);
+const {compareVersions}=require('./updater-validation.cjs');
+const {REPOSITORY,approvedURL,validateDescriptor,verifyArchive,ensurePrivateCache,validateTestingCandidate,revalidateTestingCandidate,hashFile}=require('./testing-update-validation.cjs');
+const API=`https://api.github.com/repos/${REPOSITORY}/releases?per_page=100&page=1`;
+async function atomicHint(cache,value){
+ const temporary=path.join(cache,`prepared-${crypto.randomUUID()}.tmp`),file=path.join(cache,'prepared.json');
+ const handle=await fs.open(temporary,'wx',0o600);
+ try{await handle.writeFile(JSON.stringify(value)+'\n');await handle.sync();}finally{await handle.close();}
+ try{await fs.rename(temporary,file);const parent=await fs.open(cache,'r');try{await parent.sync();}finally{await parent.close();}}
+ finally{await fs.rm(temporary,{force:true});}
+}
+async function hintPath(cache,relative,kind,filename){
+ const parts=typeof relative==='string'?relative.split('/'):[];
+ if(parts.length!==2||!new RegExp(`^${kind}-[a-zA-Z0-9]{6}$`).test(parts[0])||parts[1]!==filename)throw new Error('Invalid prepared-cache hint.');
+ const parent=path.join(cache,parts[0]),file=path.join(parent,parts[1]);
+ for(const item of [parent,file]){const stat=await fs.lstat(item);if(stat.isSymbolicLink()||stat.uid!==process.getuid())throw new Error('Prepared cache link or ownership differs.');}
+ if(!((await fs.lstat(parent)).isDirectory())||!((await fs.realpath(file)).startsWith((await fs.realpath(cache))+path.sep)))throw new Error('Prepared cache escapes its owned directory.');
+ return file;
+}
+async function cleanupObsolete(cache,keep=[]){
+ // Age protects another still-completing installation. No receipts, previous
+ // bundles, user-selected paths, or non-cache directories are ever removed.
+ const retained=new Set(keep.map(file=>path.resolve(file)));
+ for(const name of await fs.readdir(cache)){
+ if(!/^(?:download|candidate)-[a-zA-Z0-9]{6}$/.test(name))continue;
+ const file=path.join(cache,name),info=await fs.lstat(file);
+ if(!retained.has(file)&&info.isDirectory()&&!info.isSymbolicLink()&&info.uid===process.getuid()&&Date.now()-info.mtimeMs>24*3600000)await fs.rm(file,{recursive:true,force:true});
+ }
+}
+function httpsTransport(url,{kind='asset',redirects=0}={}){
+ approvedURL(url,kind,redirects>0);
+ return new Promise((resolve,reject)=>{
+ const request=https.get(url,{headers:{'User-Agent':'Rieke-OS-desktop-testing-updater','Accept':kind==='api'?'application/vnd.github+json':'application/octet-stream'}},response=>{
+ if([301,302,303,307,308].includes(response.statusCode)){
+ response.resume();
+ if(redirects>=5||!response.headers.location)return reject(new Error('Release redirect limit.'));
+ let next;try{next=new URL(response.headers.location,url).href;approvedURL(next,kind,true);}catch(error){return reject(error);}
+ resolve(httpsTransport(next,{kind,redirects:redirects+1}));
+ }else resolve({statusCode:response.statusCode,headers:response.headers,body:response});
+ });
+ request.setTimeout(30000,()=>request.destroy(new Error('Testing update network timeout.')));
+ request.on('error',reject);
+ });
+}
+async function responseFor(transport,url,kind){
+ approvedURL(url,kind);
+ const response=await transport(url,{kind});
+ if(response.statusCode!==200){response.body?.destroy?.();throw new Error('Release server did not return an asset.');}
+ return response;
+}
+async function jsonAt(transport,url,kind='asset'){
+ const response=await responseFor(transport,url,kind),chunks=[];let size=0;
+ for await(const chunk of response.body){size+=chunk.length;if(size>1024*1024){response.body.destroy?.();throw new Error('Release metadata exceeds bounds.');}chunks.push(chunk);}
+ if(response.headers?.['content-length']&&Number(response.headers['content-length'])!==size)throw new Error('Release metadata truncated.');
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
+}
+function assetURL(asset,tag,name){
+ const expected=`https://github.com/${REPOSITORY}/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(name)}`;
+ if(!asset||asset.name!==name||asset.browser_download_url!==expected)throw new Error('Release asset is not in the official repository.');
+ approvedURL(expected,'asset');return expected;
+}
+function createTestingUpdateCoordinator({app,manifest,distribution,publishStatus=()=>{},prepareQuit,authorizeQuit=()=>{},revokeQuit=()=>{},onInstallationFailure=()=>{},installedBundle,transport=httpsTransport,verifyCandidate=validateTestingCandidate,revalidateCandidate=revalidateTestingCandidate,installHelper,processIdentity,hostVersion,timers=globalThis,random=Math.random,enabled}){
+ installedBundle||=path.resolve(app.getPath('exe'),'../../..');
+ let status={state:'Current',installed:manifest.application_version,available:null,channel:'unsigned-testing',manual_updates:true,can_download:false,can_restart:false,developer_id_verified:false,message:'Using the installed testing version.'};
+ let active=false,stopped=false,timer=null,checking=null,downloading=null,installing=null,pending=null,offered=null;
+ let receiptWrites=Promise.resolve();
+ function set(state,fields={}){
+ status={...status,...fields,state,can_download:state==='Available'&&Boolean(offered),can_restart:state==='Ready'&&Boolean(pending)};publishStatus({...status});
+ const snapshot={format:'rieke-desktop-testing-update-status',version:1,installed:status.installed,available:status.available,state,checked_at:status.checked_at||null};
+ receiptWrites=receiptWrites.catch(()=>{}).then(async()=>{
+ const cache=await ensurePrivateCache(app.getPath('userData'));
+ const temporary=path.join(cache,`status-${crypto.randomUUID()}.tmp`);
+ await fs.writeFile(temporary,JSON.stringify(snapshot)+'\n',{mode:0o600,flag:'wx'});
+ await fs.rename(temporary,path.join(cache,'status.json'));
+ }).catch(()=>{});
+ return {...status};
+ }
+ function deferred(message){return set(pending?'Ready':offered?'Available':'Deferred',{message,check_error:message});}
+ async function forgetHint(){try{const cache=await ensurePrivateCache(app.getPath('userData'));await fs.rm(path.join(cache,'prepared.json'),{force:true});}catch{}}
+ async function persistPrepared(cache,candidate,descriptor){
+ const hint={format:'rieke-desktop-testing-prepared-hint',version:1,installed_version:manifest.application_version,
+ available_version:candidate.version,archive_relative_path:path.relative(cache,candidate.downloadedFile).split(path.sep).join('/'),
+ bundle_relative_path:path.relative(cache,candidate.bundle_path).split(path.sep).join('/'),
+ archive_sha256:descriptor.archive.sha256,bundle_sha256:candidate.bundle_sha256,runtime_manifest_sha256:descriptor.runtime_manifest_sha256};
+ await hintPath(cache,hint.archive_relative_path,'download',descriptor.archive.filename);
+ await hintPath(cache,hint.bundle_relative_path,'candidate','Rieke OS.app');
+ await atomicHint(cache,hint);
+ }
+ async function resumePrepared(selected){
+ const cache=await ensurePrivateCache(app.getPath('userData')),file=path.join(cache,'prepared.json');
+ let candidate=null;
+ try{
+ const handle=await fs.open(file,constants.O_RDONLY|constants.O_NOFOLLOW);
+ let hint;
+ try{const stat=await handle.stat();if(!stat.isFile()||stat.uid!==process.getuid()||(stat.mode&0o077)||stat.size>65536)throw new Error('Invalid prepared hint ownership.');hint=JSON.parse(await handle.readFile('utf8'));}finally{await handle.close();}
+ const descriptor=selected.descriptor;
+ if(hint.format!=='rieke-desktop-testing-prepared-hint'||hint.version!==1||hint.installed_version!==manifest.application_version||hint.available_version!==descriptor.application_version||hint.archive_sha256!==descriptor.archive.sha256||hint.runtime_manifest_sha256!==descriptor.runtime_manifest_sha256||!/^[a-f0-9]{64}$/.test(hint.bundle_sha256||''))throw new Error('Prepared hint differs from fresh official metadata.');
+ const downloadedFile=await hintPath(cache,hint.archive_relative_path,'download',descriptor.archive.filename);
+ const bundle=await hintPath(cache,hint.bundle_relative_path,'candidate','Rieke OS.app');
+ candidate={version:descriptor.application_version,downloadedFile,bundle_path:bundle,bundle_sha256:hint.bundle_sha256,
+ archive_sha256:descriptor.archive.sha256,runtime_manifest_sha256:descriptor.runtime_manifest_sha256,validated:true,descriptor,candidate_directory:path.dirname(bundle)};
+ set('Validating',{message:'Rechecking the previously downloaded testing update against current GitHub metadata.'});
+ await verifyArchive(downloadedFile,descriptor);
+ const checked=await revalidateCandidate({candidate,descriptor,manifest,hostVersion});
+ if(stopped)throw new Error('Updater stopped.');
+ pending={...candidate,source_dirty:checked?.source_dirty};
+ set('Ready',{source_dirty:pending.source_dirty,trust:'official-repository-https-checksums',developer_id_verified:false,message:'Previously downloaded unsigned testing update verified again. Restart to update when ready.'});
+ await cleanupObsolete(cache,[path.dirname(downloadedFile),path.dirname(bundle)]).catch(()=>{});
+ return true;
+ }catch{if(!stopped)await forgetHint();return false;}
+ }
+ function schedule(){
+ if(stopped||!active)return;
+ timer=timers.setTimeout(async()=>{await check();schedule();},Math.round(3600000*(0.9+random()*0.2)));timer?.unref?.();
+ }
+ async function check(){
+ if(!active||stopped||pending||downloading||installing)return {...status};
+ if(checking)return checking;
+ checking=(async()=>{
+ set('Checking',{checked_at:new Date().toISOString(),check_error:null,message:'Checking the official GitHub testing releases.'});
+ try{
+ const releases=await jsonAt(transport,API,'api');
+ if(!Array.isArray(releases)||releases.length>100)throw new Error('Invalid testing release list.');
+ const relevant=releases.filter(release=>{
+ if(release?.draft||typeof release.tag_name!=='string')return false;
+ const match=/^(?:v|desktop-test-v)(\d+\.\d+\.\d+)$/.exec(release.tag_name);
+ try{return match&&compareVersions(match[1],manifest.application_version)>0;}catch{return false;}
+ }).sort((a,b)=>compareVersions(b.tag_name.replace(/^(?:v|desktop-test-v)/,''),a.tag_name.replace(/^(?:v|desktop-test-v)/,'')));
+ let candidate=null,rejections=0;
+ for(const release of relevant.slice(0,20)){
+ const assets=release.assets;
+ if(!Array.isArray(assets))continue;
+ const descriptors=assets.filter(asset=>asset?.name==='desktop-release.json');
+ if(descriptors.length!==1)continue;
+ try{
+ const descriptor=validateDescriptor(await jsonAt(transport,assetURL(descriptors[0],release.tag_name,'desktop-release.json')),manifest,hostVersion);
+ if(release.tag_name!==`v${descriptor.application_version}`&&release.tag_name!==`desktop-test-v${descriptor.application_version}`)throw new Error('Release tag differs from app version.');
+ const archives=assets.filter(asset=>asset?.name===descriptor.archive.filename);
+ if(archives.length!==1||archives[0].size!==descriptor.archive.size)throw new Error('Archive metadata differs from descriptor.');
+ candidate={descriptor,url:assetURL(archives[0],release.tag_name,descriptor.archive.filename),release_url:`https://github.com/${REPOSITORY}/releases/tag/${encodeURIComponent(release.tag_name)}`};break;
+ }catch{rejections++;}
+ }
+ if(stopped)return {...status};
+ if(candidate){
+ offered=candidate;set('Available',{available:candidate.descriptor.application_version,release_url:candidate.release_url,source_dirty:null,message:`Rieke OS ${candidate.descriptor.application_version} testing update is available. Download when ready.`,check_error:null});
+ if(!await resumePrepared(candidate)&&!stopped)set('Available',{message:'Testing update available. Download when ready; any missing or changed cached update will be replaced.'});
+ }
+ else if(rejections)deferred('Published testing update metadata was rejected. The installed app remains usable.');
+ else{offered=null;set('Current',{available:null,message:'The installed testing version is current.',check_error:null});}
+ }catch{deferred('Testing update check unavailable. The installed app remains usable.');}
+ return {...status};
+ })().finally(()=>{checking=null;});return checking;
+ }
+ async function start(){
+ if(active||stopped)return {...status};
+ if(enabled===false||!app.isPackaged||process.platform!=='darwin'||process.arch!=='arm64'||distribution?.format!=='rieke-desktop-distribution'||distribution.version!==1||distribution.channel!=='unsigned-testing'||distribution.repository!==REPOSITORY)return set('Deferred',{message:'This distribution does not enable GitHub testing updates.'});
+ try{
+ hostVersion||=(await runFile('/usr/bin/sw_vers',['-productVersion'])).stdout.trim();
+ const cache=await ensurePrivateCache(app.getPath('userData'));
+ // Disk status is a display hint, never authority to install or restore.
+ try{const hint=JSON.parse(await fs.readFile(path.join(cache,'status.json'),'utf8'));if(hint.format==='rieke-desktop-testing-update-status'&&hint.installed===manifest.application_version)status.available=hint.available;}catch{}
+ }catch{return set('Deferred',{message:'The testing update cache is unavailable.'});}
+ active=true;await check();schedule();return {...status};
+ }
+ async function download(){
+ if(!active||stopped)return {...status};
+ if(downloading)return downloading;
+ if(installing||pending)return {...status};
+ if(checking)await checking;
+ if(pending)return {...status};
+ if(!offered)return {...status};
+ const selected=offered;
+ downloading=(async()=>{
+ let directory=null,candidate=null;
+ try{
+ set('Downloading',{progress:0,check_error:null,message:'Downloading the selected unsigned testing update.'});
+ const cache=await ensurePrivateCache(app.getPath('userData'));
+ directory=await fs.mkdtemp(path.join(cache,'download-'));
+ const file=path.join(directory,selected.descriptor.archive.filename);
+ const response=await responseFor(transport,selected.url,'asset');
+ if(response.headers?.['content-length']&&Number(response.headers['content-length'])!==selected.descriptor.archive.size){response.body.destroy?.();throw new Error('Archive length differs.');}
+ const output=await fs.open(file,constants.O_WRONLY|constants.O_CREAT|constants.O_EXCL|constants.O_NOFOLLOW,0o600);
+ let size=0,lastPercent=-1,lastPublished=0;
+ try{
+ for await(const chunk of response.body){
+ size+=chunk.length;if(stopped||size>selected.descriptor.archive.size){response.body.destroy?.();throw new Error('Archive download interrupted or oversized.');}
+ await output.writeFile(chunk);
+ const percent=Math.min(100,Math.floor(100*size/selected.descriptor.archive.size)),now=Date.now();
+ if(percent!==lastPercent||now-lastPublished>=200){set('Downloading',{progress:percent});lastPercent=percent;lastPublished=now;}
+ }
+ await output.sync();
+ }finally{await output.close();}
+ await verifyArchive(file,selected.descriptor);
+ set('Validating',{progress:100,message:'Checking the testing app checksums and data compatibility.'});
+ candidate=await verifyCandidate({downloadedFile:file,descriptor:selected.descriptor,manifest,cacheDirectory:cache,installedBundle,hostVersion});
+ if(stopped)throw new Error('Updater stopped.');
+ await persistPrepared(cache,candidate,selected.descriptor);
+ pending={...candidate,descriptor:selected.descriptor};
+ set('Ready',{available:candidate.version,source_dirty:candidate.source_dirty,trust:'official-repository-https-checksums',developer_id_verified:false,message:'Unsigned testing update verified. Restart to update when your work is saved.'});
+ await cleanupObsolete(cache,[directory,candidate.candidate_directory||path.dirname(candidate.bundle_path)]).catch(()=>{});
+ }catch{
+ if(candidate?.candidate_directory)await fs.rm(candidate.candidate_directory,{recursive:true,force:true});
+ if(directory)await fs.rm(directory,{recursive:true,force:true});
+ pending=null;await forgetHint();deferred('Testing update download or validation failed. You can retry the download.');
+ }
+ return {...status};
+ })().finally(()=>{downloading=null;});return downloading;
+ }
+ async function verifyPrepared(){
+ await ensurePrivateCache(app.getPath('userData'));
+ await verifyArchive(pending.downloadedFile,pending.descriptor);
+ await revalidateCandidate({candidate:pending,descriptor:pending.descriptor,manifest,hostVersion});
+ }
+ async function installPrepared(){
+ if(installing)return installing;
+ if(downloading)await downloading;
+ if(!active||stopped||!pending||status.state!=='Ready')return{ready:false,reason:'No verified testing update is prepared.'};
+ installing=(async()=>{
+ let drained=false;
+ try{
+ await verifyPrepared();set('Draining',{message:'Saving drafts and closing scientific services before updating.'});
+ const result=await prepareQuit();
+ if(result?.ready!==true){set('Ready',{message:'Testing update remains pending until drafts, writers and services close.'});return{ready:false,reason:result?.reason||status.message};}
+ drained=true;await verifyPrepared();if(stopped)throw new Error('Updater stopped during drain.');
+ const helper=(!processIdentity||!installHelper)?require('./testing-install.cjs'):{};
+ const identity=await (processIdentity||helper.processCreationIdentity)(process.pid,app.getPath('exe'));
+ const cache=await ensurePrivateCache(app.getPath('userData'));
+ const receiptPath=path.join(cache,`install-${crypto.randomUUID()}.json`);
+ const receipt={format:'rieke-unsigned-testing-update',version:1,channel:'unsigned-testing',identifier:'org.riekeos.desktop',operation:'update',install_path:installedBundle,current_executable:typeof identity==='object'?identity.executable:app.getPath('exe'),current_pid:process.pid,current_created_at:typeof identity==='object'?identity.created_at:identity,current_version:manifest.application_version,current_manifest_sha256:await hashFile(path.join(installedBundle,'Contents/Resources/runtime/runtime-manifest.json')),target_version:pending.version,runtime_manifest_sha256:pending.runtime_manifest_sha256,archive_path:pending.downloadedFile,archive_sha256:pending.archive_sha256,bundle_path:pending.bundle_path,bundle_sha256:pending.bundle_sha256,validated:true};
+ // Version is separately named because receipt.version identifies schema.
+ await fs.writeFile(receiptPath,JSON.stringify(receipt)+'\n',{mode:0o600,flag:'wx'});
+ set('Installing',{message:'Handing the verified unsigned testing app to the owned installer.'});
+ const handoff=await (installHelper||helper.launchTestingInstall)({receiptPath,currentExecutable:receipt.current_executable,currentPid:process.pid});
+ if(handoff?.ready===false)throw new Error('Testing installer did not acknowledge readiness.');
+ authorizeQuit();app.quit?.();return{ready:true,installing:true};
+ }catch{
+ revokeQuit();pending=null;await forgetHint();deferred(drained?'Testing update changed or installation failed. Restart the installed app from recovery.':'The prepared testing update changed or is unavailable. Retry the download; the installed app remains usable.');
+ if(drained)onInstallationFailure();return{ready:false,reason:status.message};
+ }
+ })().finally(()=>{installing=null;});return installing;
+ }
+ function stop(){stopped=true;active=false;if(timer)timers.clearTimeout(timer);}
+ return{getStatus:()=>({...status}),start,check,download,installPrepared,stop,flushReceipts:()=>receiptWrites};
+}
+module.exports={createTestingUpdateCoordinator,httpsTransport};
diff --git a/desktop/tests/bootstrap.test.cjs b/desktop/tests/bootstrap.test.cjs
new file mode 100644
index 0000000..0cd43b3
--- /dev/null
+++ b/desktop/tests/bootstrap.test.cjs
@@ -0,0 +1,23 @@
+'use strict';
+const {test} = require('node:test');
+const assert = require('node:assert/strict');
+const {signatureIdentity, assertNotRunning, compatibleManifest, enclosingApp, installCompleteBundle} = require('../bootstrap.cjs');
+test('bootstrap accepts only complete Developer ID signed app with exact identity', async () => {
+ const run = async (_command, args) => ({stdout: '', stderr: args.includes('--display') ? 'Identifier=org.riekeos.desktop\nTeamIdentifier=EXAMPLETEAM\nAuthority=Developer ID Application: Example\n' : ''});
+ assert.deepEqual(await signatureIdentity('/tmp/Rieke OS.app', run), {identifier: 'org.riekeos.desktop', team: 'EXAMPLETEAM'});
+ await assert.rejects(signatureIdentity('/tmp/Rieke OS.app', async () => ({stdout: '', stderr: 'Identifier=org.riekeos.desktop\nTeamIdentifier=not set\nSignature=adhoc'})), /Developer ID/);
+});
+test('any installed app process blocks replacement and only dedicated helper PID is exempt', async () => {
+ const run = async () => ({stdout: ` ${process.pid} /tmp/Rieke OS.app/Contents/MacOS/Rieke OS\n 100 /usr/bin/other\n`});
+ await assert.rejects(assertNotRunning('/tmp/Rieke OS.app', run), /running/);
+ await assertNotRunning('/tmp/Rieke OS.app', run, process.pid);
+ await assert.rejects(installCompleteBundle({source: '/tmp/source.app', allowRollback: false, ignorePid: process.pid}), /dedicated/);
+});
+test('bootstrap refuses MySQL or workspace compatibility changes even in signed apps', () => {
+ const current = {format: 'rieke-desktop-runtime', version: 1, platform: 'darwin', architecture: 'arm64', mysql_version: '8.4.2', workspace_formats: [1], database_compatibility: 1};
+ assert.equal(compatibleManifest(current, current), true);
+ assert.equal(compatibleManifest({...current, mysql_version: '9.0.0'}, current), false);
+ assert.equal(compatibleManifest({...current, workspace_formats: [2]}, current), false);
+ assert.equal(compatibleManifest({...current, database_compatibility: 2}, current), false);
+ assert.equal(enclosingApp('/Applications/Rieke OS.app/Contents/MacOS/Rieke OS'), '/Applications/Rieke OS.app');
+});
diff --git a/desktop/tests/distribution.test.cjs b/desktop/tests/distribution.test.cjs
new file mode 100644
index 0000000..40a768b
--- /dev/null
+++ b/desktop/tests/distribution.test.cjs
@@ -0,0 +1,11 @@
+'use strict';
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const {distributionPolicy} = require('../distribution.cjs');
+test('normal installs retain signed update policy unless an explicit testing build opts in', () => {
+ assert.equal(distributionPolicy().channel, 'signed');
+ assert.equal(distributionPolicy({format:'rieke-desktop-distribution',version:1,channel:'unsigned-testing',repository:'maxwellsdm1867/Rieke-OS'}).channel,'unsigned-testing');
+});
+test('a testing build cannot redirect users to a foreign repository or unknown trust channel', () => {
+ for(const value of [{format:'rieke-desktop-distribution',version:1,channel:'unsigned-testing',repository:'someone/other'}, {format:'rieke-desktop-distribution',version:1,channel:'anything',repository:'maxwellsdm1867/Rieke-OS'}]) assert.throws(()=>distributionPolicy(value));
+});
diff --git a/desktop/tests/draft-barrier.test.cjs b/desktop/tests/draft-barrier.test.cjs
new file mode 100644
index 0000000..36bdf53
--- /dev/null
+++ b/desktop/tests/draft-barrier.test.cjs
@@ -0,0 +1,20 @@
+'use strict';
+const {test} = require('node:test');
+const assert = require('node:assert/strict');
+const {DraftBarrier} = require('../draft-barrier.cjs');
+const window = () => ({isDestroyed: () => false});
+test('every scientific window must acknowledge its own persisted draft before drain', async () => {
+ const requests = []; const barrier = new DraftBarrier({timeout: 50, send: (win, request) => requests.push({win, ...request})});
+ const a = window(), b = window(); const result = barrier.prepare([a, b]);
+ assert.throws(() => barrier.acknowledge({requestId: requests[0].requestId, ok: true}, b));
+ barrier.acknowledge({requestId: requests[0].requestId, ok: true}, a);
+ barrier.acknowledge({requestId: requests[1].requestId, ok: true}, b);
+ assert.deepEqual(await result, {ready: true});
+ assert.equal(barrier.pending.size, 0);
+});
+test('missing, failed and crashed renderer acknowledgements defer quit', async () => {
+ const barrier = new DraftBarrier({timeout: 5, send: () => {}});
+ assert.equal((await barrier.prepare([window()])).ready, false);
+ assert.equal((await barrier.prepare([{isDestroyed: () => false, draftUnavailable: true}])).ready, false);
+ assert.equal((await barrier.prepare([{isDestroyed: () => true}])).ready, false);
+});
diff --git a/desktop/tests/draft-store.test.cjs b/desktop/tests/draft-store.test.cjs
new file mode 100644
index 0000000..ba460d9
--- /dev/null
+++ b/desktop/tests/draft-store.test.cjs
@@ -0,0 +1,30 @@
+'use strict';
+const {test} = require('node:test'); const assert = require('node:assert/strict');
+const fs = require('node:fs/promises'), path = require('node:path'), os = require('node:os');
+const {DraftStore, MAX_DRAFT_BYTES} = require('../draft-store.cjs');
+const payload = () => ({projectId: 'launcher', value: {format: 'rieke-renderer-draft', version: 1, projectId: 'launcher', value: {route: {page: 'overview'}}}});
+async function fixture(t) {const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rieke-drafts-')); t.after(() => fs.rm(root, {recursive: true, force: true})); const store = new DraftStore(root); await store.location('launcher'); return {root, store, filename: path.join(root, 'drafts/launcher.json')};}
+test('missing drafts are ordinary and valid durable schema roundtrips', async t => {
+ const {store} = await fixture(t); assert.equal(await store.load('launcher'), null);
+ await store.save(payload()); assert.deepEqual(await store.load('launcher'), payload().value);
+ await assert.rejects(store.reset('launcher'), /No verified/);
+ await assert.rejects(store.save({projectId: 'launcher', value: {route: 'files'}}), /schema/);
+});
+test('malformed, mismatched and oversized stored drafts block overwrite until explicit preserved reset', async t => {
+ const {store, filename, root} = await fixture(t);
+ for (const bytes of ['{corrupted', JSON.stringify({...payload().value, projectId: 'foreign'}), 'x'.repeat(MAX_DRAFT_BYTES + 1)]) {
+ await fs.writeFile(filename, bytes);
+ assert.equal((await store.load('launcher')).format, 'rieke-draft-recovery');
+ await assert.rejects(store.save(payload()), /recovery decision/); assert.equal(await fs.readFile(filename, 'utf8'), bytes);
+ await store.reset('launcher'); assert.equal(await store.load('launcher'), null);
+ const backups = (await fs.readdir(path.join(root, 'drafts'))).filter(name => name.startsWith('launcher.corrupt-'));
+ assert.ok((await Promise.all(backups.map(name => fs.readFile(path.join(root, 'drafts', name), 'utf8')))).includes(bytes));
+ }
+});
+test('symlink draft never reads target and reset preserves link without changing target', async t => {
+ const {store, filename, root} = await fixture(t); const target = path.join(root, 'private-target'); await fs.writeFile(target, 'keep target'); await fs.symlink(target, filename);
+ assert.equal((await store.load('launcher')).format, 'rieke-draft-recovery'); await assert.rejects(store.save(payload()));
+ await store.reset('launcher'); assert.equal(await fs.readFile(target, 'utf8'), 'keep target');
+ const backup = (await fs.readdir(path.join(root, 'drafts'))).find(name => name.startsWith('launcher.corrupt-'));
+ assert.equal((await fs.lstat(path.join(root, 'drafts', backup))).isSymbolicLink(), true);
+});
diff --git a/desktop/tests/e2e/updater-client.cjs b/desktop/tests/e2e/updater-client.cjs
new file mode 100644
index 0000000..5e4b2c0
--- /dev/null
+++ b/desktop/tests/e2e/updater-client.cjs
@@ -0,0 +1,119 @@
+'use strict';
+// Run with the pinned Electron executable. This exercises its real HTTP
+// executor, pinned MacUpdater parser/downloader and app coordinator against a
+// private fault server. It does not substitute for a signed Squirrel update.
+const {app, autoUpdater: nativeUpdater} = require('electron');
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const os = require('node:os');
+const http = require('node:http');
+const crypto = require('node:crypto');
+const assert = require('node:assert/strict');
+const {MacUpdater} = require('electron-updater');
+const {ElectronHttpExecutor} = require('electron-updater/out/electronHttpExecutor');
+const {createUpdateCoordinator} = require('../../updater.cjs');
+const root = path.resolve(__dirname, '../../..');
+const payload = Buffer.alloc(256 * 1024, 23);
+const unhandled = [];
+process.on('unhandledRejection', error => unhandled.push(error.code || error.name || 'unknown'));
+const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
+async function eventually(predicate, timeout = 10000) {
+ const deadline=Date.now()+timeout;
+ while(Date.now(){
+ requests++;
+ if(request.url.includes('latest-mac.yml')){
+ if(fault==='metadata-unavailable'){response.writeHead(503);response.end('unavailable');return;}
+ response.setHeader('Content-Type','application/yaml');
+ response.end(fault==='malformed-metadata'?'files: [invalid':JSON.stringify(metadata));return;
+ }
+ downloads++;
+ response.writeHead(200,{'Content-Type':'application/zip','Content-Length':payload.length});
+ if(fault==='interrupted-download'||(fault==='interrupted-download-fresh-client'&&downloads===1)){response.write(payload.subarray(0,1024));setTimeout(()=>response.destroy(),25);}
+ else response.end(payload);
+ });
+ await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
+ const feed=`http://127.0.0.1:${server.address().port}/`;
+ const adapter={version:'0.1.0',name:'Rieke updater isolated E2E',isPackaged:true,appUpdateConfigPath:configuration,userDataPath:dir,baseCachePath:dir,
+ whenReady:()=>app.whenReady(),onQuit:()=>{},quit:()=>assert.fail('Client cannot quit app in rejected scenarios'),relaunch:()=>assert.fail('Client cannot relaunch app')};
+ let updater;
+ const states=[];
+ function createClient(){
+ updater=new MacUpdater(undefined,adapter);
+ updater.httpExecutor=new ElectronHttpExecutor(()=>{});
+ updater.logger=null;
+ updater.disableDifferentialDownload=true;
+ const setFeed=updater.setFeedURL.bind(updater);
+ // Substitute only the delivery destination. The production provider arguments
+ // are asserted and every HTTP/metadata/cache operation remains real.
+ updater.setFeedURL=provider=>{
+ assert.equal(provider.provider,'github');assert.equal(provider.owner,'maxwellsdm1867');assert.equal(provider.repo,'Rieke-OS');
+ setFeed({provider:'generic',url:feed});
+ };
+ updater.quitAndInstall=()=>{installs++;assert.fail('Rejected update cannot install');};
+ return createUpdateCoordinator({app:{isPackaged:true,getPath:()=>dir},manifest:{application_version:'0.1.0'},updater,
+ verifyInstalled:async()=>({fixture_only:true}),
+ // The candidate's real production verifier cannot accept an unsigned test
+ // bundle. Transport success must end rejected, with no native installer.
+ verifyCandidate:async()=>{throw new Error('No Developer ID test candidate');},retainPrevious:async()=>assert.fail('Rejected candidate cannot retain/activate'),
+ prepareQuit:async()=>assert.fail('Rejected candidate cannot drain app'),publishStatus:status=>states.push(status.state),
+ timers:{setTimeout:()=>1,clearTimeout:()=>{}}});
+ }
+ const nativeListeners=new Map(['error','update-downloaded'].map(name=>[name,nativeUpdater.listeners(name)]));
+ let coordinator=createClient();
+ const initialUnhandled=unhandled.length;
+ try{
+ await coordinator.start();
+ if(['bad-checksum','interrupted-download','interrupted-download-fresh-client','unsigned-candidate','cache-write-failure'].includes(fault))await eventually(()=>states.includes('Deferred'));
+ await wait(100);
+ if(fault==='interrupted-download-fresh-client'){
+ assert.equal(downloads,1);assert.equal(coordinator.getStatus().state,'Deferred');
+ coordinator.stop();updater.closeServerIfExists();states.length=0;
+ coordinator=createClient();await coordinator.start();
+ await eventually(()=>states.includes('Deferred'));await wait(100);
+ assert.equal(downloads,2,'A fresh real client must fetch complete bytes instead of using the interrupted cache');
+ assert.equal(adapter.version,'0.1.0');
+ }
+ const final=coordinator.getStatus().state;
+ assert.equal(final,['older-version','current-version'].includes(fault)?'Current':'Deferred');
+ assert.equal(installs,0);
+ assert.equal(updater.autoInstallOnAppQuit,false);
+ assert.equal(unhandled.length,initialUnhandled,`Unhandled real downloader rejection for ${fault}`);
+ if(['older-version','current-version','invalid-version','malformed-metadata','metadata-unavailable','cache-write-failure'].includes(fault))assert.equal(downloads,0);
+ report.scenarios.push({name:fault,passed:true,requests,downloads,final_state:final,native_installs:installs});
+ }finally{
+ coordinator.stop();updater.closeServerIfExists();
+ await new Promise(resolve=>server.close(resolve));
+ for(const [event,listeners] of nativeListeners)for(const listener of listeners)nativeUpdater.removeListener(event,listener);
+ }
+}
+async function main(){
+ const base=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-updater-client-e2e-'));
+ await fs.mkdir(path.join(base,'electron-profile'));
+ app.setPath('userData',path.join(base,'electron-profile'));
+ await app.whenReady();
+ const report={format:'rieke-updater-client-e2e',version:1,production_ready:false,library_version:require('electron-updater/package.json').version,
+ scenarios:[],limitations:['Private fixture provider and test app identity; no Developer ID candidate','Real signed Squirrel staging/install/recovery remains unqualified']};
+ try{
+ for(const fault of ['current-version','older-version','metadata-unavailable','malformed-metadata','invalid-version','bad-checksum','interrupted-download','interrupted-download-fresh-client','cache-write-failure','unsigned-candidate'])await scenario(base,fault,report);
+ report.passed=true;
+ }catch(error){report.passed=false;report.failure=error.message;process.exitCode=1;}
+ report.unhandled_rejections=unhandled;
+ await fs.writeFile(path.join(root,'docs/dev/desktop-updater-client-e2e.json'),JSON.stringify(report,null,2)+'\n');
+ console.log(JSON.stringify(report,null,2));
+ app.exit(report.passed?0:1);
+}
+main().catch(error=>{console.error(error.name+': '+error.message);app.exit(1);});
diff --git a/desktop/tests/physical-fs.test.cjs b/desktop/tests/physical-fs.test.cjs
new file mode 100644
index 0000000..d3e47b0
--- /dev/null
+++ b/desktop/tests/physical-fs.test.cjs
@@ -0,0 +1,40 @@
+'use strict';
+const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs/promises'),path=require('node:path'),os=require('node:os'),crypto=require('node:crypto');
+const {promisify}=require('node:util'),run=promisify(require('node:child_process').execFile);
+const asar=require('@electron/asar');
+test('actual Electron validates physical ASAR bytes and removes its cache without virtual-directory traversal',async t=>{
+ const root=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-physical-asar-'));t.after(()=>fs.rm(root,{recursive:true,force:true}));
+ const content=path.join(root,'content'),cache=path.join(root,'candidate'),archive=path.join(cache,'app.asar');
+ await fs.mkdir(content);await fs.mkdir(cache);await fs.writeFile(path.join(content,'package.json'),JSON.stringify({name:'asar-boundary-fixture',version:'1.0.0'}));await fs.writeFile(path.join(content,'payload.txt'),'physical archive byte proof');
+ await asar.createPackage(content,archive);const expected=crypto.createHash('sha256').update(await fs.readFile(archive)).digest('hex');
+ const driver=path.join(root,'driver.cjs');
+ await fs.writeFile(driver,`const assert=require('node:assert/strict'),virtual=require('node:fs'),path=require('node:path');
+const physical=require(process.argv[2]),{hashFile}=require(process.argv[3]);
+(async()=>{const archive=process.argv[4];assert.equal(virtual.lstatSync(archive).isDirectory(),true);assert.equal(physical.lstatSync(archive).isFile(),true);assert.equal(await hashFile(archive),process.argv[5]);await physical.promises.rm(path.dirname(archive),{recursive:true});console.log('RIEKE_PHYSICAL_ASAR_PASS');})().catch(error=>{console.error(error.stack);process.exitCode=1;});`);
+ const result=await run(require('electron'),[driver,path.resolve(__dirname,'../physical-fs.cjs'),path.resolve(__dirname,'../testing-update-validation.cjs'),archive,expected],{timeout:30000,env:{...process.env,ELECTRON_RUN_AS_NODE:'1',HOME:root,PATH:'/usr/bin:/bin'}});
+ assert.match(result.stdout,/RIEKE_PHYSICAL_ASAR_PASS/);await assert.rejects(fs.access(cache));
+});
+test('actual Electron signed candidate rejection cleans extracted ASAR while retaining Developer ID policy',{skip:process.platform!=='darwin'},async t=>{
+ const root=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-signed-asar-reject-'));t.after(()=>fs.rm(root,{recursive:true,force:true}));
+ const content=path.join(root,'content'),bundle=path.join(root,'Rieke OS.app'),resources=path.join(bundle,'Contents/Resources');await fs.mkdir(content);await fs.mkdir(resources,{recursive:true});
+ await fs.writeFile(path.join(content,'never-execute.cjs'),'throw new Error("candidate executed")');await asar.createPackage(content,path.join(resources,'app.asar'));
+ const installed=path.join(root,'Installed.app'),bin=path.join(installed,'Contents/Resources/runtime/python/bin');await fs.mkdir(bin,{recursive:true});
+ let python=path.resolve(__dirname,'../build/runtime/python/bin/python3.11');try{await fs.access(python);}catch{python=(await run('/usr/bin/which',['python3'])).stdout.trim();}
+ await fs.symlink(python,path.join(bin,'python3.11'));
+ const archive=path.join(root,'candidate.zip');await run(python,['-I','-B','-c',String.raw`import sys,zipfile
+from pathlib import Path
+root=Path(sys.argv[1])
+with zipfile.ZipFile(sys.argv[2],'w') as z:
+ for file in root.rglob('*'):
+ if file.is_file():z.write(file,str(file.relative_to(root.parent)))`,bundle,archive]);
+ const driver=path.join(root,'driver.cjs');
+ await fs.writeFile(driver,`const assert=require('node:assert/strict'),path=require('node:path'),fs=require('original-fs').promises,{promisify}=require('node:util');
+const realRun=promisify(require('node:child_process').execFile),{validateDownloadedCandidate}=require(process.argv[2]);
+const installed=process.argv[3],cache=path.join(process.argv[5],'cache');
+let extracted=false;
+(async()=>{await assert.rejects(validateDownloadedCandidate({downloadedFile:process.argv[4],version:'1.1.0',manifest:{application_version:'1.0.0'},cacheDirectory:cache,installedBundle:installed,
+ run:async(exe,args,options)=>{if(exe==='/usr/bin/codesign'){if(args.at(-1)!==installed){extracted=true;throw new Error('untrusted candidate signer');}return {stdout:'',stderr:'TeamIdentifier=TESTTEAM\\nIdentifier=org.riekeos.desktop\\nAuthority=Developer ID Application: simulated current signature for cleanup boundary'};}return realRun(exe,args,options);}}),/untrusted candidate signer/);
+assert.equal(extracted,true);assert.deepEqual(await fs.readdir(cache),[]);console.log('RIEKE_SIGNED_ASAR_REJECT_CLEAN');})().catch(error=>{console.error(error.stack);process.exitCode=1;});`);
+ const result=await run(require('electron'),[driver,path.resolve(__dirname,'../updater-validation.cjs'),installed,archive,root],{timeout:30000,env:{...process.env,ELECTRON_RUN_AS_NODE:'1',HOME:root,PATH:'/usr/bin:/bin'}});
+ assert.match(result.stdout,/RIEKE_SIGNED_ASAR_REJECT_CLEAN/);
+});
diff --git a/desktop/tests/recovery-ui.test.cjs b/desktop/tests/recovery-ui.test.cjs
new file mode 100644
index 0000000..e7e6aa1
--- /dev/null
+++ b/desktop/tests/recovery-ui.test.cjs
@@ -0,0 +1,20 @@
+'use strict';
+const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'),vm=require('node:vm');
+function page(initial){
+ const elements=new Map(),element=id=>{if(!elements.has(id))elements.set(id,{hidden:false,disabled:false,textContent:''});return elements.get(id);};let listener,installs=0;
+ const bridge={status:async()=>initial,onStatus:fn=>{listener=fn;},installAndOpen:async()=>{installs++;return {ready:false,reason:'Another installed app is still running.'};},retryStartup:async()=>{},restorePreviousVersion:async()=>{},quit:async()=>{}};
+ vm.runInNewContext(fs.readFileSync(path.join(__dirname,'../recovery.js'),'utf8'),{window:{riekeDesktop:bridge},document:{getElementById:element}});
+ return {element,get installs(){return installs;},emit:status=>listener(status),settle:()=>new Promise(resolve=>setImmediate(resolve)),click:async id=>element(id).onclick({target:element(id)})};
+}
+test('unsigned bootstrap explains Open Anyway only before installation and requires an explicit Install and Open',async()=>{
+ const h=page({state:'Bootstrap',channel:'unsigned-testing',title:'Install Rieke OS',message:'Install this complete app in your Applications folder and open it.'});await h.settle();
+ assert.equal(h.element('channel').hidden,false);assert.match(h.element('channel').textContent,/Unsigned testing/);
+ assert.equal(h.element('gatekeeper').hidden,false);assert.match(h.element('gatekeeper').textContent,/Open Anyway/);
+ assert.equal(h.installs,0);await h.click('install');assert.equal(h.installs,1);assert.match(h.element('detail').textContent,/still running/);
+ h.emit({state:'Recovery',channel:'unsigned-testing'});assert.equal(h.element('install').hidden,true);assert.equal(h.element('gatekeeper').hidden,true);
+});
+test('signed bootstrap retains its signature guidance and never claims an unsigned exception',async()=>{
+ const h=page({state:'Bootstrap',channel:'signed',detail:'Developer ID signature verification is required.'});await h.settle();
+ assert.equal(h.element('channel').hidden,true);assert.equal(h.element('gatekeeper').hidden,true);
+ assert.equal(h.element('detail').textContent,'Developer ID signature verification is required.');assert.equal(h.installs,0);
+});
diff --git a/desktop/tests/security.test.cjs b/desktop/tests/security.test.cjs
new file mode 100644
index 0000000..8dcd952
--- /dev/null
+++ b/desktop/tests/security.test.cjs
@@ -0,0 +1,27 @@
+'use strict';
+const {test} = require('node:test');
+const assert = require('node:assert/strict');
+const {isOwnedURL, validateSender, validateDraft, approvedReleaseURL} = require('../security.cjs');
+test('only exact authenticated loopback origins and explicit recovery document are trusted', () => {
+ const origins = new Set(['http://127.0.0.1:1234']);
+ assert.equal(isOwnedURL('http://127.0.0.1:1234/api/project', origins), true);
+ for (const url of ['http://localhost:1234/', 'http://127.0.0.1:1235/', 'https://127.0.0.1:1234/', 'javascript:alert(1)', 'file:///tmp/foreign.html']) assert.equal(isOwnedURL(url, origins), false);
+ assert.equal(isOwnedURL('file:///tmp/recovery.html', origins, ['/tmp/recovery.html']), true);
+});
+test('privileged IPC rejects foreign renderer, subframe and origin', () => {
+ const mainFrame = {url: 'http://127.0.0.1:1234/'}; const contents = {mainFrame};
+ const window = {isDestroyed: () => false, webContents: contents};
+ assert.equal(validateSender({sender: contents, senderFrame: mainFrame}, new Set([window]), 'http://127.0.0.1:1234'), window);
+ assert.throws(() => validateSender({sender: {}, senderFrame: mainFrame}, [window], 'http://127.0.0.1:1234'));
+ assert.throws(() => validateSender({sender: contents, senderFrame: {...mainFrame}}, [window], 'http://127.0.0.1:1234'));
+ mainFrame.url = 'https://evil.example/';
+ assert.throws(() => validateSender({sender: contents, senderFrame: mainFrame}, [window], 'http://127.0.0.1:1234'));
+});
+test('drafts reject path traversal, unknown fields, missing JSON and oversize', () => {
+ assert.equal(validateDraft({projectId: 'launcher', value: {route: 'project'}}).projectId, 'launcher');
+ for (const payload of [{projectId: '../../evil', value: {}}, {projectId: 'launcher', value: {}, path: '/tmp'}, {projectId: 'launcher', value: undefined}, {projectId: 'launcher', value: 'x'.repeat(2 * 1024 * 1024)}]) assert.throws(() => validateDraft(payload));
+});
+test('external URLs are restricted to canonical HTTPS release notes', () => {
+ assert.equal(approvedReleaseURL('https://github.com/maxwellsdm1867/Rieke-OS/releases/tag/v0.1.0'), true);
+ for (const url of ['http://github.com/maxwellsdm1867/Rieke-OS/releases', 'https://github.com.evil.example/maxwellsdm1867/Rieke-OS/releases', 'https://github.com/user/other/releases', 'https://user:pass@github.com/maxwellsdm1867/Rieke-OS/releases']) assert.equal(approvedReleaseURL(url), false);
+});
diff --git a/desktop/tests/sign-runtime.test.cjs b/desktop/tests/sign-runtime.test.cjs
new file mode 100644
index 0000000..787bf59
--- /dev/null
+++ b/desktop/tests/sign-runtime.test.cjs
@@ -0,0 +1,22 @@
+'use strict';
+const {test} = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const os = require('node:os');
+const {resealRuntimeManifest} = require('../sign-runtime.cjs');
+const {verifyResources} = require('../updater-validation.cjs');
+test('manifest reseal tracks signed native bytes and internal symlinks while excluding metadata', async t => {
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rieke-seal-')); t.after(() => fs.rm(root, {recursive: true, force: true}));
+ await fs.writeFile(path.join(root, 'runtime-manifest.json'), JSON.stringify({format: 'rieke-desktop-runtime'}));
+ await fs.writeFile(path.join(root, 'runtime-audit.json'), '{}');
+ await fs.writeFile(path.join(root, 'binary'), 'final signed bytes', {mode: 0o755});
+ await fs.symlink('binary', path.join(root, 'entry'));
+ const manifest = await resealRuntimeManifest(root);
+ assert.deepEqual(Object.keys(manifest.resources), ['binary', 'entry']);
+ assert.equal(manifest.resources.binary.executable, true);
+ assert.equal(manifest.resources.entry.symlink, 'binary');
+ await verifyResources(root, manifest.resources);
+ await fs.writeFile(path.join(root, 'binary'), 'mutated after outer signing');
+ await assert.rejects(verifyResources(root, manifest.resources));
+});
diff --git a/desktop/tests/supervisor.test.cjs b/desktop/tests/supervisor.test.cjs
new file mode 100644
index 0000000..6ef2ac2
--- /dev/null
+++ b/desktop/tests/supervisor.test.cjs
@@ -0,0 +1,92 @@
+'use strict';
+const {test} = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs/promises');
+const os = require('node:os');
+const path = require('node:path');
+const {EventEmitter} = require('node:events');
+const {ServiceSupervisor} = require('../supervisor.cjs');
+async function fixture(t, request, {bind = true} = {}) {
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rieke-supervisor-test-')); t.after(() => fs.rm(root, {recursive: true, force: true}));
+ const runtime = path.join(root, 'runtime');
+ await fs.mkdir(path.join(runtime, 'python', 'bin'), {recursive: true});
+ await fs.mkdir(path.join(runtime, 'application', 'python'), {recursive: true});
+ await fs.writeFile(path.join(runtime, 'python', 'bin', 'python3.11'), 'interpreter');
+ await fs.writeFile(path.join(runtime, 'application', 'python', 'workspace_desktop.py'), 'entry');
+ const manifest = {format: 'rieke-desktop-runtime', version: 1, platform: process.platform, architecture: process.arch,
+ application_version: '0.1.0', source_commit: 'test-source', workspace_formats: [1], database_compatibility: 1};
+ await fs.writeFile(path.join(runtime, 'runtime-manifest.json'), JSON.stringify(manifest));
+ const child = new EventEmitter(); child.pid = 123456789; child.stdout = new EventEmitter(); child.stdout.resume = () => {}; let spawned;
+ const supervisor = new ServiceSupervisor({resourcesPath: root, userData: path.join(root, 'state'), appVersion: '0.1.0', startupTimeout: 5, drainTimeout: 5,
+ spawnProcess: (...args) => {
+ spawned = args;
+ if (bind) process.nextTick(() => child.stdout.emit('data', Buffer.from('RIEKE_DESKTOP_BOUND=' + JSON.stringify({pid: child.pid, session_id: supervisor.sessionId,
+ port: Number(args[1][args[1].indexOf('--port') + 1]), application_version: '0.1.0', source_commit: 'test-source'}) + '\n')));
+ return child;
+ }, request: (...args) => request(supervisor, child, ...args)});
+ return {supervisor, child, manifest, root, spawned: () => spawned};
+}
+const response = (value, ok = true, status = 200) => ({ok, status, json: async () => value});
+test('readiness validates owned process/release/session; launch never relies on PATH', async t => {
+ const {supervisor, spawned} = await fixture(t, sup => response({...sup.expectedHealth(), ready: true}));
+ assert.match(await supervisor.start(), /^http:\/\/127\.0\.0\.1:/);
+ const [executable, args, options] = spawned();
+ assert.ok(executable.startsWith(await fs.realpath(supervisor.resourcesPath))); assert.equal(args[0], '-B');
+ assert.equal(options.env.PATH, undefined); assert.equal(options.env.PYTHONDONTWRITEBYTECODE, '1');
+ assert.notEqual(supervisor.rendererCapability, supervisor.capability);
+});
+test('occupied port receives zero capability requests without private owned bind handshake', async t => {
+ let requests = 0;
+ const {supervisor, child} = await fixture(t, () => { requests++; return response({ready: true, pid: 99, session_id: 'foreign'}); }, {bind: false});
+ let killed = false; child.kill = () => { killed = true; };
+ await assert.rejects(supervisor.start(), /deadline/); assert.equal(supervisor.ready, false); assert.equal(killed, false); assert.equal(requests, 0);
+});
+test('busy writer drain keeps backend alive and resumes it without kill', async t => {
+ const routes = [];
+ const {supervisor, child} = await fixture(t, (sup, _child, url) => {
+ routes.push(url);
+ if (url.endsWith('/drain')) return response({error: 'busy'}, false, 409);
+ return response({...sup.expectedHealth(), ready: true});
+ });
+ child.kill = () => assert.fail('writer must never be killed');
+ await supervisor.start(); assert.equal((await supervisor.drain()).ready, false);
+ assert.equal(supervisor.exited, false); assert.ok(routes.some(route => route.endsWith('/resume')));
+});
+test('drain acknowledgement alone cannot authorize replacement until actual process exit', async t => {
+ const {supervisor, child} = await fixture(t, (sup, _child, url) => response(url.endsWith('/health') ? {...sup.expectedHealth(), ready: true} : {ready: true}));
+ await supervisor.start(); assert.equal((await supervisor.drain()).ready, false);
+ child.emit('exit', 0); assert.equal((await supervisor.drain()).ready, true);
+});
+test('unexpected process crash is never interpreted as a service drain', async t => {
+ const {supervisor, child} = await fixture(t, sup => response({...sup.expectedHealth(), ready: true}));
+ await supervisor.start(); child.emit('exit', 1);
+ assert.equal((await supervisor.drain()).ready, false);
+});
+test('incompatible manifest is rejected before executing any code', async t => {
+ const {supervisor, root, manifest, spawned} = await fixture(t, () => assert.fail('no request allowed'));
+ manifest.database_compatibility = 'invalid'; await fs.writeFile(path.join(root, 'runtime', 'runtime-manifest.json'), JSON.stringify(manifest));
+ await assert.rejects(supervisor.start(), /compatibility/); assert.equal(spawned(), undefined);
+});
+test('project authorization uses root validated process proof and never sends control token to child port', async t => {
+ const requests = [];
+ let childRecord;
+ const {supervisor} = await fixture(t, (sup, _child, url, options) => {
+ requests.push(url);
+ const rootHealth = {...sup.expectedHealth(), ready: true, services: childRecord ? [childRecord] : []};
+ if (url.endsWith('/authorize-project')) {
+ assert.equal(JSON.parse(options.body).port, 12345);
+ return response({record: childRecord, health: {...rootHealth, pid: childRecord.pid,
+ project_uuid: childRecord.project_uuid, project_path: childRecord.project_path}});
+ }
+ return response(rootHealth);
+ });
+ await supervisor.start();
+ childRecord = {pid: 87654321, port: 12345, session_id: supervisor.sessionId, application_version: '0.1.0', source_commit: 'test-source',
+ project_uuid: 'ec845d76-ce6a-472e-aed5-b3ad651a06b8', project_path: '/physical/project', bound: true};
+ assert.equal(await supervisor.authorizeProjectURL('http://127.0.0.1:12345/'), true);
+ assert.ok(requests.every(url => url.startsWith(supervisor.origin + '/')));
+ await assert.rejects(supervisor.api('/api/desktop/health', {origin: 'http://127.0.0.1:12345'}));
+ childRecord.bound = false;
+ assert.equal(await supervisor.authorizeProjectURL('http://127.0.0.1:12345/'), false);
+ assert.equal(supervisor.origins.has('http://127.0.0.1:12345'), false);
+});
diff --git a/desktop/tests/testing-install.test.cjs b/desktop/tests/testing-install.test.cjs
new file mode 100644
index 0000000..6eb8419
--- /dev/null
+++ b/desktop/tests/testing-install.test.cjs
@@ -0,0 +1,261 @@
+'use strict';
+const {test}=require('node:test');
+const assert=require('node:assert/strict');
+const fs=require('node:fs/promises');
+const path=require('node:path');
+const os=require('node:os');
+const crypto=require('node:crypto');
+const {promisify}=require('node:util');
+const execFile=promisify(require('node:child_process').execFile);
+const {installCompleteBundle}=require('../bootstrap.cjs');
+async function fixture(version='0.1.0') {
+ const root=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-install-policy-'));
+ const source=path.join(root,'Downloads','Rieke OS.app');
+ const runtime=path.join(source,'Contents/Resources/runtime');
+ await fs.mkdir(runtime,{recursive:true});await fs.mkdir(path.join(source,'Contents/MacOS'),{recursive:true});
+ await fs.writeFile(path.join(source,'Contents/MacOS/Rieke OS'),'native executable fixture',{mode:0o755});
+ await fs.writeFile(path.join(source,'Contents/Info.plist'),`CFBundleIdentifierorg.riekeos.desktopCFBundleExecutableRieke OSCFBundleShortVersionString${version}LSMinimumSystemVersion14.0`);
+ const resources={};
+ for(const name of ['python/bin/python3.11','mysql/bin/mysqld','mysql/bin/mysql','mysql/bin/mysqldump','application/python/workspace_desktop.py']){
+ const file=path.join(runtime,name);await fs.mkdir(path.dirname(file),{recursive:true});await fs.writeFile(file,'immutable fixture',{mode:0o755});
+ resources[name]={sha256:'79c3ce98db9b18e2c5fb3b1e74909c744e4f7b79a6c9ab5e6a0c538b0115cc50',size:17,executable:true};
+ resources[name].sha256=crypto.createHash('sha256').update('immutable fixture').digest('hex');
+ }
+ const manifest={format:'rieke-desktop-runtime',version:1,application_version:version,source_commit:'a'.repeat(40),parser_commit:'b'.repeat(40),source_dirty:false,platform:'darwin',architecture:'arm64',minimum_macos_version:'14.0',mysql_version:'8.4.2',python_version:'3.11.13',workspace_formats:[1],database_compatibility:1,resources};
+ await fs.writeFile(path.join(runtime,'runtime-manifest.json'),JSON.stringify(manifest));
+ const destination=path.join(root,'Applications','Rieke OS.app');
+ const commands=[];
+ async function run(command,args,options){
+ commands.push([command,...args]);
+ if(command==='/usr/bin/codesign'&&args.includes('--verify'))return {stdout:'',stderr:''};
+ if(command==='/usr/bin/codesign'||command==='/usr/sbin/spctl')throw new Error('Unsigned fixture is not Developer ID approved');
+ if(command==='/bin/ps')return {stdout:'',stderr:''};
+ if(command==='/usr/bin/ditto'){await fs.cp(args.at(-2),args.at(-1),{recursive:true,dereference:false});return {stdout:'',stderr:''};}
+ return execFile(command,args,options);
+ }
+ return {root,source,destination,run,commands,manifest};
+}
+test('explicit unsigned testing Install and Open copies the whole verified app while signed defaults reject it',async()=>{
+ const f=await fixture();try{
+ await assert.rejects(installCompleteBundle(f),/Unsigned fixture/);
+ const result=await installCompleteBundle({...f,distribution:{channel:'unsigned-testing'}});
+ assert.equal(result.destination,f.destination);
+ assert.equal(await fs.readFile(path.join(f.destination,'Contents/MacOS/Rieke OS'),'utf8'),'native executable fixture');
+ assert.equal(await fs.readFile(path.join(f.destination,'Contents/Resources/runtime/mysql/bin/mysqld'),'utf8'),'immutable fixture');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+module.exports={fixture};
+test('native unsigned update helper rejects an exposed receipt before changing the installed app',async()=>{
+ const {applyTestingInstall}=require('../testing-install.cjs');
+ const f=await fixture();try{
+ await installCompleteBundle({...f,distribution:{channel:'unsigned-testing'}});
+ const receiptPath=path.join(f.root,'update.json');await fs.writeFile(receiptPath,'{}',{mode:0o644});
+ await assert.rejects(applyTestingInstall({receiptPath,currentExecutable:path.join(f.destination,'Contents/MacOS/Rieke OS'),run:f.run}),/receipt.*private/i);
+ assert.equal(await fs.readFile(path.join(f.destination,'Contents/MacOS/Rieke OS'),'utf8'),'native executable fixture');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+async function updateFixture(){
+ const f=await fixture('0.1.0'),fresh=await fixture('0.1.1');
+ await installCompleteBundle({...f,distribution:{channel:'unsigned-testing'}});
+ const cache=path.join(f.root,'updates','unsigned-testing');await fs.mkdir(cache,{recursive:true,mode:0o700});
+ const candidate=path.join(cache,'Rieke OS.app');await fs.rename(fresh.source,candidate);
+ await fs.rm(fresh.root,{recursive:true,force:true});
+ const archive=path.join(cache,'candidate.zip');await fs.writeFile(archive,'validated ZIP transport fixture',{mode:0o600});
+ const {bundleDigest}=require('../bootstrap.cjs'),{sha256}=require('../testing-install.cjs');
+ const currentExecutable=path.join(f.destination,'Contents/MacOS/Rieke OS');
+ const receipt={format:'rieke-unsigned-testing-update',version:1,channel:'unsigned-testing',identifier:'org.riekeos.desktop',validated:true,
+ install_path:f.destination,current_executable:currentExecutable,current_pid:12345,current_created_at:17,current_version:'0.1.0',target_version:'0.1.1',
+ current_manifest_sha256:await sha256(path.join(f.destination,'Contents/Resources/runtime/runtime-manifest.json')),
+ runtime_manifest_sha256:await sha256(path.join(candidate,'Contents/Resources/runtime/runtime-manifest.json')),
+ archive_path:archive,archive_sha256:await sha256(archive),bundle_path:candidate,bundle_sha256:await bundleDigest(candidate)};
+ const receiptPath=path.join(cache,'install.json');await fs.writeFile(receiptPath,JSON.stringify(receipt),{mode:0o600});
+ return {...f,receipt,receiptPath,currentExecutable,cache};
+}
+test('native unsigned update acknowledges readiness, waits exact current exit, installs compatible complete bundle and retains previous',async()=>{
+ const {applyTestingInstall}=require('../testing-install.cjs'),{readBundleManifest}=require('../bootstrap.cjs');
+ const f=await updateFixture();let ready=false,exited=false,identityReads=0;
+ try{
+ const run=async(command,args,options)=>{
+ if(command.endsWith('/python/bin/python3.11')){
+ identityReads++;exited=identityReads>1;
+ return {stdout:'RIEKE_PROCESS_IDENTITY='+JSON.stringify(exited?{pid:12345,alive:false}:{pid:12345,alive:true,created_at:17,executable:f.currentExecutable})+'\n'};
+ }
+ if(command==='/usr/bin/open'){assert.equal(exited,true);assert.equal((await readBundleManifest(f.destination)).application_version,'0.1.1');return {stdout:''};}
+ if(command==='/usr/bin/ditto')assert.equal(ready&&exited,true,'No replacement before parent acknowledgement and exit');
+ return f.run(command,args,options);
+ };
+ const result=await applyTestingInstall({receiptPath:f.receiptPath,currentExecutable:f.currentExecutable,run,publishReady:()=>{ready=true;}});
+ assert.equal(result.state,'Installed');assert.equal((await readBundleManifest(result.previous)).application_version,'0.1.0');
+ assert.equal((await readBundleManifest(f.destination)).application_version,'0.1.1');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('failed native launch restores the previous complete app and requests its normal macOS launch',async()=>{
+ const {applyTestingInstall}=require('../testing-install.cjs'),{readBundleManifest}=require('../bootstrap.cjs');
+ const f=await updateFixture();let identities=0,opens=0;
+ try{
+ const run=async(command,args,options)=>{
+ if(command.endsWith('/python/bin/python3.11'))return {stdout:'RIEKE_PROCESS_IDENTITY='+JSON.stringify(++identities===1?{pid:12345,alive:true,created_at:17,executable:f.currentExecutable}:{pid:12345,alive:false})+'\n'};
+ if(command==='/usr/bin/open'){if(++opens===1)throw new Error('LaunchServices refused candidate');assert.equal((await readBundleManifest(f.destination)).application_version,'0.1.0');return {stdout:''};}
+ return f.run(command,args,options);
+ };
+ const result=await applyTestingInstall({receiptPath:f.receiptPath,currentExecutable:f.currentExecutable,run,publishReady:()=>{}});
+ assert.equal(result.state,'Restored');assert.equal(opens,2);assert.equal((await readBundleManifest(f.destination)).application_version,'0.1.0');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('controlled restore installs only the retained verified previous bundle and leaves update archive unnecessary',async()=>{
+ const {applyTestingInstall,sha256}=require('../testing-install.cjs'),{readBundleManifest,bundleDigest}=require('../bootstrap.cjs');
+ const f=await updateFixture();let identities=0;
+ try{
+ const run=async(command,args,options)=>{
+ if(command.endsWith('/python/bin/python3.11'))return {stdout:'RIEKE_PROCESS_IDENTITY='+JSON.stringify(++identities===1?{pid:12345,alive:true,created_at:17,executable:f.currentExecutable}:{pid:12345,alive:false})+'\n'};
+ if(command==='/usr/bin/open')return {stdout:''};
+ return f.run(command,args,options);
+ };
+ const installed=await applyTestingInstall({receiptPath:f.receiptPath,currentExecutable:f.currentExecutable,run,publishReady:()=>{}});
+ const prior=JSON.parse(await fs.readFile(path.join(f.cache,'previous.json'),'utf8'));
+ const restore={...f.receipt,operation:'restore',current_version:'0.1.1',target_version:'0.1.0',bundle_path:installed.previous,
+ bundle_sha256:await bundleDigest(installed.previous),current_manifest_sha256:await sha256(path.join(f.destination,'Contents/Resources/runtime/runtime-manifest.json')),
+ runtime_manifest_sha256:f.receipt.current_manifest_sha256,archive_path:null,archive_sha256:null};
+ const restorePath=path.join(f.cache,'restore.json');await fs.writeFile(restorePath,JSON.stringify(restore),{mode:0o600});identities=0;
+ await fs.rm(f.receipt.archive_path);
+ const result=await applyTestingInstall({receiptPath:restorePath,currentExecutable:f.currentExecutable,run,publishReady:()=>{}});
+ assert.equal(result.state,'Restored');assert.equal((await readBundleManifest(f.destination)).application_version,'0.1.0');
+ assert.equal(prior.previous_version,'0.1.0');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('Restore Previous defers a busy scientific session without launching helper or authorizing quit',async()=>{
+ const {applyTestingInstall,restoreTestingPriorBundle}=require('../testing-install.cjs'),{readBundleManifest}=require('../bootstrap.cjs');
+ const f=await updateFixture();let identities=0;
+ try{
+ const run=async(command,args,options)=>{
+ if(command.endsWith('/python/bin/python3.11'))return {stdout:'RIEKE_PROCESS_IDENTITY='+JSON.stringify(++identities===1?{pid:12345,alive:true,created_at:17,executable:f.currentExecutable}:{pid:12345,alive:false})+'\n'};
+ if(command==='/usr/bin/open')return {stdout:''};return f.run(command,args,options);
+ };
+ await applyTestingInstall({receiptPath:f.receiptPath,currentExecutable:f.currentExecutable,run,publishReady:()=>{}});
+ const result=await restoreTestingPriorBundle({app:{isPackaged:true,getPath:name=>name==='exe'?f.currentExecutable:f.root},manifest:await readBundleManifest(f.destination),
+ prepareQuit:async()=>({ready:false,busy:true}),authorizeQuit:()=>assert.fail('Busy session cannot quit'),spawnHelper:()=>assert.fail('Busy session cannot spawn helper'),
+ run:async(command,args,options)=>command.endsWith('/python/bin/python3.11')?{stdout:'RIEKE_PROCESS_IDENTITY='+JSON.stringify({pid:Number(args.at(-1)),alive:true,created_at:17,executable:f.currentExecutable})+'\n'}:f.run(command,args,options)});
+ assert.equal(result.busy,true);assert.equal((await readBundleManifest(f.destination)).application_version,'0.1.1');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('testing policy never replaces an app with any live installed process',async()=>{
+ const f=await updateFixture();try{
+ const run=async(command,args,options)=>command==='/bin/ps'?{stdout:' 23456 '+f.currentExecutable+'\n'}:f.run(command,args,options);
+ await assert.rejects(installCompleteBundle({source:f.receipt.bundle_path,destination:f.destination,distribution:{channel:'unsigned-testing'},run}),/running/);
+ assert.equal((await require('../bootstrap.cjs').readBundleManifest(f.destination)).application_version,'0.1.0');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('testing copy corruption outside runtime is rejected before retaining or replacing current app',async()=>{
+ const f=await updateFixture();try{
+ await fs.writeFile(path.join(f.receipt.bundle_path,'Contents/Resources/app.asar'),'published app code');
+ const run=async(command,args,options)=>{
+ const value=await f.run(command,args,options);
+ if(command==='/usr/bin/ditto')await fs.writeFile(path.join(args.at(-1),'Contents/Resources/app.asar'),'corrupted copied code');
+ return value;
+ };
+ await assert.rejects(installCompleteBundle({source:f.receipt.bundle_path,destination:f.destination,distribution:{channel:'unsigned-testing'},run}),/Copied application bundle checksum/);
+ assert.equal((await require('../bootstrap.cjs').readBundleManifest(f.destination)).application_version,'0.1.0');
+ await assert.rejects(fs.stat(path.join(path.dirname(f.destination),'.Rieke OS.previous.app')),{code:'ENOENT'});
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('helper timeout leaves current app installed and never signals it or launches a replacement',async()=>{
+ const f=await updateFixture();try{
+ const run=async(command,args,options)=>{
+ if(command.endsWith('/python/bin/python3.11'))return {stdout:'RIEKE_PROCESS_IDENTITY='+JSON.stringify({pid:12345,alive:true,created_at:17,executable:f.currentExecutable})+'\n'};
+ if(command==='/usr/bin/open')assert.fail('No launch while parent still alive');return f.run(command,args,options);
+ };
+ await assert.rejects(require('../testing-install.cjs').applyTestingInstall({receiptPath:f.receiptPath,currentExecutable:f.currentExecutable,run,publishReady:()=>{},timeoutMs:1,pollMs:1}),/exit was not acknowledged/);
+ assert.equal((await require('../bootstrap.cjs').readBundleManifest(f.destination)).application_version,'0.1.0');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('prepared archive changed after readiness cannot replace current app',async()=>{
+ const f=await updateFixture();let reads=0;try{
+ const run=async(command,args,options)=>{
+ if(command.endsWith('/python/bin/python3.11'))return {stdout:'RIEKE_PROCESS_IDENTITY='+JSON.stringify(++reads===1?{pid:12345,alive:true,created_at:17,executable:f.currentExecutable}:{pid:12345,alive:false})+'\n'};
+ return f.run(command,args,options);
+ };
+ await assert.rejects(require('../testing-install.cjs').applyTestingInstall({receiptPath:f.receiptPath,currentExecutable:f.currentExecutable,run,publishReady:()=>{require('node:fs').writeFileSync(f.receipt.archive_path,'modified after acknowledgement');}}),/archive or bundle checksum differs/);
+ assert.equal((await require('../bootstrap.cjs').readBundleManifest(f.destination)).application_version,'0.1.0');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('helper readiness must match its spawned PID, current process, target version and archive before quit can proceed',async()=>{
+ const f=await updateFixture();try{
+ const directory=path.join(f.destination,'Contents/Resources/app.asar');await fs.mkdir(directory);
+ for(const file of ['testing-install.cjs','bootstrap.cjs','updater-validation.cjs','physical-fs.cjs'])await fs.copyFile(path.join(__dirname,'..',file),path.join(directory,file));
+ const helper=require(await fs.realpath(path.join(directory,'testing-install.cjs')));
+ const {EventEmitter}=require('node:events'),{PassThrough}=require('node:stream');
+ const spawnHelper=()=>{
+ const child=new EventEmitter();child.pid=777;child.stdout=new PassThrough();child.stderr=new PassThrough();child.unref=()=>{};child.kill=()=>assert.fail('No helper or current app force kill');
+ setImmediate(()=>child.stdout.write('RIEKE_TESTING_INSTALL_READY='+JSON.stringify({pid:777,current_pid:12345,version:'0.0.1',archive_sha256:f.receipt.archive_sha256})+'\n'));
+ return child;
+ };
+ const run=async(command,args,options)=>command.endsWith('/python/bin/python3.11')?{stdout:'RIEKE_PROCESS_IDENTITY='+JSON.stringify({pid:12345,alive:true,created_at:17,executable:f.currentExecutable})+'\n'}:f.run(command,args,options);
+ await assert.rejects(helper.launchTestingInstall({receiptPath:f.receiptPath,currentExecutable:f.currentExecutable,currentPid:12345,run,spawnHelper}),/readiness identity differs/);
+ assert.equal((await require('../bootstrap.cjs').readBundleManifest(f.destination)).application_version,'0.1.0');
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+test('testing copy cannot silently lose the downloaded app quarantine attribute',async()=>{
+ const f=await fixture();try{
+ await execFile('/usr/bin/xattr',['-w','com.apple.quarantine','0083;00000001;RiekeFixture;',f.source]);
+ await assert.rejects(installCompleteBundle({...f,distribution:{channel:'unsigned-testing'}}),/quarantine attribute differs/);
+ await assert.rejects(fs.stat(f.destination),{code:'ENOENT'});
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+
+test('actual Electron main and RUN_AS_NODE hash physical ASAR bytes and copy and remove the complete native bundle',async()=>{
+ const f=await fixture();try{
+ const archive=path.join(f.source,'Contents/Resources/app.asar');
+ const contents=path.join(f.root,'asar-content');await fs.mkdir(contents);
+ await fs.writeFile(path.join(contents,'main.cjs'),'module.exports="owned archive fixture";');
+ await require('@electron/asar').createPackage(contents,archive);
+ const expected=await require('../bootstrap.cjs').bundleDigest(f.source);
+ const archiveSHA=crypto.createHash('sha256').update(await fs.readFile(archive)).digest('hex');
+ const electron=require('electron');const home=path.join(f.root,'home');await fs.mkdir(home);
+ const code=`const physical=require('original-fs').promises;const patched=require('node:fs/promises');
+ const bootstrap=require(process.argv[1]);const helper=require(process.argv[2]);
+ const path=require('node:path');const {promisify}=require('node:util');const native=promisify(require('node:child_process').execFile);
+ (async()=>{const source=process.argv[3],home=process.argv[4],archive=path.join(source,'Contents/Resources/app.asar');
+ const patchedVirtual=(await patched.lstat(archive)).isDirectory();
+ const digest=await bootstrap.bundleDigest(source);const archiveSHA=await helper.sha256(archive);
+ const run=async(command,args,options)=>command==='/usr/bin/codesign'?{stdout:'',stderr:''}:native(command,args,options);
+ const installed=await bootstrap.installCompleteBundle({source,home,run,distribution:{channel:'unsigned-testing'},expectedBundleSha256:digest});
+ const copied=(await physical.lstat(path.join(installed.destination,'Contents/Resources/app.asar'))).isFile();
+ const copiedDigest=await bootstrap.bundleDigest(installed.destination);
+ await physical.rm(installed.destination,{recursive:true,force:true});
+ console.log(JSON.stringify({patchedVirtual,digest,archiveSHA,copied,copiedDigest,removed:!(await physical.readdir(path.dirname(installed.destination))).length}));
+ })().catch(error=>{console.error(error);process.exitCode=1;});`;
+ const parameters=[path.resolve(__dirname,'../bootstrap.cjs'),path.resolve(__dirname,'../testing-install.cjs'),f.source,home];
+ for(const mainProcess of [false,true]){
+ const script=path.join(f.root,'owned-main.cjs');
+ if(mainProcess)await fs.writeFile(script,`const ownedApp=require('electron').app;ownedApp.setPath('userData',require('node:path').join(process.env.HOME,'main-profile'));process.argv.splice(1,1);`+
+ code.replace('process.exitCode=1;});','process.exitCode=1;}).finally(()=>ownedApp.exit(process.exitCode||0));'));
+ const result=await execFile(electron,mainProcess?[script,...parameters]:['-e',code,...parameters],
+ {env:{HOME:home,PATH:'/usr/bin:/bin',...(mainProcess?{}:{ELECTRON_RUN_AS_NODE:'1'})},timeout:20000});
+ const proof=JSON.parse(result.stdout.trim().split('\n').find(line=>line.startsWith('{')));
+ assert.equal(proof.patchedVirtual,true,'The actual Electron ASAR patch must be exercised');
+ assert.equal(proof.digest,expected);assert.equal(proof.archiveSHA,archiveSHA);
+ assert.equal(proof.copied,true);assert.equal(proof.copiedDigest,expected);assert.equal(proof.removed,true);
+ }
+ }finally{await fs.rm(f.root,{recursive:true,force:true});}
+});
+
+test('native helper CLI loads from a real ASAR under Electron RUN_AS_NODE and safely defers an invalid private receipt',async()=>{
+ const root=await fs.mkdtemp(path.join(os.tmpdir(),'rieke-native-helper-cli-'));try{
+ const contents=path.join(root,'helper-content');await fs.mkdir(contents);
+ for(const file of ['testing-install.cjs','bootstrap.cjs','updater-validation.cjs','physical-fs.cjs'])
+ await fs.copyFile(path.join(__dirname,'..',file),path.join(contents,file));
+ const archive=path.join(root,'app.asar');await require('@electron/asar').createPackage(contents,archive);
+ const home=path.join(root,'home'),cache=path.join(home,'profile/updates/unsigned-testing');await fs.mkdir(cache,{recursive:true});
+ const receipt=path.join(cache,'install-test.json');await fs.writeFile(receipt,JSON.stringify({
+ format:'rieke-unsigned-testing-update',version:1,channel:'unsigned-testing',identifier:'org.riekeos.desktop',validated:true}),{mode:0o600});
+ const executable=require('electron');
+ await assert.rejects(execFile(executable,[path.join(archive,'testing-install.cjs'),'--apply',receipt],
+ {env:{HOME:home,PATH:'/usr/bin:/bin',ELECTRON_RUN_AS_NODE:'1'},timeout:10000}),error=>{
+ assert.equal(error.code,1);assert.ok(!error.stdout.includes('RIEKE_TESTING_INSTALL_READY='));
+ assert.equal(error.stderr,'','Helper must reach its owned receipt failure handler');return true;
+ });
+ const result=JSON.parse(await fs.readFile(receipt+'.result.json','utf8'));
+ assert.equal(result.state,'Deferred');assert.equal((await fs.stat(receipt+'.result.json')).mode&0o077,0);
+ assert.ok(!(await fs.readdir(home)).includes('Applications'));
+ }finally{await fs.rm(root,{recursive:true,force:true});}
+});
diff --git a/desktop/update-recovery.cjs b/desktop/update-recovery.cjs
new file mode 100644
index 0000000..e0d1a72
--- /dev/null
+++ b/desktop/update-recovery.cjs
@@ -0,0 +1,66 @@
+'use strict';
+const fs = require('./physical-fs.cjs').promises;
+const path = require('node:path');
+const {spawn} = require('node:child_process');
+const {promisify} = require('node:util');
+const execFile = promisify(require('node:child_process').execFile);
+const {signingIdentity, verifyResources} = require('./updater-validation.cjs');
+const {enclosingApp, compatibleManifest} = require('./bootstrap.cjs');
+const {atomicJSON} = require('./supervisor.cjs');
+
+async function verifyPriorBundle({app, manifest, run = execFile}) {
+ const installedBundle = enclosingApp(app.getPath('exe'));
+ const directory = path.join(app.getPath('userData'), 'updates');
+ const previous = path.join(directory, 'previous', 'Rieke OS.app');
+ const receipt = JSON.parse(await fs.readFile(path.join(directory, 'previous.json'), 'utf8'));
+ const currentIdentity = await signingIdentity(installedBundle, run);
+ const priorIdentity = await signingIdentity(previous, run);
+ if (currentIdentity.team !== priorIdentity.team || currentIdentity.identifier !== priorIdentity.identifier || receipt.team !== priorIdentity.team || receipt.identifier !== priorIdentity.identifier) throw new Error('Previous app signing identity differs.');
+ const runtime = path.join(previous, 'Contents', 'Resources', 'runtime');
+ const previousManifest = JSON.parse(await fs.readFile(path.join(runtime, 'runtime-manifest.json'), 'utf8'));
+ if (!compatibleManifest(previousManifest, manifest) || previousManifest.mysql_version !== manifest.mysql_version || receipt.application_version !== previousManifest.application_version || receipt.source_commit !== previousManifest.source_commit) throw new Error('Previous app is incompatible with the current data contract.');
+ await verifyResources(runtime, previousManifest.resources);
+ await run('/usr/sbin/spctl', ['--assess', '--type', 'execute', previous]);
+ return {previous, installedBundle, directory};
+}
+
+async function retainPriorBundle({app, manifest, run = execFile}) {
+ const installedBundle = enclosingApp(app.getPath('exe'));
+ const identity = await signingIdentity(installedBundle, run);
+ const directory = path.join(app.getPath('userData'), 'updates');
+ await fs.mkdir(directory, {recursive: true, mode: 0o700});
+ const staging = await fs.mkdtemp(path.join(directory, 'prior-stage-'));
+ const copied = path.join(staging, 'Rieke OS.app');
+ try {
+ await run('/usr/bin/ditto', ['--rsrc', '--extattr', '--acl', installedBundle, copied], {timeout: 180000});
+ const copiedIdentity = await signingIdentity(copied, run);
+ if (identity.team !== copiedIdentity.team || identity.identifier !== copiedIdentity.identifier) throw new Error('Previous app could not be retained with its signature intact.');
+ const runtime = path.join(copied, 'Contents', 'Resources', 'runtime');
+ const copiedManifest = JSON.parse(await fs.readFile(path.join(runtime, 'runtime-manifest.json'), 'utf8'));
+ if (copiedManifest.application_version !== manifest.application_version || copiedManifest.source_commit !== manifest.source_commit) throw new Error('Previous app identity differs.');
+ await verifyResources(runtime, copiedManifest.resources);
+ const previous = path.join(directory, 'previous');
+ const old = path.join(directory, 'previous-old');
+ await fs.rm(old, {recursive: true, force: true});
+ try { await fs.rename(previous, old); } catch (error) { if (error.code !== 'ENOENT') throw error; }
+ try { await fs.rename(staging, previous); } catch (error) { try { await fs.rename(old, previous); } catch {} throw error; }
+ await atomicJSON(path.join(directory, 'previous.json'), {format: 'rieke-desktop-previous', version: 1, ...identity,
+ application_version: manifest.application_version, source_commit: manifest.source_commit,
+ workspace_formats: manifest.workspace_formats, database_compatibility: manifest.database_compatibility, mysql_version: manifest.mysql_version});
+ await fs.rm(old, {recursive: true, force: true});
+ } finally { await fs.rm(staging, {recursive: true, force: true}); }
+}
+
+async function restorePriorBundle({app, manifest, prepareQuit, authorizeQuit, run = execFile, spawnHelper = spawn}) {
+ if (!app.isPackaged) throw new Error('Recovery installation requires a signed installed app.');
+ // No renderer-provided path, version or command participates in this boundary.
+ const {previous, installedBundle} = await verifyPriorBundle({app, manifest, run});
+ const result = await prepareQuit();
+ if (result?.ready !== true) return result;
+ const helper = spawnHelper(app.getPath('exe'), [path.join(__dirname, 'recovery-install.cjs'), String(process.pid), previous, installedBundle],
+ {detached: true, stdio: 'ignore', cwd: app.getPath('userData'), env: {HOME: process.env.HOME, TMPDIR: process.env.TMPDIR, ELECTRON_RUN_AS_NODE: '1'}});
+ await new Promise((resolve, reject) => {helper.once('spawn', resolve); helper.once('error', reject);});
+ helper.unref(); authorizeQuit(); app.quit();
+ return {ready: true, restoring: true};
+}
+module.exports = {verifyPriorBundle, retainPriorBundle, restorePriorBundle};
diff --git a/desktop/updater-validation.cjs b/desktop/updater-validation.cjs
new file mode 100644
index 0000000..e9bb78e
--- /dev/null
+++ b/desktop/updater-validation.cjs
@@ -0,0 +1,146 @@
+'use strict';
+const fs = require('./physical-fs.cjs').promises;
+const path = require('node:path');
+const crypto = require('node:crypto');
+const {promisify} = require('node:util');
+const execFile = promisify(require('node:child_process').execFile);
+
+function stableVersion(value) {
+ if (typeof value !== 'string' || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value)) throw new Error('Only stable semantic versions are accepted.');
+ const parts = value.split('.').map(Number);
+ if (!parts.every(Number.isSafeInteger)) throw new Error('Invalid release version.');
+ return parts;
+}
+function compareVersions(left, right) {
+ const a = stableVersion(left), b = stableVersion(right);
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return Math.sign(a[i] - b[i]);
+ return 0;
+}
+function compatibleMacMinimum(minimum, host) {
+ function parts(value) {
+ if (typeof value !== 'string' || !/^\d+\.\d+(?:\.\d+)?$/.test(value)) throw new Error('Invalid macOS compatibility declaration.');
+ const numbers=value.split('.').map(Number);while(numbers.length<3)numbers.push(0);return numbers;
+ }
+ const required=parts(minimum), actual=parts(host);
+ for(let index=0;index<3;index++){
+ if(actual[index]>required[index])return true;
+ if(actual[index] candidate.workspace_formats.includes(format))) throw new Error('Update requires an unqualified database or workspace migration.');
+ if (!candidate.resources || typeof candidate.resources !== 'object' || Array.isArray(candidate.resources) || !Object.keys(candidate.resources).length) throw new Error('Update has no resource inventory.');
+ return candidate;
+}
+function safeResource(root, relative) {
+ if (typeof relative !== 'string' || relative.includes('\\') || relative.includes('\0') || relative.split('/').some(part => !part || part === '.' || part === '..') || path.isAbsolute(relative)) throw new Error('Unsafe runtime resource path.');
+ return path.join(root, relative);
+}
+async function verifyResources(root, resources) {
+ const resolvedRoot = await fs.realpath(root);
+ const actualPaths = [];
+ async function inventory(directory) {
+ for (const entry of await fs.readdir(directory, {withFileTypes: true})) {
+ const file = path.join(directory, entry.name), relative = path.relative(root, file).split(path.sep).join('/');
+ if (entry.isDirectory()) await inventory(file);
+ else if (!['runtime-manifest.json', 'runtime-audit.json'].includes(relative)) actualPaths.push(relative);
+ }
+ }
+ await inventory(root);
+ if (JSON.stringify(actualPaths.sort()) !== JSON.stringify(Object.keys(resources).sort())) throw new Error('Runtime resource inventory is incomplete or contains unexpected files.');
+ for (const [relative, expected] of Object.entries(resources)) {
+ const file = safeResource(root, relative), stat = await fs.lstat(file);
+ const physical = await fs.realpath(file);
+ if (!physical.startsWith(resolvedRoot + path.sep)) throw new Error('Runtime resource escapes its bundle.');
+ if (expected.symlink !== undefined) {
+ if (!stat.isSymbolicLink() || await fs.readlink(file) !== expected.symlink || path.isAbsolute(expected.symlink)) throw new Error('Runtime symlink differs from its manifest.');
+ } else {
+ if (!stat.isFile() || stat.isSymbolicLink() || !/^[a-f0-9]{64}$/.test(expected.sha256) || stat.size !== expected.size) throw new Error('Runtime resource metadata is invalid.');
+ if (typeof expected.executable === 'boolean' && expected.executable !== Boolean(stat.mode & 0o111)) throw new Error('Runtime executable permissions differ.');
+ const actual = crypto.createHash('sha256');
+ const handle = await fs.open(file, 'r');
+ try { for await (const chunk of handle.createReadStream()) actual.update(chunk); } finally { await handle.close(); }
+ if (actual.digest('hex') !== expected.sha256) throw new Error('Runtime resource checksum failed.');
+ }
+ }
+}
+async function signingIdentity(bundle, run = execFile) {
+ await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', bundle]);
+ const result = await run('/usr/bin/codesign', ['-dv', '--verbose=4', bundle]);
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`;
+ const team = output.match(/^TeamIdentifier=([A-Z0-9]+)$/m)?.[1];
+ const identifier = output.match(/^Identifier=(.+)$/m)?.[1];
+ if (!team || !identifier || !output.includes('Authority=Developer ID Application:')) throw new Error('A verified Developer ID application signature is required.');
+ return {team, identifier};
+}
+// ZIP extraction is always to an isolated disposable directory. Neither the
+// manifest nor candidate code is ever executed during validation.
+async function validateDownloadedCandidate({downloadedFile, version, manifest, cacheDirectory, installedBundle, run = execFile}) {
+ const installed = await signingIdentity(installedBundle, run);
+ if (installed.identifier !== 'org.riekeos.desktop') throw new Error('Unexpected installed app identity.');
+ await fs.mkdir(cacheDirectory, {recursive: true, mode: 0o700});
+ const temporary = await fs.mkdtemp(path.join(cacheDirectory, 'candidate-'));
+ try {
+ // Reject traversal before allowing a native extractor to touch disk.
+ const interpreter = path.join(installedBundle, 'Contents', 'Resources', 'runtime', 'python', 'bin', 'python3.11');
+ await run(interpreter, ['-I', '-B', '-c', ARCHIVE_CHECK, downloadedFile], {timeout: 30000});
+ await run('/usr/bin/ditto', ['-x', '-k', downloadedFile, temporary], {timeout: 180000});
+ const bundles = (await fs.readdir(temporary)).filter(name => name.endsWith('.app'));
+ if (bundles.length !== 1 || bundles[0] !== 'Rieke OS.app') throw new Error('Update must contain exactly the expected app.');
+ const bundle = path.join(temporary, bundles[0]);
+ const identity = await signingIdentity(bundle, run);
+ if (identity.team !== installed.team || identity.identifier !== installed.identifier) throw new Error('Update signer or application identity differs.');
+ const plist = path.join(bundle, 'Contents', 'Info.plist');
+ const declared = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleShortVersionString', plist]);
+ if (declared.stdout.trim() !== version) throw new Error('App version differs from update metadata.');
+ const runtime = path.join(bundle, 'Contents', 'Resources', 'runtime');
+ const candidate = compatibleCandidate(JSON.parse(await fs.readFile(path.join(runtime, 'runtime-manifest.json'), 'utf8')), manifest, version);
+ const host = await run('/usr/bin/sw_vers', ['-productVersion']);
+ const bundleMinimum = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :LSMinimumSystemVersion', plist]);
+ compatibleMacMinimum(candidate.minimum_macos_version, host.stdout.trim());
+ if(bundleMinimum.stdout.trim() !== candidate.minimum_macos_version)throw new Error('App and runtime macOS minimums differ.');
+ await verifyResources(runtime, candidate.resources);
+ await run('/usr/sbin/spctl', ['--assess', '--type', 'execute', '--verbose=2', bundle]);
+ const hasher = crypto.createHash('sha256');
+ const handle = await fs.open(downloadedFile, 'r');
+ try {for await (const chunk of handle.createReadStream()) hasher.update(chunk);} finally {await handle.close();}
+ return {version, team: identity.team, identifier: identity.identifier, downloadedFile, archive_sha256: hasher.digest('hex'), validated: true, native_staging_verified: false, startup_health_verified: false};
+ } finally { await fs.rm(temporary, {recursive: true, force: true}); }
+}
+const ARCHIVE_CHECK = String.raw`
+import posixpath, stat, sys, zipfile
+with zipfile.ZipFile(sys.argv[1]) as archive:
+ entries=archive.infolist()
+ if not entries or len(entries)>100000 or sum(e.file_size for e in entries)>6*1024**3: raise ValueError('Archive bounds')
+ names=set(); links={}
+ for entry in entries:
+ name=entry.filename.rstrip('/')
+ if not name or name.startswith('/') or chr(92) in name or chr(0) in name or any(p in ('','..','.') for p in name.split('/')): raise ValueError('Archive path')
+ if name in names: raise ValueError('Duplicate archive path')
+ names.add(name)
+ mode=entry.external_attr>>16
+ if stat.S_IFMT(mode) and not (stat.S_ISREG(mode) or stat.S_ISDIR(mode) or stat.S_ISLNK(mode)): raise ValueError('Archive special file')
+ if stat.S_ISLNK(mode):
+ if entry.file_size>4096: raise ValueError('Archive link size')
+ target=archive.read(entry).decode('utf8')
+ if target.startswith('/') or chr(92) in target: raise ValueError('Archive link')
+ links[name]=target
+ for name in names:
+ resolved=name
+ for _ in range(40):
+ parts=resolved.split('/'); changed=False
+ for i in range(1,len(parts)+1):
+ prefix='/'.join(parts[:i])
+ if prefix in links:
+ resolved=posixpath.normpath(posixpath.join(posixpath.dirname(prefix),links[prefix],*parts[i:]))
+ if resolved=='..' or resolved.startswith('../') or resolved.startswith('/'): raise ValueError('Archive link escape')
+ changed=True; break
+ if not changed: break
+ else: raise ValueError('Archive link loop')
+`;
+module.exports = {stableVersion, compareVersions, compatibleMacMinimum, compatibleCandidate, safeResource, verifyResources, signingIdentity, validateDownloadedCandidate, ARCHIVE_CHECK};
diff --git a/desktop/updater.cjs b/desktop/updater.cjs
new file mode 100644
index 0000000..ed3ee3e
--- /dev/null
+++ b/desktop/updater.cjs
@@ -0,0 +1,148 @@
+'use strict';
+const fs = require('./physical-fs.cjs').promises;
+const path = require('node:path');
+const {compareVersions, signingIdentity, validateDownloadedCandidate} = require('./updater-validation.cjs');
+
+function createUpdateCoordinator({app, manifest, publishStatus = () => {}, prepareQuit, authorizeQuit = () => {}, revokeQuit = () => {}, onInstallationFailure = () => {}, updater, verifyCandidate = validateDownloadedCandidate, verifyInstalled = signingIdentity, retainPrevious = options => require('./update-recovery.cjs').retainPriorBundle(options), receiptPath, timers = globalThis, random = Math.random, enabled, installedBundle}) {
+ const cacheDirectory = path.join(app.getPath('userData'), 'updates');
+ receiptPath ||= path.join(cacheDirectory, 'status.json');
+ installedBundle ||= path.resolve(app.getPath('exe'), '../../..');
+ let status = {state: 'Current', installed: manifest.application_version, available: null, message: 'Using the installed version.'};
+ let pending = null, checking = null, installing = null, timer = null, stopped = false, active = false, validation = null;
+ let receiptWrites = Promise.resolve();
+ const listeners = [];
+ async function verifyPendingArchive() {
+ if (!pending?.archive_sha256) return;
+ const crypto = require('node:crypto'), hasher = crypto.createHash('sha256');
+ const handle = await fs.open(pending.downloadedFile, 'r');
+ try {for await (const chunk of handle.createReadStream()) hasher.update(chunk);} finally {await handle.close();}
+ if (hasher.digest('hex') !== pending.archive_sha256) throw new Error('Prepared archive changed');
+ }
+ function set(state, fields = {}) {
+ status = {...status, ...fields, state};
+ publishStatus({...status});
+ const snapshot = {format: 'rieke-desktop-update-status', version: 1, installed: status.installed, available: status.available, state, validated: pending?.version || null, checked_at: status.checked_at || null};
+ receiptWrites = receiptWrites.catch(() => {}).then(async () => {
+ await fs.mkdir(path.dirname(receiptPath), {recursive: true, mode: 0o700});
+ const temporary = receiptPath + '.tmp';
+ await fs.writeFile(temporary, JSON.stringify(snapshot) + '\n', {mode: 0o600});
+ await fs.rename(temporary, receiptPath);
+ }).catch(() => {});
+ return {...status};
+ }
+ function defer(message) { return set(pending ? 'Ready' : 'Deferred', {available: pending?.version || status.available, message, check_error: message}); }
+ const on = (event, listener) => {updater.on(event, listener); listeners.push([event, listener]);};
+ async function downloaded(event) {
+ if (!active || stopped) return;
+ set('Validating', {available: event.version, message: 'Verifying downloaded app identity and compatibility.'});
+ // MacUpdater's native proxy now refers to this event's ZIP. A previous
+ // candidate can no longer authorize that proxy if this ZIP is rejected.
+ pending = null;
+ try {
+ const candidate = await verifyCandidate({downloadedFile: event.downloadedFile, version: event.version, manifest, cacheDirectory, installedBundle});
+ await retainPrevious({app, manifest});
+ if (!active || stopped) return;
+ pending = candidate;
+ set('Ready', {available: candidate.version, native_staging_verified: false, startup_health_verified: false, message: 'Update verified and prepared for an orderly quit. Native installation and startup health are checked separately.'});
+ } catch {
+ defer('The downloaded update was rejected. The installed app remains usable.');
+ }
+ }
+ function schedule() {
+ if (stopped || !active) return;
+ timer = timers.setTimeout(async () => {await check(); schedule();}, Math.round(3600000 * (0.9 + random() * 0.2)));
+ timer?.unref?.();
+ }
+ async function check() {
+ if (!active || stopped) return {...status};
+ if (pending) return {...status};
+ if (checking) return checking;
+ if (['Downloading', 'Validating', 'Draining', 'Installing'].includes(status.state)) return {...status};
+ checking = (async () => {
+ set('Checking', {checked_at: new Date().toISOString(), check_error: null, message: 'Checking for a published update.'});
+ try {
+ const result = await updater.checkForUpdates();
+ // The pinned client resolves metadata before its automatic download.
+ // Observe that separate promise so a failed checksum/network write
+ // cannot escape as an unhandled rejection in the Electron main process.
+ if (result?.downloadPromise) await result.downloadPromise;
+ } catch { defer('Update check unavailable. The installed app remains usable.'); }
+ return {...status};
+ })().finally(() => {checking = null;});
+ return checking;
+ }
+ async function start() {
+ if (active || stopped) return {...status};
+ // Receipts are display hints only: a previous Ready never authorizes install.
+ try {
+ const previous = JSON.parse(await fs.readFile(receiptPath, 'utf8'));
+ if (previous.format === 'rieke-desktop-update-status' && previous.version === 1 && previous.installed === manifest.application_version && previous.validated) status.available = previous.available;
+ } catch {}
+ if (enabled === false || !app.isPackaged || process.platform !== 'darwin' || process.arch !== 'arm64') return set('Deferred', {message: 'Automatic updates require a qualified signed macOS Apple Silicon installation.'});
+ try { await verifyInstalled(installedBundle); } catch { return set('Deferred', {message: 'Automatic updates are disabled for this unsigned development build.'}); }
+ updater ||= require('electron-updater').autoUpdater;
+ updater.autoInstallOnAppQuit = false;
+ updater.autoDownload = true;
+ updater.autoRunAppAfterInstall = false;
+ updater.allowDowngrade = false;
+ updater.allowPrerelease = false;
+ updater.logger = null; // Never log cache/session URLs or local paths.
+ updater.setFeedURL({provider: 'github', owner: 'maxwellsdm1867', repo: 'Rieke-OS', private: false});
+ on('checking-for-update', () => set('Checking'));
+ on('update-available', info => {
+ try {
+ if (compareVersions(info.version, manifest.application_version) <= 0) throw new Error('Older release');
+ set('Available', {available: info.version, release_url: `https://github.com/maxwellsdm1867/Rieke-OS/releases/tag/v${info.version}`, message: `Rieke OS ${info.version} is available.`});
+ } catch { updater.autoDownload = false; defer('Published update metadata was rejected.'); }
+ });
+ on('download-progress', progress => set('Downloading', {progress: Math.max(0, Math.min(100, Number(progress.percent) || 0)), message: 'Downloading an update quietly.'}));
+ on('update-not-available', () => set(pending ? 'Ready' : 'Current', {message: pending ? 'The verified pending update remains prepared.' : 'The installed version is current.'}));
+ on('update-downloaded', event => { validation = downloaded(event); });
+ on('error', () => {
+ if (status.state === 'Installing') {
+ revokeQuit();
+ defer('Native installation failed. Restart the installed app from recovery.');
+ onInstallationFailure();
+ } else defer('Update preparation failed. The installed app remains usable.');
+ });
+ active = true;
+ await check();
+ schedule();
+ return {...status};
+ }
+ async function installPrepared() {
+ if (installing) return installing;
+ if (validation) await validation;
+ if (installing) return installing;
+ if (!pending || status.state !== 'Ready') return {ready: false, reason: 'No verified update is prepared.'};
+ installing = (async () => {
+ set('Draining', {message: 'Saving drafts and waiting for all scientific services to close.'});
+ if (pending.archive_sha256) {
+ try { await verifyPendingArchive(); }
+ catch { pending = null; defer('The prepared update changed or is missing. Installation is deferred.'); return {ready: false, reason: status.message}; }
+ }
+ let result;
+ try { result = await prepareQuit(); } catch { result = {ready: false}; }
+ if (result?.ready !== true) {
+ set('Ready', {message: 'Update remains pending: a draft, writer or service has not acknowledged closure.'});
+ return {ready: false, reason: result?.reason || status.message};
+ }
+ // Drain can take long enough for a cached ZIP to change. Check the exact
+ // validated bytes again at the installation boundary. Services are already
+ // closed here, so failed verification must offer backend recovery.
+ try { await verifyPendingArchive(); }
+ catch {
+ pending = null; revokeQuit();
+ defer('The prepared update changed during shutdown. Restart the installed app from recovery.');
+ onInstallationFailure(); return {ready: false, reason: status.message};
+ }
+ set('Installing', {message: 'All owned services have closed; handing the verified app to the native installer.'});
+ try { authorizeQuit(); updater.quitAndInstall(); return {ready: true, installing: true}; }
+ catch { revokeQuit(); defer('Native installation failed. Use recovery to restart the installed app.'); onInstallationFailure(); return {ready: false, reason: status.message}; }
+ })().finally(() => {installing = null;});
+ return installing;
+ }
+ function stop() { stopped = true; active = false; if (timer) timers.clearTimeout(timer); for (const [event, listener] of listeners) updater.removeListener(event, listener); }
+ return {getStatus: () => ({...status}), check, start, stop, installPrepared};
+}
+module.exports = {createUpdateCoordinator};
diff --git a/docs/DATA_EFFICIENCY_AUDIT.md b/docs/DATA_EFFICIENCY_AUDIT.md
new file mode 100644
index 0000000..05a3e1d
--- /dev/null
+++ b/docs/DATA_EFFICIENCY_AUDIT.md
@@ -0,0 +1,140 @@
+# Rieke OS data-efficiency audit — 2026-09-29
+
+**Conclusion:** the raw-signal loading design is sound, and the remaining scientific SQL data is small. The biggest remaining problems are repeated ancestor metadata, work tied to whole-project state rather than the requested operation, and derived/history data with no lifecycle. Fix those before reconsidering the database engine.
+
+This is a code and data-lifecycle audit, not a request to enforce an arbitrary byte budget. A representation earns its storage by serving a current feature, recovery requirement, or deliberate scientific export. Recomputable copies need a clear invalidation and reclamation path.
+
+## Scope and evidence
+
+Inspected the active app checkout at `/Users/maxwellsdm/Documents/GitHub/epicTreeGUI`, including the recent persistence changes. Compared core modules with `/Users/maxwellsdm/Documents/GitHub/Rieke-OS`; snapshot, cache, index, explorer, curation, and MATLAB modules matched at measurement time. Service/API/SQLite-export modules differ between checkouts; source hashes are recorded in the measurements. Findings refer to the active checkout unless stated otherwise.
+
+Read live SRM database metadata and records in a read-only SQL transaction, inspected SQLite files read-only, and sampled GETs and the read-only annotation POST. No scientific records or application code were changed by this audit. Its read-only POST exercises the existing snapshot hook; unchanged snapshots were not rewritten. This was not a concurrent-user or large-project stress test.
+
+Evidence: [measurements](dev/data-efficiency-audit/measurements.json), [local measurement runner](dev/data-efficiency-audit/measure.py). HTTP timings are medians of three local requests, not service-level guarantees. JSON sizes are decoded/uncompressed representations unless explicitly labeled gzip. SQL `information_schema` row counts are estimates; the separately reported revision counts are exact.
+
+| Measured item | Result |
+|---|---:|
+| SRM epochs / cells / sources | 1,776 / 8 / 2 |
+| Database directory, allocated | 167.9 MiB |
+| Parsed imports, allocated | 38.9 MiB |
+| Metadata indexes, allocated | 104.9 MiB |
+| Compressed source projections, allocated | 20.5 MiB |
+| Exports, allocated | 57.9 MiB |
+| State snapshot | 538,865 bytes |
+| Snapshot excluding export recipes | 31,971 bytes |
+| SQL explorer revisions / active bindings | 28 / 5 |
+| Epoch references across SQL explorer recipes | 6,886 |
+| Explorer recipe JSON in MySQL | 1,577,166 bytes |
+
+## Prioritized findings
+
+### 1. High: the snapshot hook runs on read-only POSTs and captures too much
+
+[API hook](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_api.py:1416), [capture](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_snapshot.py:34), [query compaction](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_snapshot.py:99).
+
+Every successful POST/PUT/PATCH/DELETE performs a synchronous snapshot while holding the shared database lock. POST is also used for annotation reads, tree pages, predicate previews, and membership queries. These operations can therefore rebuild backup state without changing anything. The equality check prevents a final file write, but occurs **after** reading SQL, decoding JSON, sorting, rerunning pinned queries, and serializing the snapshot.
+
+`capture()` also fetches all explorer/preset versions before filtering to active ones in Python. On this small project, capture alone used 26 SQL queries, fetched approximately 2.21 MB in serialized result representations, took 53–73 ms, and peaked at 7.33 MiB of traced Python allocations. These figures exclude pinned-query compaction. A 401-byte annotation-read response took a median 121 ms; this is an observed endpoint time, not an isolated attribution of all latency to the hook.
+
+**Change:** mark actual committed state mutations, snapshot only when that state revision changes, select active revisions in SQL, and separate immutable export recipes from current settings. Keep transaction/backup-failure semantics explicit rather than simply making backups fire-and-forget.
+
+**Acceptance:** repeated annotation reads, previews, and tree-page requests perform zero snapshot captures; a tag save creates one recoverable checkpoint; snapshot work does not scale with obsolete revision history. Retain the existing restore and crash-recovery tests.
+
+### 2. High: each epoch carries a large copy of its block metadata
+
+[Detail construction](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py:273), [per-epoch index storage](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py:74), [detail cache](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py:396).
+
+The inspected epoch response was 123,639 bytes. Its block metadata accounted for 112,287 bytes, including a **111,371-byte `frameTimesMs` array**. That block contains 430 epochs. The block array is copied into each epoch's serialized detail record and reconstructed on each uncached detail read. The current 64-entry detail cache holds independent decoded copies, so compression on disk hides repeated decoding and allocation.
+
+Serializing that shared array for all 430 epoch details represents approximately 45.7 MiB of repeated JSON. This is a representation-volume calculation, not a claim of 45.7 MiB extra physical disk or simultaneous RAM.
+
+**Change:** store source/cell/group/block metadata once under immutable identity plus version; let epoch details reference it. Fetch and cache block context separately when needed. Preserve the recorded frame-time arrays exactly—do not discard them or infer an epoch-to-array mapping without validating the acquisition format.
+
+**Acceptance:** inspecting many epochs in one block fetches/decodes the shared block object once; scientific metadata and legacy/export round trips remain exact.
+
+### 3. High: obsolete cache generations are never reclaimed
+
+[Generation selection](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py:380), [index publication](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py:140), [projection writer](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_projection_cache.py:42).
+
+There are **seven complete indexes**, each covering the same 1,776 epochs. They total 100.3 MiB logical / 104.9 MiB allocated; one index is 14.3 MiB logical. Source projections add another 20.5 MiB allocated. Changed signatures/code produce new filenames; cleanup removes temporary build files, not superseded published generations.
+
+**Change:** introduce ownership/reference tracking for active generations and collect unreferenced indexes and projections after successful publication. A small explicit rollback set can be retained if it serves a real workflow. Do not remove a generation still referenced by a running reader.
+
+**Acceptance:** repeated metadata refreshes/upgrades cease increasing retained cache generations once readers release old ones; unchanged restarts reuse the current index; interrupted rebuilds keep the last valid generation.
+
+### 4. Medium: query-based recovery does not yet make live SQL pins query-based
+
+[Revision creation](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_explorer.py:125), [recipe cache](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_explorer.py:58).
+
+The recent backup optimization removes epoch lists from reproducible pins **in the backup**. Live `ExplorerHistory.create()` still stores a complete epoch list, UUID diff lists, and provenance in every new revision. There are 28 revisions containing 6,886 epoch references for five active bindings. `_recipe_cache` is also keyed by revision UUID without eviction.
+
+The present 1.50 MiB of recipe JSON is modest. The concern is growth proportional to selected epochs × applied revisions, even when the source recordings do not change. This is not a reason to delete existing frozen history blindly: bindings, parents, and exports can refer to it.
+
+**Change:** represent ordinary working pins as predicates + exact source/metadata versions + result digest, with sparse explicit overrides where a selection is not reproducible. Retain explicit frozen export records. Reclaim unreferenced materialized memberships and evict retired recipe-cache entries after auditing dependencies.
+
+**Acceptance:** repeatedly applying a large unchanged query does not append another full epoch list; edited/tag-dependent subsets and immutable exports retain their scientific meaning.
+
+### 5. Medium: the overview mostly transports historical event detail
+
+[Overview events](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py:623), [five visible events](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/workspace-app/src/components/Overview.jsx:34).
+
+The overview response was 284,851 bytes; its `events` value alone was 265,071 bytes (about 93%). The server includes 25 full events while the view displays five. Old provenance-heavy events remain large even though routine new edits no longer create them. With gzip, the complete response body was 40,774 bytes: compression helps transport, but not SQL decoding and constructing the original JSON objects.
+
+**Change:** return a small activity summary for the visible items. Load a selected event's full evidence through the existing detail endpoint. Keep detailed history where the user requests it.
+
+**Acceptance:** overview does not retrieve full event payloads; opening an event still retrieves the original evidence.
+
+### 6. Medium: bounded page responses still require whole-scope work
+
+[Filtered rows](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py:572), [epoch pagination](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py:637), [temporary query scope](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py:172), [export-membership copy](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_curation.py:397).
+
+A one-epoch page is small on the wire, but `filtered_rows()` constructs/decorates/sorts the whole protocol membership before slicing. Query helpers create and populate a temporary scope table for each call, and predicate matching materializes Python sets. Protocol binding access deep-copies the full recipe. Some summary paths deep-copy the project-wide export-membership index as well.
+
+The measured one-epoch page was 3,793 bytes and 54 ms median. This small-project number does not establish a problem at present; the implementation establishes that work is not bounded by page size.
+
+**Change:** reuse immutable query results by generation, push indexed filtering/order/pagination into the disk index where semantics permit, and return memberships for the requested scope rather than cloning all export links.
+
+**Acceptance:** benchmark fixed-size pages at increasing epoch and export counts, checking CPU, rows visited, and peak memory—not just response size. Preserve exact typing, null/missing distinctions, and anchor ordering.
+
+### 7. Medium: cold rebuild and MATLAB export can defeat lazy metadata loading
+
+[Index build](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py:85), [cold source JSON load](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py:418), [MATLAB catalog construction](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab.py:139).
+
+Changed generations build a complete new index. On a projection-cache miss, the full source JSON is decoded before index construction. Warm loading still keeps lightweight rows/fingerprints for every epoch in memory; only detailed metadata is lazy.
+
+The MATLAB export builder separately runs `catalog(list(service.rows.values()), service.details, ...)` for the whole project before cataloging its selected records. A small export can therefore visit detailed metadata for unrelated epochs. The SQLite exporter also receives an already materialized complete package and hashes a complete serialized representation; it is not a streaming export pipeline.
+
+**Change:** reuse the sealed index's schema/catalog for MATLAB exports, project only selected IDs/fields, and stream large export records where integrity checks allow. For ingestion growth, build per-source partitions or incrementally merge additions rather than rebuilding unchanged sources.
+
+**Acceptance:** a small MATLAB export does not decode unrelated epoch details; cold/import benchmarks report peak memory and temporary disk alongside elapsed time. The older repository benchmark of 50,000 epochs reports a 63-second full index build and a 238 MB index; it is historical fixture evidence, not a new production measurement.
+
+### 8. Medium: daily state backups repeatedly embed immutable exports
+
+[Captured state tables](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_snapshot.py:17), [daily snapshot](/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_snapshot.py:194).
+
+Current settings/query/edit state is 31,971 bytes, while the complete snapshot is 538,865 bytes because it includes seven existing export recipes. On each active new UTC date a complete new SQLite snapshot is written, even if the state is identical. At the current logical payload alone, 365 active days would repeat about 188 MiB. That is arithmetic, not an observed annual workload.
+
+**Change:** store immutable export recipes once by content hash and reference them from compact state snapshots; make each recovery package's dependencies explicit and verified. Deduplicate identical daily content. Preserve independent backups where they are intentional—deduplication is not a reason to leave recovery dependent on missing files.
+
+**Acceptance:** adding a day of unchanged state does not duplicate every frozen export recipe; restoring a packaged snapshot works without the original live SQL server and detects missing immutable objects.
+
+## What is already appropriate
+
+- Raw traces are read from H5 in bounded windows of at most 100,000 samples. They are not copied into scientific SQL waveform rows.
+- Source SHA verification is cached by file signature. A first trace can require a full source checksum read; that integrity cost is distinct from eagerly loading waveforms into memory.
+- The SQLite detail cache is limited to 64 entries. The frontend epoch cache has entry, estimated-byte, and TTL bounds; adjacent prefetch is limited to metadata for two epochs. There is no evidence here of unbounded raw-trace caching.
+- Typed field values and exported parameter sets already have deduplication. Export files intentionally contain queryable metadata plus a compressed exact archive; the measured frozen-record archive is only roughly 9–14% of each inspected SQLite export. Do not call all of that redundancy waste or remove reproducibility checks casually.
+- Tags use stable target/author identities and current-state updates. Query last-run state updates a row per distinct predicate rather than appending one per execution. No-op shared-tag and layout saves are already handled.
+- The reduced redo allocation and durable-commit settings should remain. Disabling crash recovery is not an efficiency improvement.
+
+## Recommended order
+
+1. Fix snapshot triggering and SQL projection; remove heavyweight event payloads from overview.
+2. Normalize shared block/ancestor metadata and add cache-generation reclamation.
+3. Move ordinary live pins to query/version descriptors and audit history dependencies.
+4. Address page-level work, small-export behavior, and incremental ingestion with representative larger fixtures.
+5. Deduplicate immutable objects in recovery archives while testing full restore bundles.
+
+A reusable efficiency check for every new feature: identify its authoritative data; list every retained copy; distinguish configuration, cache, and frozen scientific output; specify invalidation/reclamation; measure bytes read/written and objects decoded for a common operation; test growth with data size and operation count separately; then verify recovery from the retained representations.
+
+The storage reduction already completed remains valid. This audit identifies additional work; it does not claim the whole data lifecycle has already been optimized.
diff --git a/docs/STORAGE_RECOVERY.md b/docs/STORAGE_RECOVERY.md
index 646848c..d2d0f94 100644
--- a/docs/STORAGE_RECOVERY.md
+++ b/docs/STORAGE_RECOVERY.md
@@ -6,34 +6,79 @@ queries, layouts, or inclusion decisions.
## What is saved
-`app-state.json` is an atomic current-state snapshot. It includes:
+`app-state.json` locates the compact current-state recovery database under
+`backups/app-state/current-.sqlite`. Together they protect:
- Project settings and protocol configuration files.
- Named predicates/presets, layouts, and the latest run of each distinct query
(predicate, splits, run time, source identities, and query/metadata versions).
-- Current annotation profiles, sparse cell/epoch tags, and curation decisions.
+- Current annotation profiles, cell/epoch tags, and curation decisions, including
+ densely tagged datasets with several tags and authors per target.
- Current pinned queries with source H5 checksums, metadata checksums, version
- information, and a digest of the expected result. Recoverable pins do not
- enumerate all epochs: recovery reruns their predicates and verifies the digest.
+ information, and their exact frozen epoch membership and metadata fingerprints.
- Existing export records and their immutable recipes. Export artifacts and H5
recordings remain separate files and must also be retained.
-An existing frozen subset that a saved predicate cannot reproduce retains its
-explicit membership. It is never silently widened on recovery. UUIDs remain
-necessary for explicitly tagged/edited cells and epochs.
-
-Successful API writes update the snapshot; unchanged state does not rewrite it.
-Startup and successful writes create one write-once SQLite snapshot per active
-UTC day under `backups/app-state/YYYY-MM-DD.sqlite`. A day without running the app
-has no scheduled backup. These daily files record the first captured state that
-day; `app-state.json` holds the latest successful capture. No H5 data or derived
-metadata caches are copied into them.
-
-Routine query runs, tag/curation edits, layout saves, and preset saves no longer
-append action events. Each distinct query has a current/latest-run record, not a
-record per execution. Existing history and scientific import/export/external
-exchange receipts remain available. Explicit export/version workflows remain
-unchanged.
+Tag associations use target and profile UUIDs; display names do not establish
+identity. New backups retain explicit frozen membership, so later tag edits
+cannot change a pin during recovery. Older query-based backups remain readable
+and must reproduce their recorded membership digest before restore succeeds.
+
+Direct cell/epoch tag edits commit their current rows and an append-only change
+event together in MySQL. The API acknowledges that database commit immediately;
+it does not wait for a separate recovery copy or a page refresh. A burst of tag
+edits queues one background recovery capture after one second of inactivity,
+with a five-second maximum scheduling delay while edits continue. Lock contention
+or a failed capture can extend that delay. The response and `/api/backup/status`
+report pending, running, current, or degraded backup state. Normal database crash
+recovery still preserves acknowledged commits; loss of the database before the
+independent capture finishes can lose those newest edits from the recovery copy.
+Clean project close forces the recovery capture and refuses to finish if it fails.
+
+On supported native databases, a verified change tracker copies only changed
+records into that recovery database. Rows and the coverage watermark commit
+together. An unknown change gap, changed schema/server identity, or unsupported
+database requires a full current-state capture. Other successful mutations still
+wait for the recovery write; a failed write returns `507` with `saved: true` and
+must not be blindly replayed as a new edit.
+
+Startup and recovery captures also create a write-once checkpoint for the active
+UTC day under `backups/app-state/checkpoint-v2-YYYY-MM-DD.sqlite`. It records the
+first capture that day; the current recovery database holds the latest capture.
+No background schedule runs on inactive days. New checkpoint history keeps at
+most two checkpoints within a combined 128 MiB budget, except that the newest
+checkpoint is retained even if it alone exceeds that budget. Current state is
+always retained. Old-format backups and explicit before-restore copies are
+preserved separately. H5 signals and derived metadata indexes are not copied
+into these app-state files.
+
+The JSON pointer alone is not a standalone backup. Keep it with its referenced
+SQLite file, or use a sealed daily checkpoint for an independent app-state copy.
+Use the complete project transfer workflow to include recordings and exports.
+
+Shared cell/epoch tag batches append one event containing their before/after
+values in the same transaction as the edit. Routine query runs, curation edits,
+layout saves, and preset saves retain current state without a new event for each
+operation. Each distinct query has a current/latest-run record. Existing history
+and scientific import/export/external exchange receipts remain available. The
+app-state mirror restores current state, not the append-only event history; a
+full SQL backup includes that history.
+
+Ordinary shared-tag filtering uses a persistent MySQL lookup. Existing projects
+build it once during preparation; database triggers maintain changed memberships
+in the same transaction as each canonical tag edit, including external SQL
+writes. Clean reopen reuses it after checking fresh canonical content, the
+lookup table identity, and its schema and triggers. Missing triggers, replaced
+tables, or an unclean checkpoint cause validation or rebuilding before reuse.
+The autocomplete dictionary keeps current tag names, distinct-target usage
+counts, and author references. Prefix keys use Python's exact Unicode casefold
+semantics and are computed once for each new name. Database triggers maintain
+counts with the same transaction as the tag edit, including external SQL writes.
+These tables are rebuildable indexes, not additional canonical annotation copies.
+There is no full SQLite tag-cache build on the ordinary native filtering path.
+Full aggregate views and unsupported database contracts retain their existing
+canonical/derived readers. Cell tags remain one cell record per author, joined
+to their child epochs by UUID. Durable SQL autosave remains enabled.
## Recovery
@@ -46,12 +91,13 @@ app server** first, leaving its database available. From the application checkou
```sh
PYTHONPATH=python .rieke-runtime/venv/bin/python python/workspace_state_snapshot.py \
--project-dir /path/to/project \
- --restore /path/to/project/backups/app-state/YYYY-MM-DD.sqlite
+ --restore /path/to/project/backups/app-state/checkpoint-v2-YYYY-MM-DD.sqlite
```
-The same command accepts `app-state.json`. Restore refuses a running app, a
+The same command accepts `app-state.json` with its referenced database, or older
+JSON/SQLite snapshots. Restore refuses a running app, a
foreign project, changed source/metadata identities, corrupted SQLite snapshots,
-or a pinned query whose reconstructed result differs. It saves the immediate
+or an older pinned query whose reconstructed result differs. It saves the immediate
pre-restore configuration under `backups/app-state/before-restore-*.json`, changes
SQL state transactionally, and restores configuration files if an ordinary error
occurs. An interrupted restore leaves a marker that prevents app startup until
diff --git a/docs/WORKSPACE_SHARING_AND_UPDATES.md b/docs/WORKSPACE_SHARING_AND_UPDATES.md
index 9f9caaf..d6f82cf 100644
--- a/docs/WORKSPACE_SHARING_AND_UPDATES.md
+++ b/docs/WORKSPACE_SHARING_AND_UPDATES.md
@@ -2,15 +2,62 @@
## Choose the project folder itself
+The **+** in the left project rail opens a popup over the current workspace.
+Choose either project action inside it. The workspace remains visible; **×**
+or **Escape** closes the popup and returns focus to **+**. Creating a new
+project from the welcome screen also opens a popup.
+
+Every project location field has **Browse…**. Choose folders using the local
+folder picker; paths remain editable. New projects can use an existing empty
+folder or a named new folder inside a chosen location. Move, share and restore
+destinations ask for a new folder name. Browsing only selects a path; it does not
+create or modify files. Cancel the folder picker to return to setup unchanged.
+
+New project setup ends with **Cancel** and **Create & open**. **Cancel** closes
+the popup and returns to the visible workspace.
+
+The left sidebar has two project actions:
+
+- **Add new project** opens an existing working project or a portable copy someone
+ gave you. Selecting a prepared copy checks its inventory and opens the restore
+ form automatically; choose a new local project folder, restore, then open it.
+- **Start a brand new project** asks for a name and an empty folder and creates
+ the project manifests and managed storage. Opening initializes its database.
+
New project asks for a name and one **Project folder**. That exact folder is the
project root: it can be anywhere writable, outside the application code. Choose a
new or empty folder; existing project folders should be opened instead. No extra
parent workspace or generated subfolder is required.
+For example, name a new project **Spike response study** and choose a new or
+empty folder named **Spike response study** in your own research location.
+Matching names make the folder easier to recognize later. The project name is
+shown in the app; the folder name can differ, and neither name is its unique
+identity. A project's name can change without moving its files.
+
+When reopening or receiving that project, select the **Spike response study**
+folder itself. Its project files and contents should be directly inside it.
+Choose its top folder rather than the research folder above it or an `imports`,
+`database` or other folder inside it. The app checks the files to identify the
+project, so a renamed folder can still be recognized.
+
Open project checks the identity manifests, storage layout and database files
before starting the project, then checks recording dependencies during opening.
It opens the folder in place. The preferred projects location is optional.
+Created, restored and successfully opened project roots are remembered in a
+local user-profile index, so projects on other drives or outside the preferred
+location remain in the chooser after closing or restarting. The index contains
+folder references and display identities; the project files stay in their
+chosen location. An unavailable drive leaves an unavailable project entry.
+
+Choose the top folder containing `project.json` and `catalog.json`, or the top
+folder of a received portable copy. If you choose a project subfolder, the app
+suggests its nearest valid project root. If you choose a parent, it lists valid
+immediate child roots. Select **Use this folder**, then open or check that folder.
+Several matches remain separate choices; the app does not open one automatically.
+Nearby-folder discovery is bounded and read-only; it does not scan a whole drive.
+
To organize a closed native project, expand **Optional: move to a preferred
location**, select **Move this folder before opening**, and review the new exact
destination. The app moves the whole folder without overwriting an existing
@@ -19,24 +66,31 @@ disk, close the project, copy its whole folder with the file manager, and open t
copy before removing the old folder.
-## Automatic update notifications
+## Automatic update availability
Rieke OS checks the official GitHub release channel when you open the app, every
15 minutes while the page is visible, and when you return after that interval.
-No button press is required. A newer release produces an **Update available**
-notice in the app and an update indicator in the header. Dismissing the notice
-leaves the header indicator available.
-
-The header distinguishes **Up to date**, **No release published**, and **Update
-check unavailable**. An offline check does not count as up to date. Open the
-indicator for installed version, last check, release notes, and an optional manual
-retry. Manual retries bypass the automatic check cache.
-
-Managed installations with the official trusted signing key can download and
-verify a release from this panel. The download prepares a separate installation;
-it does not replace the running app or modify projects. After staging, follow the
-displayed apply command. Activation waits for project services to close and
-refuses incompatible database versions. Automatic shutdown/restart from the
+No button press is required. A newer release highlights **Release / Publish**
+with a persistent **Update** badge in the project sidebar and project chooser.
+There is no toast, system notification, or automatically opened dialog. Opening
+and closing the panel leaves the badge available until a successful check shows
+there is no newer release.
+
+Open **Release / Publish** for installed version, last check, release notes, and
+an optional manual retry. The panel distinguishes **Up to date**, **No release
+published**, and **Update check unavailable**. An offline check does not count as
+up to date or erase an update already discovered during this app-service session;
+the panel explains when a refresh failed. Manual retries bypass the automatic
+check cache.
+
+Managed installations with the official trusted signing key automatically
+download and verify a new release in the background. The download prepares a
+separate installation; it does not replace the running app or modify projects.
+The badge changes to **Ready** once preparation completes. Close Rieke OS and all
+project services, then launch normally through the stable manager or a workspace
+launcher created by the managed app. That launch applies the prepared update.
+If another service is still open, the current version continues to run.
+Incompatible database versions are refused. Automatic shutdown/restart from the
browser is not implemented. Development checkouts can check for releases but
cannot install them in place.
@@ -44,6 +98,10 @@ No signed release or trusted production key has been published by this change.
Release workflows and signing setup must be configured before downloads become
available to users.
+MATLAB is optional: the app runs without it. Only opening the EpicTreeGUI export
+in MATLAB needs a MATLAB installation. The self-contained desktop installer is
+still release-engineering work; the current source bundle requires setup tools.
+
## Open, move, or share a project folder
The main project catalog and user state remain MySQL/DataJoint. New projects use
@@ -82,9 +140,25 @@ interchange.
**Prepare to share** remains available when you want a separately verified copy
with a logical database backup and all registered recordings included. It tests a
-restore before reporting success. **Open prepared project** restores that optional
-transfer to a new local folder with fresh native database credentials. Existing
-projects are never overwritten.
+restore before reporting success. After **Close project**, **Prepare portable
+copy** opens sharing with that project's folder already selected. Give the entire
+prepared folder to the recipient. Their **Add new project** action recognizes it
+and restores it into a new folder with fresh native database credentials.
+Existing projects are never overwritten.
+
+The logical backup includes all acquisition and project database tables: saved
+predicates and every preset version, query run records, explorer revisions and
+bindings, tags and authors, review decisions, inclusion masks, tree layouts, source
+state, exports and event history. New copies record table row counts and content
+checksums; restoration compares every table before updating current file paths.
+Existing transfer copies without this additional database inventory remain readable.
+
+Recent search shortcuts and protocol pins/order are stored in
+`protocols/ui-state/project-preferences.json`. The app migrates earlier browser
+shortcuts when their project is opened, so these choices accompany subsequent
+copies. Browser size, pane widths and the current user's author selection remain
+local preferences. Durable import, app-job, error and storage history under
+`logs/` is included; server process records and runtime logs are excluded.
Legacy Docker projects keep their original configuration and data. They are not
silently converted. Preparing a logical transfer from such a project still needs
@@ -92,8 +166,8 @@ its original database available; restoring that transfer creates a native projec
that no longer needs Docker. Native-to-native transfers use the bundled server and
clients throughout.
-The optional logical transfer currently requires an initialized project database.
-Runtime logs, caches and existing backup directories are excluded from it.
+Runtime logs, caches and existing backup directories are excluded from logical
+transfers.
Historical export files retain their original bytes; external readers of old
exports may need embedded recording paths relinked. Interrupted logical restores
do not resume automatically; inspect the reported destination before retrying.
diff --git a/docs/design/app-icon-geometry-repair.md b/docs/design/app-icon-geometry-repair.md
new file mode 100644
index 0000000..d36ba2e
--- /dev/null
+++ b/docs/design/app-icon-geometry-repair.md
@@ -0,0 +1,7 @@
+# App icon geometry repair
+
+Built-in image_gen edit. Final asset: `workspace-app/public/rieke-os-icon.png`.
+
+## Prompt
+
+Correct the malformed R in this app icon. Inspect the top of the letter: there are unwanted pale diagonal wings/chevron extensions running down-left and down-right from the R's top bar parallel to the shield shoulders. REMOVE BOTH OF THESE WINGS COMPLETELY and fill their areas with matching purple. The outer shield perimeter must remain a single uninterrupted pale narrow outline. Inside the shield, draw a clearly recognizable rounded capital R: straight vertical stem on left, horizontal top bar starting exactly at the left stem and ending in the rounded right bowl, enclosed purple bowl counter, and graceful diagonal leg. The letter's top bar must NOT extend left beyond its vertical stem, and must NOT extend right beyond its rounded bowl. NO diagonal shoulders or chevrons on the letter's top. A clean purple gap must separate the complete top of the R from the outer shield border, and purple must also separate the R's upper left and upper right from the shield shoulders. Preserve the R's sweeping leg and purple negative space underneath, existing muted lavender/plum palette, rounded square tile, soft matte layered rim, transparent exterior. Keep the overall shield size and composition. This is a targeted geometry repair, no new decorative lines, no double shield, no inner chevron, no additional symbols. One finished app icon.
diff --git a/docs/design/app-icon-shadow-cleanup.md b/docs/design/app-icon-shadow-cleanup.md
new file mode 100644
index 0000000..176ef65
--- /dev/null
+++ b/docs/design/app-icon-shadow-cleanup.md
@@ -0,0 +1,7 @@
+# Shadow cleanup
+
+Edited with built-in image_gen. Final asset: `workspace-app/public/rieke-os-icon.png`.
+
+## Prompt
+
+Targeted cleanup of this exact R shield app icon. Remove the ugly nearly black/plum offset shadow wedges around the letter, especially below the upper-left junction and around the lower-right junction where the diagonal R leg meets the shield. Remove ALL dark extruded outlines/drop shadows immediately surrounding the pale R and pale shield. Exposed negative spaces should reveal a consistent muted lavender tile face, not near-black triangular pockets or heavy dark borders. Keep the pale R silhouette EXACTLY: separate straight top bar, round bowl, left stem, sweeping leg. Keep the pale single shield outline and purple empty area beneath R. Keep a clear lavender separation between R top bar and shield top. Preserve original rounded square tile and outer layered rim unchanged; ONLY clean the central emblem's dark outlines and shadow artifacts. Result is a clean flat pale lavender R and shield on a consistent mid-purple face, with precise smooth edges and no dark wedges, no extruded side faces, no black spots. No redesign or side extensions. Transparent outside rounded tile.
diff --git a/docs/design/app-icon-top-bar.md b/docs/design/app-icon-top-bar.md
new file mode 100644
index 0000000..74c90de
--- /dev/null
+++ b/docs/design/app-icon-top-bar.md
@@ -0,0 +1,7 @@
+# Top bar refinement
+
+Generated with built-in image_gen. Saved to `workspace-app/public/rieke-os-icon.png` and used by the sidebar and browser icon.
+
+## Prompt
+
+Make one precise edit to this purple R shield app icon. The pale top horizontal stroke of the R currently merges into the pale top border of the shield. Separate them: draw a clearly visible continuous purple negative-space horizontal gap between the shield's top inner edge and the R's own top bar. The R must have its OWN distinct rounded horizontal top bar that flows into its rounded right bowl, sitting below the shield top border. Keep a thin pale shield perimeter above it. Gap must be bold enough to remain visible at 38px icon size (roughly 35-45px high on this 1254px canvas). The R stays a connected recognizable pale R with rounded corners, stem and sweeping diagonal leg. Preserve purple negative space beneath R. Preserve all other styling, muted lavender and plum palette, rounded-square tile, rim, scale and transparent exterior. Do not add a bar above the shield. No text, no mockup. Output the corrected single icon.
diff --git a/docs/design/app-icon.md b/docs/design/app-icon.md
new file mode 100644
index 0000000..ab38c97
--- /dev/null
+++ b/docs/design/app-icon.md
@@ -0,0 +1,13 @@
+# Rieke OS app icon
+
+Asset: `workspace-app/public/rieke-os-icon.png`
+
+Generated with the built-in image_gen tool using the three user-supplied references (R shield, rounded blue icon style, purple sidebar palette).
+
+## Generation prompt
+
+Create one production app icon for Rieke OS. Reference 1 is the subject: preserve recognizable stylized R inside pentagonal diamond superhero shield. Reference 2 is style only: rounded square tile, thick layered inset border, restrained smooth gradient and dimensional offset relief. Reference 3 is palette/context only: muted lavender and deep aubergine sidebar. Make a purple rounded square tile with deep plum outer rim, lavender inner rim, violet-to-plum face, and a large high contrast pale lavender/ivory R shield emblem centered. Preserve the R with its broad curved upper bowl and diagonal leg, and its shield silhouette. Replace all red/yellow/blue with harmonious purples. Clean precise vector-like edges, bold simple legibility at 36px, minimal layers, no glossy sparkle, no text beyond the R, no additional symbols. Single straight-on square icon, almost fills canvas with only 2% transparent outer padding, genuinely transparent outside rounded corners. No mockup, no multiple versions, no background.
+
+## Final refinement prompt (built-in image_gen)
+
+Refine the app icon in reference 1 to belong to the actual UI in reference 2. Reference 3 shows the original custom R superhero shield identity. Final single production square icon with transparent outside rounded tile. Critical requirements: all enclosed negative space UNDER the R must be PURPLE tile background, NEVER white fill. The R should have rounded flowing sculpted curves like the fluid S in Superman's shield, but unmistakably an R, with rounded bowl, softened corners, and a gracefully sweeping curved diagonal leg. Keep leg and stem bold enough at 38px. The app visual language is quiet, warm, muted lavender #baa4ce, dusty lilac #d9cce4, aubergine #302538 and soft purple #685275. Use these subdued colors, no saturated electric violet. Rounded-square tile with a subtle two-layer inset rim inspired by the original blue app-icon reference, but reduce visual weight: soft matte lavender face, delicate slightly lighter top edge, darker plum lower rim. Central shield/R symbol in rich dark plum, lighter dusty lavender negative spaces matching tile. Narrow shield border with softened shoulder corners and softened bottom tip. Rounded R with broad curving strokes, no razor-sharp hooks. Whole icon feels like a beautifully crafted member of the existing app theme, restrained gentle depth, near-flat smooth colors. No glossy 3D bevel, no dramatic shadow, no texture, no grain. Central R shield as large and readable as possible with sensible breathing room. Single icon, straight-on, 2 percent transparent padding, no mockups, no explanatory text.
diff --git a/docs/design/electron-production-spec.md b/docs/design/electron-production-spec.md
new file mode 100644
index 0000000..1062a56
--- /dev/null
+++ b/docs/design/electron-production-spec.md
@@ -0,0 +1,261 @@
+# Rieke OS Electron production specification
+
+Status: implementation specification, 2026-09-29. Electron is the selected
+desktop direction following the user's preference. This specification replaces
+the AppKit/Sparkle proposal in the [earlier installer plan](one-click-installer.md).
+The [GitHub comparison](../dev/GITHUB_DESKTOP_UPDATE_PATTERNS.md) remains useful
+evidence about distribution mechanisms. No desktop installer is certified by
+this document.
+
+## Product contract
+
+Users install a complete app from Rieke-OS GitHub Releases and open its icon.
+The existing React interface runs in an Electron window. All required Python,
+parser utilities, database executables and frontend assets are bundled. First
+launch and ordinary use require no dependency downloads, Terminal, system Python,
+Node, Git, compiler, Homebrew, Docker, or MATLAB. Original recordings and projects
+are kept outside the app and are never part of an application update.
+
+New versions are checked automatically, downloaded quietly and shown through
+Release / Publish as Update or Ready. Installation waits for an orderly app quit;
+the next normal launch uses the prepared version. Closing a window, an OS exit
+event or an updater callback is not proof that scientific workers have stopped.
+Imports and other writes must finish or explicitly cancel through their existing
+transaction contracts before application replacement.
+
+First advertised platform: macOS Apple Silicon. Electron's cross-platform shell
+does not remove backend POSIX locks/process behavior or provide an Intel/Windows
+MySQL build. Support for other platforms remains a separate qualification gate.
+The minimum macOS version must be determined from the complete dependency closure
+and tested before release, rather than copied from Electron's minimum alone.
+
+Initial macOS delivery is a signed/notarized DMG containing the complete app and
+the ZIP required by the updater. To retain the requested Install and Open action,
+implement a first-open bootstrap mode before starting Python or any project.
+That mode copies this exact complete signed app to a fixed user-owned location
+such as `~/Applications/Rieke OS.app`, verifies the copied bundle, and launches
+the installed copy. It must preserve permissions, symlinks and signatures, handle
+an existing installation safely, and not overwrite a running app. Normal download
+and macOS first-open prompts still apply. This install action remains E06 work;
+electron-builder's DMG creation alone does not implement it.
+
+## Intended infrastructure
+
+| Component | Intended responsibility | Current gap |
+| --- | --- | --- |
+| Rieke-OS public repository | Canonical reviewed application baseline, release tags, documentation and version history | New development behavior lives in a separate dirty epicTreeGUI checkout. Reconcile deliberately. |
+| GitHub Actions | Build dependencies and frontend, run tests, assemble platform artifacts, sign/notarize, qualify and publish | Public CI currently verifies source installation; no desktop pipeline exists. |
+| Electron main process | Own application lifetime, launch authenticated local services, open project windows, track readiness, drain services and own updater | Not implemented. |
+| Sandboxed React renderer | Existing scientific UI, draft persistence and visible update status | Current UI is a browser app; desktop bridge/navigation/download behavior needs integration. |
+| Private Python runtime | Flask application served through a production WSGI server, pinned parser/wheels and compiled utilities | Existing venv and editable parser have absolute development paths. |
+| Private MySQL runtime | Server/client/dump tools and complete native library/plugin closure; one database per project | Source provisioning rewrites binaries on the destination. Relocation must move into the build. |
+| electron-builder | Platform installer, macOS DMG and ZIP update artifact, complete resource inclusion and update metadata | No desktop package/configuration or packaged runtime manifest yet. |
+| electron-updater | Sole desktop download/install authority, configured for the public Rieke-OS GitHub provider | Source updater must be disabled for desktop installations. |
+| GitHub Releases | Serve versioned installer/ZIP assets, blockmaps where generated and latest-mac.yml | Existing v0.1.2 assets are source ZIP and checksums. No separate feed service is required for this provider. |
+| Signing infrastructure | Developer ID identity/private key, notarization authentication and temporary signing keychain in CI | Account ownership and protected secret configuration remain required. |
+| Local user state | Preferences, service registry, diagnostics, update cache and recovery records | Must be separated from immutable application resources. |
+
+The desktop app must specify the GitHub owner/repository explicitly. Never ship
+an Actions token, signing key or private-repository credential in the client.
+Public users require no GitHub login. The RSA source manager and Sparkle appcast
+are not the selected desktop protocol; electron-builder/electron-updater metadata
+and macOS code-signature verification are the selected path.
+[Provider contract](https://www.electron.build/docs/features/auto-update/).
+
+Registry versions observed on 2026-09-29 were Electron 44.5.0, electron-builder
+26.15.3 and electron-updater 6.8.9. These are candidates for an exact lockfile,
+not installed dependencies or compatibility-tested pins. Do not implement APIs
+from newer documentation without checking the chosen package source. For example,
+the documented v7 autoInstallEvent/onNextLaunch feature must not be assumed in
+6.8.9 or treated as macOS behavior.
+
+## Resource and data ownership
+
+| Location | Contents and rules |
+| --- | --- |
+| Signed app resources | Electron main/preload, built React, standalone Python, installed dependency/parser wheels, native MySQL libraries, licenses and runtime manifest. Read-only after signing. |
+| Electron userData directory | Preferences, validated process registry, app-instance identity and startup/recovery receipts. Do not store scientific catalog contents here. |
+| User cache and log directories | Disposable runtime caches and bounded, redacted diagnostics; Python bytecode is disabled or redirected outside the signed app. |
+| User-selected project folder | SQL data, project credentials, catalogs, H5 source references, saved queries, annotations, masks and exports. Update operations do not alter these. |
+
+Executables belong in electron-builder extraResources, outside ASAR. Resolve them
+from process.resourcesPath, never PATH, the working directory, an editable source
+checkout, or an external uv installation. The runtime manifest records app/build
+version, source/parser commits, platform/architecture, Python/MySQL versions,
+workspace/database compatibility and packaged resource hashes. The manifest is
+covered by the signed app; an untrusted metadata file alone cannot authorize
+execution or a project migration.
+
+Parser configuration must support a user-state location. Its current generated
+config.ini inside the source package and bootstrap origin probe must be adapted
+for installed wheels. Preserve parser semantics and run source-fidelity checks
+after that change. Build native binaries and finish dependency-path rewrites
+before signing; no first-launch pip/conda setup or ad-hoc re-signing is allowed.
+
+## Reliability requirements and acceptance evidence
+
+These are release requirements, not claims about present behavior.
+
+| ID | Required invariant | Evidence that closes it |
+| --- | --- | --- |
+| R01 | Project ownership, original H5 identity/checksums and scientific state survive installation and updates. | Real import/tag/query/export/restart and source-user migration comparisons before/after. |
+| R02 | The artifact is self-contained and relocatable, with no operational reference to build-user directories or external dependencies. | Offline install/use under another standard user and path; packaged import and native dependency audit. |
+| R03 | Startup exposes the scientific UI only after the exact owned app/service versions are ready. | Wrong version, occupied port, failed import and duplicate-launch fault tests. |
+| R04 | Exactly one process owns desktop installation; renderers and Python APIs cannot independently install or restart code. | IPC/API authorization tests and a concurrent two-window update test. |
+| R05 | Active writers are never killed to install an update. | Imports, inbox ingestion, transfers and exports hold the update pending; every owned service acknowledges drain/exit. |
+| R06 | Network, metadata, signature, platform, interrupted-download and disk failures leave the current app usable. | Fault injection against real packaged update clients, including restart after interruption. |
+| R07 | The signed bundle is unchanged after launch, import, export and shutdown. | Signature verification and resource hashes before/after on downloaded artifacts. |
+| R08 | Incompatible workspace/database/MySQL formats never silently activate or downgrade. | Reject incompatible packaged candidates before installation; separately validate any future migration. |
+| R09 | A failed new backend/renderer startup reaches a recovery UI before opening or modifying projects. | Deliberately broken new-version health test and recovery using the last compatible signed artifact. |
+| R10 | Release promotion never points clients at partial, missing, mismatched or older assets. | Multi-job publication/retry/race tests and verification of final public URLs/hashes. |
+| R11 | Renderer content has no general filesystem, shell, process or updater authority. | Isolation/sandbox/CSP/navigation/IPC tests against malformed messages and foreign frames. |
+| R12 | Diagnostics support recovery without leaking credentials or scientific recordings into release artifacts. | Logs/receipt redaction, packaging inventory and reproducible failure reports. |
+
+Normal startup has a bounded readiness deadline with explicit failure information;
+choose the production timeout from measured cold/warm launches on the supported
+hardware. Backend request draining currently has a 30-second deadline. Reaching
+any drain deadline defers replacement and keeps recovery controls available; it
+does not authorize force-killing a writer. A process crash and OS shutdown need
+their own recovery tests, since Electron quit hooks are not universal shutdown
+acknowledgements.
+
+## Desktop service and renderer boundary
+
+Electron starts only its bundled interpreter and registered application entry
+points, passing private session identity and user-state paths. Python binds
+loopback only, with host/origin checks and a per-launch capability for desktop
+control. Credentials and capabilities are not sent to external pages or logs.
+Validate process records by PID plus executable/release, project identity and
+physical path; PID existence alone is insufficient after a crash or PID reuse.
+
+Initial integration may load the owned loopback UI to retain existing same-origin
+API behavior. Before release, test server impersonation and desktop capability
+handling; localhost is not an authentication mechanism. Use contextIsolation,
+renderer sandboxing and nodeIntegration disabled, restrictive CSP, blocked
+arbitrary navigation/popups and narrowly validated IPC. Open approved HTTPS
+release notes externally with no privileged bridge. File chooser/download
+integration must preserve the existing project path validation.
+[Electron security requirements](https://www.electronjs.org/docs/latest/tutorial/security).
+
+The preload bridge exposes typed operations and serialized status, not raw
+ipcRenderer, arbitrary URLs, shell commands or filesystem paths for execution.
+Validate sender window/frame and payload on every privileged operation.
+Draft persistence is acknowledged before orderly closure. A renderer crash must
+not imply permission to kill its backend or discard a job.
+
+## Update state machine
+
+```mermaid
+stateDiagram-v2
+ Current --> Checking
+ Checking --> Available: compatible published version
+ Checking --> Current: confirmed current
+ Checking --> Deferred: network or metadata failure
+ Available --> Downloading
+ Downloading --> Validating: complete cached artifact
+ Downloading --> Deferred: interrupted or failed
+ Validating --> Ready: signature and compatibility pass
+ Validating --> Deferred: invalid or incompatible
+ Ready --> Draining: orderly quit requested
+ Draining --> Ready: busy or unacknowledged service
+ Draining --> Installing: all writers and services closed
+ Installing --> HealthCheck: native updater relaunch
+ HealthCheck --> Current: packaged health verified
+ HealthCheck --> Recovery: startup failed
+```
+
+The main process publishes these states to all windows. Status survives normal
+restart where appropriate, and failed refresh does not erase a verified pending
+update. Configure automatic downloads and an hourly check with jitter; do not
+call a system-notification convenience API. A manual check remains available.
+Retries are bounded and distinguish transient failures from rejected artifacts.
+
+For the candidate electron-updater 6.8.9, explicitly set autoInstallOnAppQuit=false
+and leave downgrade/prerelease selection disabled for stable clients. Only our
+coordinator may call quitAndInstall, after package validation and all drain
+acknowledgements. Native Squirrel.Mac staging can occur later than the library's
+downloaded event; the Ready badge must not imply a signature/health guarantee
+that has not been established. Implement a candidate-validation step with exact
+expected app identity, platform, version and compatibility, and qualify it using
+the pinned updater's actual macOS behavior.
+[Pinned updater control](https://github.com/electron-userland/electron-builder/blob/electron-updater%406.8.9/packages/electron-updater/src/MacUpdater.ts).
+
+Do not call quitAndInstall first and rely on before-quit to save drafts: Electron
+documents that this path closes windows before that event. All preparation must
+precede the native installer call. Ordinary close, Quit, OS session-end and
+installer-triggered quit require separate tests.
+[Electron quit ordering](https://www.electronjs.org/docs/latest/api/app#event-before-quit).
+
+Rollback is not assumed from updater library selection. Retaining a signed prior
+installer and providing a recovery path without opening projects are required
+work. Recovery must verify app/data compatibility and use the same controlled
+installation boundary; it must not become a second arbitrary-code updater.
+
+## Release pipeline and controls
+
+In Rieke-OS, a reviewed version tag triggers the platform build. Build from a
+clean source baseline with exact JS/Python/native pins, compare all version and
+compatibility declarations, assemble resources, sign nested binaries/app,
+notarize and package final DMG/ZIP assets. Generate latest-mac.yml and hashes for
+the final bytes. Never sign an intermediate archive and mutate it afterward.
+
+Test both ordinary installation and an actual older-to-newer update using the
+exact artifacts destined for publication. Assemble a draft release until all
+required assets and evidence are present. Serialize promotion across versions,
+verify public artifact availability and updater metadata, and only then mark the
+complete release stable/latest. No raw-branch appcast or additional update
+service is needed with the selected electron-updater GitHub provider.
+
+Configure least-privilege build/publication jobs and protected signing secrets;
+ordinary PRs do not receive them. Reviewer protection must be explicitly enabled
+where used. Cache dependencies for speed but validate pins/hashes; a cache hit is
+not provenance. Retrying a release recovers the original artifact hashes rather
+than silently replacing public bytes. Stable and prerelease channels are
+explicitly separate. A withdrawn or incompatible release does not force users
+off a still-working installation.
+
+Each release evidence bundle records source/build versions, artifact hashes,
+platform/OS/architecture, packaged dependency inventory/licenses, signing and
+notarization results, fresh-install results, old-to-new results, native database
+restart/backup/restore and independent-client fault scenarios. Do not publish
+private recordings, local paths, database credentials or signing material.
+
+## Implementation gaps and build sequence
+
+| Work | Scope | Current status | Depends on |
+| --- | --- | --- | --- |
+| E01 | Reconcile reviewed public/development baseline and coordinated release version | Open; no source sync or release bump performed | None |
+| E02 | Build portable Python/wheels and relocated MySQL closure | Open; source runtime preflight is the first executable audit | E01 |
+| E03 | Externalize mutable state and implement signed runtime manifest/readiness contract | Open | E02 |
+| E04 | Electron package/main/preload, isolated renderer, owned backend supervision and production WSGI serving | Open | E02, E03 |
+| E05 | Global service registry, draft acknowledgements and drain/quit/crash recovery | Open; project-specific close is useful but incomplete | E04 |
+| E06 | electron-builder self-contained DMG/ZIP resources and first-open Install and Open mode | Open | E02–E04 |
+| E07 | Candidate validation, quiet update state, controlled installation and recovery | Open; source updater is not this desktop implementation | E05, E06 |
+| E08 | Developer ID/notarization configuration and signed platform qualification | Open; signing owner/secrets needed | E06 |
+| E09 | GitHub desktop release workflow, complete promotion and metadata | Open; source-candidate workflow cannot substitute | E07, E08 |
+| E10 | Source-user migration and clean-machine, multi-client, failure-injection qualification | Open | E05–E09 |
+| E11 | Intel/Windows/Linux backend support and independent native artifacts | Outside first advertised scope; POSIX/runtime gaps remain | Platform decision and dedicated evidence |
+
+Execute in that order. E02/E03 are the largest immediate packaging unknowns;
+resolve them before declaring an installer ready. Unit tests support individual
+contracts. Production acceptance also requires real clean-machine installation,
+real native database/scientific workflows and a real signed update. No count of
+passing mock tests closes those gates.
+
+## First implementation receipt
+
+The first bounded implementation is a read-only source-runtime preflight:
+[desktop_runtime_audit.py](../../tools/desktop_runtime_audit.py). It checks known
+external interpreter/venv/editable/configuration paths and source provenance,
+and records actionable blockers. It does not execute project code, start SQL,
+package an app, validate all dynamic libraries or certify production readiness.
+Its findings feed E02/E03; portable-runtime assembly and relocation remain open.
+
+The first [local preflight receipt](../dev/desktop-runtime-preflight.json) is
+blocked as expected: seven findings identify an external Python interpreter
+(including its three executable links), an absolute venv home, the editable
+parser import path and instance-specific parser configuration. The auditor
+inspected 3 Python path files and 3,049 runtime symlinks. Six fault tests pass,
+including rejection of external/escaping paths and malformed metadata and the
+requirement that even a clean static result never claim production qualification.
+This is baseline evidence, not a relocated-runtime pass or Electron build.
diff --git a/docs/design/external-tag-return.md b/docs/design/external-tag-return.md
new file mode 100644
index 0000000..985a314
--- /dev/null
+++ b/docs/design/external-tag-return.md
@@ -0,0 +1,75 @@
+# Automatic external tag return
+
+Status: initial receiving path implemented. Implementation details below supersede the earlier proposal where noted.
+
+## Decision
+
+Keep shared project annotations as the authoritative tag store. The receiving service scans registered export folders for external tag additions. Each export carries its own writable annotation return area beside its frozen database. No separate project inbox location needs to be supplied to Wheeler, and the user never reimports the dataset.
+
+```text
+project/exports//
+ recordings.sqlite frozen recording metadata
+ annotation-return.json format, identities, relative return path
+ annotations/
+ incoming/.json
+ receipts/.json
+```
+
+Wheeler reads the exported database, obtains exact epoch/cell UUIDs, and writes a tag message into that export's `annotations/incoming` directory. The project service discovers it, commits tags to the existing DataJoint annotation store, and updates the UI. A helper can make this a single `tag(target_uuid, tag, author)` operation for any external service; implemented by `workspace_external_tags.submit_tags` and its CLI.
+
+The SQLite file remains a frozen scientific handoff. Its current annotation tables are export-time snapshots, protected by mutation triggers; the export download route also verifies the saved artifact checksum. Modifying that file would invalidate the artifact check. A writable sidecar therefore gives the actor a place to tag the exported data while preserving the existing export contract. The sidecar can be attached to SQLite for analysis if needed, but JSON additions are sufficient for the initial receiver. Do not scan arbitrary changes in frozen tag tables or import the old snapshot as new assertions.
+
+## Receiving-end experience
+
+- Opening a project page automatically POSTs a scan of its registered export return directories.
+- Reloading the page scans again. The External tags control also offers Check for tags and shows scan state.
+- While a project page is visible, a sequential three-second poll picks up additions without user action; returning to the page checks immediately. Scan bounded return directories, not all recordings or arbitrary SQL files; a filesystem watcher can reduce latency but is not the correctness mechanism.
+- The epoch's ordinary Tags area gains the tag with Wheeler attribution. Existing direct/inherited scope and query fields remain in use. A publication-specific count of tagged child epochs is a future UI extension.
+- Existing tag search and author filters work after ingestion. The External tags control shows a received count; expanding it shows recent authors, counts, and receipt times. Normal success does not open a dialog.
+- The External tags control lists each return location, last checked time, pending/error count, and a “Check for tags” retry. Missing folders are shown as unavailable, never interpreted as tag deletions.
+
+Only a folder visible to the receiving machine can be scanned. The default is the app-managed export folder Wheeler opens in place. If Wheeler works on a copy elsewhere, deliver/synchronize its complete annotation messages into the original project export return folder. Registering arbitrary alternate folders is not implemented. A remote copy cannot communicate changes without a shared path, file transfer, or API. Do not synchronize a live writable SQLite database through a file-sync service; complete immutable JSON messages work better for that case.
+
+## What already exists
+
+- `python/workspace_annotations.py`: project-scoped `SharedAnnotation` records keyed by target kind, target UUID, and author profile UUID; revisions, locking, transactional audit, batch reads, and tag suggestions.
+- `python/workspace_tag_exchange.py`: portable `rieke-tag-exchange` v1 JSON; exact UUID/source checks; additive import preview/apply; author attribution. Repeated additions do not duplicate tags.
+- `python/workspace_sqlite.py`: exported `epoch_uuid`, `cell_uuid`, `project_uuid`, `export_uuid`, and source hashes; frozen `shared_annotations` and `annotation_revisions`. Exported `epoch_tags` instead means protocol curation.
+- `python/workspace_tag_predicates.py`: query fields for direct cell tags, direct epoch tags, effective tags, and authors.
+- Web components already distinguish direct epoch tags from inherited cell tags. UI saves invoke local refresh callbacks. There is no project-wide external annotation change observer in the inspected frontend.
+
+Today a service can use the existing HTTP preview/apply endpoints, but the portable-file workflow requires explicit import and the open UI has no automatic notification path.
+
+## Implemented transaction and discovery
+
+Both SQLite export paths create `annotation-return.json` and `annotations/incoming`. Existing registered SQLite exports receive these sidecars on first scan. The manifest lists exact targets and source hashes and supplies a message example; it is a convenience for writers, not the receiver's authority. The receiver validates membership against the database's registered export recipe and current acquisition identity registry.
+
+Writers publish uniquely named complete JSON messages using an atomic rename or the provided helper (which uses a temporary file and an exclusive hard link). Each message contains a fresh UUID, export UUID, and an existing v1 tag-exchange document. All entries must name this project and provide matching source evidence. Profile names are attribution claims consistent with the existing local model; no authenticated remote actor boundary is implied.
+
+`POST /api/annotations/scan` runs under the existing HTTP database lock and project annotation lock. It visits registered export folders only, rejects redirected folders/message symlinks, limits messages to 8 MiB, rotates through at most 100 messages per export per scan, and uses the existing bounded tag validation. Malformed messages report per-file errors without blocking other valid messages. Database failures abort the request and retry on a later poll.
+
+Additions and a receipt are committed together by `SharedAnnotations.apply_batch`. Receipts use the existing transactional Event table, with action `external_annotation_received`, rather than introducing a second receipt database. The key is project plus message UUID; a content hash binds the entire message including its authors and export. Same ID/content returns the saved receipt; same ID/different content is rejected. Unchanged-only submissions also receive a committed receipt. The project lock serializes independent consumers. Files remain immutable and receipts are reconstructed after crashes, so deleting a file receipt cannot replay a previously removed tag.
+
+Imported tags are additive. Missing tags/files never delete annotations. Existing explicit author-scoped annotation edits can remove tags; automated retraction commands are not part of this initial transport. Bad identities and missing targets are reported and retried without guessing matches.
+
+## Implemented UI observation
+
+`ExternalTagSync` starts a scan when a project page opens, polls sequentially every three seconds while visible, and checks on focus. Errors back off to 30 seconds. A manual Check for tags retries immediately. There is no background daemon: closed pages resume ingestion on reopen.
+
+The receiver returns a compact checksum of annotation target/author revision columns. This detects changes across processes without loading tag text or requiring a schema migration. It still scans revision rows and receipt events; a persisted sequence and indexed receipt table would improve very large histories later. The response includes only the latest 20 receipts plus total counts.
+
+On a changed revision (including the first scan), the UI invalidates epoch resource caches and advances its existing global resource revision, refreshing tags, summaries, vocabulary, and live queries. Late monitor responses after unmount are discarded and scans never overlap within one page. This uses existing view refresh behavior rather than introducing new selection semantics. Saved query evidence, pinned snapshots, and previous exports stay frozen.
+
+## Scope and querying
+
+For publication use, tag the exact epoch UUIDs actually used, for example `publication:paper-2026-01`. Record export/run/publication provenance with the import audit. Do not automatically tag the entire cell merely because one epoch was used: a cell tag inherits to every epoch of that cell, including later imports. The cell UI can instead show a derived summary such as “3 epochs used in paper-2026-01”; that summary must not become an inherited cell tag.
+
+A genuine cell classification should target `cell_uuid`. It appears on that cell and as inherited metadata on its epochs across protocols. Actors can read current tags using existing `/api/annotations/read`, `/api/cells//annotations`, `/api/epochs//annotations`, and `/api/annotation-tags` endpoints, and use existing annotation predicate fields for search. Historical SQLite exports answer what was known at export time; they do not answer the current live state.
+
+An optional HTTP submission endpoint can later feed the same ingest function and receipt ledger. It is a second transport, not a second tag system. Avoid direct actor writes to DataJoint tables, which bypass validation, audit, receipts, and revision notifications. Automatic synchronization across separate projects is also outside this initial design: delivery names one project explicitly, even if another contains the same acquisition UUIDs.
+
+## Validation
+
+`test_workspace_external_tags.py` exercises protocol SQLite export → actor helper → scan → current annotation API and tag query, unchanged artifact checksums, cell inheritance, unchanged receipts, restart recovery, replay after removal, transactional rollback on receipt failure, invalid identity/source isolation, changed-content rejection, missing folders, and existing-export sidecar creation. Candidate export tests verify their return manifest and exact subset.
+
+Frontend tests cover reopen refresh, stable-revision suppression, changed revisions, overlapping scan prevention, unmount races, and error recovery. Related annotation/cache tests and the production frontend build validate integration. Automated tests use disposable database doubles and files. A subsequent live browser test used a separate native MySQL 8 instance with real DataJoint tables and transactions, the real Flask routes, and the production React build. It verified automatic epoch display and search, preserved drafts, reopen ingestion, cell inheritance, invalid-target isolation, receipt recovery from another process, replay after removal, and unchanged SQLite bytes. See `docs/dev/external_tag_live_validation.json` and `docs/dev/external-tag-live.png`. Acquisition metadata was generated for two test cells; the research database was untouched.
diff --git a/docs/design/one-click-installer.md b/docs/design/one-click-installer.md
new file mode 100644
index 0000000..c6b053e
--- /dev/null
+++ b/docs/design/one-click-installer.md
@@ -0,0 +1,337 @@
+# Rieke OS one click installer plan
+
+Architecture status: the user has selected Electron. The
+[Electron production specification](electron-production-spec.md) is now the
+implementation authority. This earlier plan preserves the source audit, example
+systems and initial gap analysis; its AppKit/Sparkle proposal is superseded.
+
+Reviewed 2026-09-29. This plan covers installation and automatic updates for the
+public Rieke OS app. It is based on the current public distribution, the
+development updater, and a [review of primary documentation](../dev/INSTALLER_PRIMARY_SOURCE_REVIEW.md).
+It proposes implementation work; it does not claim that a desktop installer has
+been built or that the proposed update path has passed validation.
+
+The recommended first release is a complete macOS Apple Silicon app, built on
+GitHub, installed into a user-owned application directory, and updated as a whole
+signed app. Users should not need Terminal, Python, Node, Git, a compiler,
+Homebrew, Docker, or MATLAB. A native launcher should own all local services so
+an update can wait for an orderly app shutdown.
+
+## Installation and update experience
+
+The user downloads one installer from GitHub Releases, opens it, and chooses
+**Install and Open**. The proposed default destination is
+`~/Applications/Rieke OS.app`, which the current user can update without needing
+administrator access. The installer copies the complete app and opens it. Normal
+macOS download and first-open interactions still apply; one click describes the
+installer's action, not the entire download and operating-system flow.
+
+The app opens the existing browser interface after its local service is ready.
+Projects remain in folders chosen by the user. Subsequent launches use the same
+app icon. Available updates download in the background and appear in
+**Release / Publish** as **Update** and then **Ready**. Ordinary updates should
+produce no toast, system notification, or automatically opened dialog. When the
+user quits and all workers have closed cleanly, the updater installs the prepared
+version; the next launch uses it. Active scientific work must not be interrupted
+to satisfy an update schedule.
+
+First scope assumption: Apple Silicon Macs. Intel Macs and Windows require their
+own build and native integration evidence before support is advertised. MATLAB
+is optional and used only to open the EpicTreeGUI export; Python writes the MAT
+files and reads selection masks without running MATLAB.
+
+## What the inspection found
+
+The public repository is
+[`maxwellsdm1867/Rieke-OS`](https://github.com/maxwellsdm1867/Rieke-OS).
+Its inspected main commit and clean local checkout were
+`f52312f06a651500ea3e121feaebd118201d0f7b`. The development checkout is the separate
+epicTreeGUI repository at `47f87f9f1adc373523869ac624c35fdfe566b4e9`, with uncommitted
+changes. These are different application states; development behavior is not
+evidence of what public users already have.
+
+| Area | Published Rieke OS | Development checkout |
+| --- | --- | --- |
+| Release | [v0.1.2](https://github.com/maxwellsdm1867/Rieke-OS/releases/tag/v0.1.2), with a 1,463,427-byte source ZIP and SHA256SUMS | Coordinated metadata still says 0.1.0. Discovery now points to Rieke-OS. |
+| Installation | [install.sh](https://github.com/maxwellsdm1867/Rieke-OS/blob/f52312f06a651500ea3e121feaebd118201d0f7b/install.sh) downloads local tooling and dependencies and compiles/builds the app | Setup likewise builds Python/parser/frontend; managed launch can use prebuilt frontend assets. |
+| Launch | [start.sh](https://github.com/maxwellsdm1867/Rieke-OS/blob/f52312f06a651500ea3e121feaebd118201d0f7b/start.sh) requires a terminal and installed runtime | Python manager and chooser; no native desktop entry point. |
+| Automatic updates | No updater module or signed desktop update assets | Quiet badge, automatic source staging, and next-launch activation exist locally. Staging still executes setup. |
+| CI | [verify.yml](https://github.com/maxwellsdm1867/Rieke-OS/blob/f52312f06a651500ea3e121feaebd118201d0f7b/.github/workflows/verify.yml) verifies a fresh source installation | Signed source-candidate workflow and a proposed tag/publication job; these have not been carried into the public repo. |
+| Release protection | GitHub API returned no configured environments and no repository Actions secret names visible to this account | No distributed trusted key in this checkout. A workflow environment name alone does not establish reviewer protection. |
+
+The development runtime measured approximately 1.3 GB for the Python venv,
+302 MB for linked MySQL, and 45 MB for native package archives. These are local
+disk measurements, not the size of a finished app or download. The interpreter
+is an absolute symlink into the developer's uv installation, and the editable
+parser `.pth` contains an absolute checkout path. Copying this directory would
+not establish a portable app.
+
+## Existing GitHub distribution patterns
+
+GitHub can host the source, run the build, and serve the installer/update assets.
+The installed application still needs a desktop updater to select its platform,
+verify the downloaded package, and coordinate replacement. GitHub's automatic
+Source code ZIP is not a prebuilt installer, and a successful release upload
+does not by itself update users' computers.
+
+| Pattern | GitHub's role and client behavior | Implication for Rieke OS |
+| --- | --- | --- |
+| Native macOS with Sparkle | Complete app archives live in Releases; a static appcast links to exact archives/signatures. Sparkle runs in the app. | Proposed Mac-first route. GitHub can host the static feed too; no custom update server is necessary. |
+| Tauri with its updater plugin | Actions builds app/update artifacts; a static `latest.json` asset in Releases supplies platform URLs and signatures. The installed plugin checks it. | Strong alternative if Windows/Linux are immediate targets. Existing React can remain the frontend and Python can be packaged as a sidecar. |
+| Electron with electron-builder/electron-updater | The builder publishes installers and `latest*.yml` metadata to its GitHub provider; the app's updater reads the metadata and fetches binaries. | Established cross-platform path. Requires an Electron shell and a packaged Python/MySQL runtime; the updater does not remove backend packaging work. |
+| Electron Forge with update-electron-app | Forge publishes to Releases; Electron's hosted update service translates release assets into a client feed. | GitHub-based, but uses an additional hosted service. This is a distinct route from electron-builder's direct GitHub provider. |
+
+Sources: [Sparkle publication](https://sparkle-project.org/documentation/),
+[Tauri GitHub pipeline](https://v2.tauri.app/distribute/pipelines/github/),
+[Tauri static updater](https://v2.tauri.app/plugin/updater/),
+[Tauri Python sidecars](https://v2.tauri.app/develop/sidecar/),
+[electron-builder updater](https://github.com/electron-userland/electron-builder/blob/master/website/docs/features/auto-update.md),
+[Electron Forge publishing tutorial](https://www.electronjs.org/docs/latest/tutorial/tutorial-publishing-updating).
+
+Rectangle and MonitorControl are directly verified native Mac examples. Their
+app configurations embed Sparkle feed URLs and EdDSA public keys, and their live
+feeds point to signed version-specific DMGs in GitHub Releases.
+[Rectangle configuration](https://github.com/rxhanson/Rectangle/blob/c0ae7f87abe66f66b2857fbd4ad19ef802a9b43a/Rectangle/Info.plist),
+[Rectangle feed](https://rectangleapp.com/downloads/updates.xml),
+[MonitorControl configuration](https://github.com/MonitorControl/MonitorControl/blob/84ac2d72bfb53b653536e484946f6ed027e4229c/MonitorControl/Info.plist),
+[MonitorControl feed](https://monitorcontrol.app/appcast2.xml).
+These examples show GitHub serving app binaries while separate static URLs serve
+update metadata. Rieke OS can host its static feed on GitHub too. The inspected
+Rectangle public build workflow only produces an ad-hoc signed Actions artifact;
+it does not establish how its production signing/publication is automated.
+
+One directly inspected application is Electron Fiddle. Its
+[Forge configuration](https://github.com/electron/fiddle/blob/a63225b53664fd750828c29aa35de4b4d0eb0b28/forge.config.ts)
+publishes to `electron/fiddle` on GitHub, and its
+[updater code](https://github.com/electron/fiddle/blob/a63225b53664fd750828c29aa35de4b4d0eb0b28/src/main/update.ts)
+starts `update-electron-app` after a startup delay, configured for hourly checks.
+That demonstrates the connection between a release publisher and installed
+clients; it does not establish that Fiddle's notification or shutdown behavior
+matches our quiet-update and scientific-worker requirements.
+
+Use an existing framework's artifact generator, metadata format, signature
+verification and installer. Build our own app/service lifecycle adapter and
+compatibility rules. Neither a GitHub provider nor a sidecar configuration
+automatically drains our detached Python/MySQL workers, preserves browser
+drafts, or qualifies a database-version change. Do not use notification-producing
+sample APIs as the product behavior when routine updates should stay quiet.
+
+The desktop shell decision is conditional on platforms: AppKit/Sparkle is the
+recommended first route for the current Mac-first scope. If Windows must ship
+alongside macOS, evaluate Tauri before implementing I05 and I08. Electron is an
+alternative when its Chromium/Node desktop environment is specifically useful.
+These alternatives can reuse React and Python, but embedded webviews introduce
+different origins, download/dialog behavior and process ownership that must be
+tested. Framework selection does not change the I02/I03 runtime experiment.
+
+The [GitHub desktop update comparison](../dev/GITHUB_DESKTOP_UPDATE_PATTERNS.md)
+records the exact application sources, feed examples, and framework contracts.
+
+## Proposed architecture
+
+Use a small Swift/AppKit application to provide the app icon, launch readiness,
+duplicate-launch handling, Quit, service ownership, and updater integration.
+Keep React as the product interface in the browser. This does not require
+rewriting the scientific application in Swift or adding Electron.
+
+Bundle a pinned standalone Python interpreter, installed dependency wheels,
+the pinned parser and compiled utilities, built React assets, and a relocatable
+private MySQL server/client/dump dependency closure. Complete binary relocation
+and configuration of executable paths during the build. Sign the final code,
+notarize the product, and leave its contents read-only at runtime. Apple's
+signature seals the app's resources and nested code; current destination-side
+conda linking and ad-hoc signing cannot be used inside that sealed product.
+[Apple code-signing rules](https://developer.apple.com/library/archive/technotes/tn2206/_index.html).
+
+Proposed ownership layout:
+
+```text
+~/Applications/Rieke OS.app/ signed code and immutable resources
+~/Library/Application Support/RiekeOS/ preferences, service registry, update state
+~/Library/Caches/RiekeOS/ disposable runtime caches
+~/Library/Logs/RiekeOS/ application and installer diagnostics
+/ catalog, SQL data, credentials, H5 references, exports
+```
+
+The native launcher resolves resources from its actual bundle location and
+invokes its bundled interpreter by absolute path. Python bytecode, parser
+configuration and temporary files must go outside the bundle or be disabled.
+`doctor` validates the packaged layout and signed build receipts, rather than
+requiring a Git checkout, editable package location, or Node installation.
+
+The default packaging approach is a standalone interpreter with preinstalled
+packages and a parser wheel. PyInstaller `onedir` workers are a fallback to
+evaluate if that layout fails; a choice between these approaches must be based
+on the relocation/import experiment, not an assumption that either handles
+dynamic parser imports and native MySQL automatically.
+
+### One desktop updater
+
+**Recommendation:** use Sparkle for complete signed app updates. The native app
+owns update state; React displays it through a local bridge. Disable the Python
+manager's staging/activation routes in desktop builds. Its useful existing
+tests become requirements for the new path, not proof that Sparkle preserves
+those behaviors automatically. Source installations may retain a separate
+explicitly identified updater if we choose to support them.
+
+This changes the earlier source-updater direction: a shipped app should download
+prebuilt software, not run `rieke.py setup` on the user's computer. The current
+manager also lacks an independent bundled bootstrap interpreter, uses an RSA
+manifest instead of Sparkle's EdDSA/appcast contract, reads downloads into memory,
+limits artifacts to 512 MiB/extraction to 2 GiB, and rejects every symbolic link.
+That extractor is unsuitable for a normal Sparkle framework bundle, whose
+symlinks and executable permissions must be preserved.
+[Current manager](../../python/workspace_updates.py),
+[Sparkle setup](https://sparkle-project.org/documentation/).
+
+Sparkle's automatic checks and automatic downloads need explicit configuration;
+they are not silent defaults. Its scheduled-check minimum is one hour. Proposed
+desktop policy: a startup check when appropriate and hourly automatic checks,
+with the UI text updated accordingly. The current 15-minute development watcher
+must not remain as an independent download/install authority. If 15-minute
+discovery becomes a hard requirement, assess that separately before committing
+to this updater policy.
+[Sparkle customization](https://sparkle-project.org/documentation/customization/).
+
+Keep ordinary updates pending until orderly app quit, including the framework's
+impatient-update behavior. A custom Sparkle user driver cannot merely hide all
+UI: automatic-update settings provide silent installation, and manual actions
+or necessary authorization still need a conforming interface. A user-owned app
+location reduces authorization requirements; managed Macs may impose other
+policies. Verify these cases in the product rather than promise that all updates
+can always be invisible.
+[Sparkle user-interface contract](https://sparkle-project.org/documentation/custom-user-interfaces/).
+
+## Gap map and implementation order
+
+All entries below are open for the desktop product, even where a useful source
+implementation exists. IDs are implementation work packages, not filed issues.
+
+| ID | Gap and concrete work | Depends on | Completion evidence |
+| --- | --- | --- | --- |
+| I01 | Establish Rieke-OS as the public build authority; selectively reconcile the development changes and coordinate app, npm, bundle and tag versions. | None | A clean reviewed public commit reproduces the chosen feature baseline and version checks. No wholesale replacement of the independently validated public app. |
+| I02 | Build a portable Python layout and noneditable parser wheel; include compiled utilities and data files; adapt parser configuration and readiness probes. | I01 | Copy to a different user/path and run all imports/parser work offline with no external interpreter or checkout. |
+| I03 | Prepare the complete private MySQL runtime in CI; resolve runtime load paths, plugins, TLS/share resources and licenses before signing. | I01 | Server/client/dump, actual import and dump/restore work after relocation, with no system MySQL or post-install code rewriting. |
+| I04 | Move all mutable preferences, caches, compatibility configuration and receipts out of app code. Preserve user-selected project locations. | I02, I03 | Read-only app works; launch, imports and quit leave its signature valid. |
+| I05 | Add native lifetime owner, readiness checks, occupied-port handling and a bundled production WSGI server on loopback; retain project origins where possible. | I02, I04 | Double-click launch reaches the UI once; duplicate launch reuses the right instance; failures are visible without Terminal. |
+| I06 | Coordinate every chooser/project service, inbox/import, export/transfer and database during Quit; preserve browser drafts and acknowledge shutdown. | I05 | Busy work defers Quit/update; all admitted requests drain and all owned databases close cleanly before native termination. |
+| I07 | Package the complete app and a small Install and Open helper into a downloadable installer. Copy to a permanent user-owned location without downloading dependencies. | I03–I05 | Fresh standard user installs offline using the native action and launches without developer tools. |
+| I08 | Integrate Sparkle as the sole desktop updater, signed appcast, compatibility filtering and React Update/Ready state. | I05, I06 | Real older app downloads a complete update quietly; it stays pending during work and installs only after safe quit. |
+| I09 | Configure Developer ID signing, hardened runtime, notarization and Sparkle EdDSA signing; verify the complete nested-code closure. | I02–I07 | Browser-downloaded artifact passes Gatekeeper/notarization checks and still verifies after real use. |
+| I10 | Replace the source-candidate publication path with a prebuilt desktop pipeline; publish versioned assets before advertising them in the stable feed. | I08, I09 | Tag produces the exact tested/notarized installer and update archive; failed jobs never advance the stable feed. |
+| I11 | Provide crash/startup recovery and previous-version restoration within the same updater authority. Port database-generation restrictions. | I06, I08 | Failed new startup can recover without losing data; incompatible schema or MySQL formats never silently activate or downgrade. |
+| I12 | Migrate existing v0.1.2 source users through one explicit desktop install, preserving projects/preferences and retiring old launch paths safely. | I06–I08, I11 | A real existing project reopens with the same source references, tags, queries and export history after old services stop. |
+| I13 | Run the full clean-machine and multiple-client qualification below, then publish the installer. | I07–I12 | Recorded passing evidence for the actual final artifact, not just a source build or mocked update. |
+
+Critical path: **I01 → I02/I03 → I04 → I05/I06 → I07/I08 → I09/I10 → I11/I12 → I13**.
+Signing account preparation can start earlier, but final code is signed only
+after relocation and bundle assembly. Recovery and compatibility design should
+be specified before I08, even though their full tests depend on it.
+
+### Why shutdown is a first release requirement
+
+The development [project lifecycle](../../python/workspace_lifecycle.py) already
+drains requests and refuses closure while imports are active. Extend that
+contract to every service and work type, and make the native app own the registry
+and final acknowledgement. The chooser currently has no equivalent coordinated
+native Quit. Published Rieke-OS documents that Ctrl-C stops the chooser while
+previously opened project services and databases may remain running.
+[Published shutdown behavior](https://github.com/maxwellsdm1867/Rieke-OS/blob/f52312f06a651500ea3e121feaebd118201d0f7b/docs/RIEKE_OS_QUICK_START.md).
+
+Sparkle may install when the native app terminates. Therefore it is insufficient
+to say an update waits until the app closes while detached workers keep using
+the old runtime. Reject or postpone native termination until all workers have
+acknowledged closure. After a crash, reconcile recorded processes before allowing
+replacement. App file locks alone do not automatically constrain Sparkle.
+[Sparkle termination contract](https://sparkle-project.org/documentation/api-reference/Protocols/SPUUpdaterDelegate.html).
+
+## GitHub release pipeline
+
+Proposed stable pipeline in Rieke-OS:
+
+```mermaid
+flowchart LR
+ A[Push reviewed version tag] --> B[Tests and version checks]
+ B --> C[Build portable Python and MySQL]
+ C --> D[Assemble native app and installer]
+ D --> E[Sign and notarize]
+ E --> F[Qualify exact final artifacts]
+ F --> G[Publish versioned GitHub Release assets]
+ G --> H[Advance signed stable appcast]
+ H --> I[Clients download quietly]
+ I --> J[Install after acknowledged app quit]
+```
+
+Keep the source installer as a developer fallback, clearly separate from the
+desktop download. A branch push runs regression CI; a stable version tag declares
+release intent. No user's installed app executes `git pull` or tracks arbitrary
+branch commits.
+
+Publish two product assets: the initial installer and a whole-app update archive
+containing only the installed app. Build both from the same signed app. Generate
+Sparkle signatures/appcast from the final archive bytes, after any packaging or
+stapling changes. The existing RSA source manifest does not substitute for this
+appcast.
+
+Use GitHub Release URLs for versioned binaries. Proposed feed location is a
+stable HTTPS URL in the same repository, initially a raw `updates/stable`
+appcast branch, updated by CI after assets exist. Validate GitHub redirects,
+cache behavior, feed signatures, compatibility filtering and roll-forward
+ordering with an actual client. GitHub Pages is an alternative feed host, not a
+prerequisite for a separate backend service.
+
+The feed must never point at an unpublished/missing asset. Retrying publication
+must recover the same artifact hashes, not overwrite a public release with
+different bytes. Serialize promotions across versions so an older build finishing
+late cannot replace the newer stable feed. No version is advertised before its
+operating-system, architecture and database-compatibility checks pass. Schema
+changes require a separate tested backup/migration/recovery path.
+
+Configure GitHub environments explicitly where approvals are wanted; the field
+in a workflow does not itself enable reviewers. Credentials needed are Developer
+ID certificate/private key, notarization authentication, and the Sparkle update
+signing key. These are separate from the current RSA source signing key.
+Keep secrets in the selected secret system, expose them only to signing jobs,
+and test restoration/rotation procedures. They were not created or changed by
+this planning pass.
+
+## Qualification before the public installer
+
+| Scenario | Required result |
+| --- | --- |
+| Clean supported Mac, standard user, no development tools, network disabled after download | Install and first project creation/import work using only the artifact and OS facilities. A GitHub runner with development tools hidden from PATH is useful, but not sufficient proof. |
+| Downloaded/quarantined app, alternate user, spaces/Unicode and moved application path | Native launch, Python imports and MySQL paths work; bundle signature remains valid. No quarantine removal workaround. |
+| Real original H5 and existing source-user project | Import, browse trace, tag, save query, export, restart, and dump/restore retain original identities and source checksums. Validation data is supplied separately from the app. |
+| Old-to-new release on two independent clients | Both discover/download the same published version; one busy client stays on its old version while the idle client updates. Their projects/settings remain independent. |
+| Active import, inbox ingestion, transfer/export, open second project and unsaved browser draft | Safe quit is postponed or drafts are preserved; no worker is killed to make an update succeed. All owned services acknowledge shutdown before installation. |
+| Offline server, invalid signature, wrong platform, truncated download, disk-full and permissions failure | Current app remains usable; incomplete updates are not activated. Necessary action is available in the release panel. |
+| New version fails startup or uses incompatible project/MySQL formats | Recovery restores compatible app behavior without silently migrating or downgrading data. Previous-app retention alone does not count as tested recovery. |
+| No MATLAB installation | All core installation, launch, data and update scenarios pass. Optional MATLAB GUI interoperability has its own validation claim. |
+
+Test the exact notarized installer and archive that will be published. Preserve
+machine/platform, versions, artifact hashes and pass/fail receipts. Neither the
+current unit tests nor dummy-process manager tests establish these product gates.
+Keep private recording data and credentials out of public release artifacts and
+validation logs.
+
+## Decisions and next executable experiment
+
+Proposed defaults are Apple Silicon macOS first, a user-owned app location,
+Sparkle as the sole desktop updater, hourly scheduled checks, and separate
+project storage. Still decide the minimum macOS version, whether Intel/Windows
+are required for the first release, who owns the signing identities, and the
+publication approval policy before claiming support.
+
+Start with **I01 and the I02/I03 relocation experiment**, not the installer UI.
+Produce a prebuilt payload from a clean reviewed application baseline, move it
+to a different path, use a minimal environment with dependency downloads disabled,
+and exercise real parser imports plus MySQL import/dump/restore. Record every
+runtime access outside the payload and user data directories. No source venv
+copy, editable install, external executable, or destination-side code rewrite
+may be necessary for that pass.
+
+If this experiment fails, fix the packaging closure or evaluate the documented
+PyInstaller fallback before building the native installer. If it passes, add the
+read-only bundle configuration, lifetime owner and installer. This order turns
+the largest unknown into a concrete pass/fail result before investing in the
+download and launch interface.
diff --git a/docs/dev/GITHUB_DESKTOP_UPDATE_PATTERNS.md b/docs/dev/GITHUB_DESKTOP_UPDATE_PATTERNS.md
new file mode 100644
index 0000000..70b5cd5
--- /dev/null
+++ b/docs/dev/GITHUB_DESKTOP_UPDATE_PATTERNS.md
@@ -0,0 +1,135 @@
+# GitHub desktop installation and update patterns
+
+Reviewed 2026-09-29 against project source and official framework documentation.
+Pinned commits make the inspected implementations reproducible; live feeds and
+release URLs can change. These are comparative findings, not implementations
+already added to Rieke OS.
+
+## The reusable pattern
+
+GitHub serves distinct jobs: source control, CI execution, downloadable Release
+assets, and optionally a static update feed. The installed **client updater**
+reads metadata, chooses a compatible version, verifies its artifact and controls
+installation. Users do not clone/pull the source repository. A successful CI
+build artifact is not automatically a public, trusted update.
+
+## Two concrete native macOS examples
+
+### Rectangle uses Sparkle and GitHub Releases
+
+At commit `c0ae7f87abe66f66b2857fbd4ad19ef802a9b43a`, Rectangle embeds
+`https://rectangleapp.com/downloads/updates.xml` and a Sparkle EdDSA public key in
+its [Info.plist](https://github.com/rxhanson/Rectangle/blob/c0ae7f87abe66f66b2857fbd4ad19ef802a9b43a/Rectangle/Info.plist).
+Its native [AppDelegate](https://github.com/rxhanson/Rectangle/blob/c0ae7f87abe66f66b2857fbd4ad19ef802a9b43a/Rectangle/AppDelegate.swift)
+constructs `SPUStandardUpdaterController`, exposes manual checks and supports
+gentle scheduled reminders.
+
+The [live appcast](https://rectangleapp.com/downloads/updates.xml) contains
+version/build metadata, exact file lengths and EdDSA signatures. Its current
+full update points to the [v2.0.2 GitHub DMG](https://github.com/rxhanson/Rectangle/releases/tag/v2.0.2),
+with optional signed deltas also hosted as Release assets. The feed host and
+artifact host are independent; Sparkle does not infer metadata from a Git tag.
+
+Its visible [build workflow](https://github.com/rxhanson/Rectangle/blob/c0ae7f87abe66f66b2857fbd4ad19ef802a9b43a/.github/workflows/build.yml)
+archives the app, signs ad-hoc, creates a DMG and uploads an Actions artifact.
+It does **not** show the production Developer ID/notarization/feed-publication
+pipeline. Copying that workflow alone would not reproduce its public updater.
+
+**Applicable lesson:** Adopt the feed-to-signed-complete-app pattern and native
+update-state ownership. The existing React release panel can remain the visible
+interface. Do not copy Rectangle's user-notification policy as Rieke OS's policy.
+
+### MonitorControl uses signed GitHub downloads and explicit channels
+
+At commit `84ac2d72bfb53b653536e484946f6ed027e4229c`, MonitorControl embeds
+`https://monitorcontrol.app/appcast2.xml` and an EdDSA public key in its
+[Info.plist](https://github.com/MonitorControl/MonitorControl/blob/84ac2d72bfb53b653536e484946f6ed027e4229c/MonitorControl/Info.plist).
+Its [AppDelegate](https://github.com/MonitorControl/MonitorControl/blob/84ac2d72bfb53b653536e484946f6ed027e4229c/MonitorControl/Support/AppDelegate.swift)
+creates and starts a Sparkle controller. Its
+[UpdaterDelegate](https://github.com/MonitorControl/MonitorControl/blob/84ac2d72bfb53b653536e484946f6ed027e4229c/MonitorControl/Support/UpdaterDelegate.swift)
+selects the beta channel only when the corresponding preference is enabled.
+
+The [live feed](https://monitorcontrol.app/appcast2.xml) encloses the exact
+version-specific GitHub URL for the [v4.4.0 DMG](https://github.com/MonitorControl/MonitorControl/releases/tag/v4.4.0),
+its EdDSA signature and length (20,391,423 bytes, matching the Release asset
+observed during review). No production release workflow was present in the
+inspected repository tree; publication mechanics beyond these artifacts are
+not established by this inspection.
+
+**Applicable lesson:** Separate stable and beta selection explicitly and keep
+artifact URLs bound to specific releases. Neither example implies their
+application code, dependency choices or release scripts should be copied wholesale.
+
+## Cross-platform frameworks
+
+| Approach | GitHub role | Installed client mechanism | Fit for this app |
+| --- | --- | --- | --- |
+| Native shell + Sparkle | Releases hold signed app archives; a static host supplies appcast XML | Sparkle verifies and replaces the complete app | Evaluated native macOS alternative |
+| Tauri updater | Releases hold updater archives/signatures and optionally `latest.json` | Tauri plugin checks, downloads and installs | Credible if a cross-platform webview shell becomes a requirement |
+| Electron + electron-updater | GitHub provider discovers Release metadata/artifacts | electron-updater manages download/install state | Credible if Electron is otherwise wanted; it adds its own runtime |
+| Electron Forge + update-electron-app | Releases hold binaries; `update.electronjs.org` supplies a feed | Electron/Squirrel consumes that service | Demonstrates GitHub-based delivery with an additional service |
+
+Tauri's [official updater guide](https://v2.tauri.app/plugin/updater/) documents
+mandatory artifact signatures, a bundled public key and the static endpoint
+`https://github.com/user/repo/releases/latest/download/latest.json`. Each
+platform entry requires its artifact URL and signature. The application calls
+the updater API; the existence of metadata does not define check scheduling or
+safe worker shutdown. [Tauri Action](https://github.com/tauri-apps/tauri-action)
+builds/publishes assets and generates that JSON. It explicitly warns that
+latest-download artifact URLs can fail when other releases lack updater bundles.
+
+Tauri supports [external binaries](https://v2.tauri.app/develop/sidecar/), with
+architecture-specific filenames. **Inference:** Python and MySQL could be
+bundled sidecars while retaining React, but their portable dependency closure,
+signing and orderly lifetime still require engineering. Switching to Tauri is
+not necessary to solve GitHub hosting.
+
+[electron-builder's official update guide](https://www.electron.build/docs/features/auto-update/)
+supports a GitHub Releases provider, generates internal `app-update.yml`, and
+emits availability/progress/downloaded events suitable for custom UI. Automatic
+download is configurable; the notification convenience API is unnecessary for
+a quiet badge. macOS uses Squirrel.Mac's native staged update/relaunch behavior;
+Windows/Linux installer details differ. It also requires signed macOS apps and
+the corresponding ZIP update target. **Inference:** Electron could retain the
+React UI and packaged Python workers, but it is a product-shell decision rather
+than a prerequisite for automatic updates.
+
+As a real third example, Electron Fiddle at
+`a63225b53664fd750828c29aa35de4b4d0eb0b28`
+[publishes through Forge's GitHub publisher](https://github.com/electron/fiddle/blob/a63225b53664fd750828c29aa35de4b4d0eb0b28/forge.config.ts)
+and [initializes update-electron-app](https://github.com/electron/fiddle/blob/a63225b53664fd750828c29aa35de4b4d0eb0b28/src/main/update.ts)
+with `electron/fiddle`, a one-hour interval and ten-second startup delay.
+[Electron's tutorial](https://www.electronjs.org/docs/latest/tutorial/updates)
+explains the default `update.electronjs.org` service and restart dialog. This
+is neither GitHub-only hosting nor the requested quiet UX by default.
+
+## Proposed GitHub deployment for Rieke OS
+
+Use GitHub Releases for immutable, version-specific DMG/ZIP assets. Publish the
+Sparkle appcast at a stable GitHub Pages URL, or a protected branch's raw URL.
+[GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/what-is-github-pages)
+is a static hosting service, so a separate update server is unnecessary for this
+basic channel. The raw-branch option is an engineering choice to validate, not
+a guarantee of freshness or response headers. Use the raw file rather than a
+GitHub HTML file view.
+
+GitHub [documents latest-release asset links](https://docs.github.com/en/repositories/releasing-projects-on-github/linking-to-releases),
+but such a redirect is not an update manifest. Every promoted release must have
+the correct metadata asset if using that pattern. Prefer version-specific
+artifact URLs within a stable feed. Test redirects, content type, cache headers,
+stale-feed responses, offline behavior and publication races from the installed
+client; no promise of instant delivery follows from hosting on GitHub.
+
+The release sequence should be: build/relocate dependencies; sign and notarize;
+package preserving symlinks; generate signatures/feed; upload all assets; verify
+public URLs and signatures; publish the stable feed last. Sparkle's
+[publication guide](https://sparkle-project.org/documentation/publishing/)
+specifies complete app archives, symlink preservation and signature metadata.
+
+**Selected direction:** Following the user's preference, retain React and
+Python/MySQL, add an Electron lifetime owner, and use electron-builder and
+electron-updater with the direct GitHub provider. The
+[production specification](../design/electron-production-spec.md) defines that
+contract. This replaces the bespoke source updater for desktop activation; no
+Sparkle updater runs alongside it. The largest remaining gaps are the sealed
+portable runtime and worker shutdown contract, not GitHub hosting.
diff --git a/docs/dev/GITHUB_TESTING_RELEASE.md b/docs/dev/GITHUB_TESTING_RELEASE.md
new file mode 100644
index 0000000..0408f26
--- /dev/null
+++ b/docs/dev/GITHUB_TESTING_RELEASE.md
@@ -0,0 +1,45 @@
+# GitHub unsigned desktop testing releases
+
+The testing distribution is explicit in `desktop/distribution.json`. It uses the
+public `maxwellsdm1867/Rieke-OS` repository and remains separate from Developer ID
+signed production installation and updates.
+
+Users download the complete Apple Silicon DMG from a release asset link, open
+Rieke OS, and click **Install and Open**. The app copies its complete private
+runtime to the current user's Applications folder. No Terminal, Docker, Python,
+Node, or external MySQL setup is required. An unsigned download may require
+macOS **Privacy & Security → Open Anyway** approval before it can run.
+The app has a structurally verified local ad-hoc bundle seal, which needs no
+certificate and provides no Apple developer identity. The installer retains quarantine attributes and does not bypass Gatekeeper.
+
+In the app, **Release / Publish** displays a newer available version. Metadata
+checks run at startup and about once an hour; **Check for updates** also runs on
+request. A notice never starts a download. Users choose **Download update**, then
+**Restart to update** once the archive and app resources have been verified.
+Active imports, unsaved drafts, and scientific services can defer the restart.
+An ordinary Quit does not install a testing update.
+
+Testing updates trust the official repository over HTTPS and compare the exact
+archive, shell, runtime manifest, resource inventory, version, architecture,
+macOS minimum, and database/workspace formats. These checks do not establish an
+Apple Developer ID signature. The current trusted app validates the download;
+downloaded Python or app code is not executed during validation. A dedicated
+current-app helper waits for actual process exit, retains the prior bundle, and
+installs only to the same user-owned app path. Scientific projects stay in their
+selected folders.
+
+Each testing release includes `desktop-release.json`, the DMG, complete app ZIP,
+checksums, and local qualification receipts. Its tag starts with
+`desktop-test-v`, it is labeled **unsigned testing**, and it is a prerelease so it
+does not replace the existing stable source release. Never overwrite published
+assets under an existing version. A failed or interrupted download can be
+retried; incompatible or changed candidates must remain uninstalled.
+
+Production signing/notarization and a clean-machine test are separate open
+qualification requirements. The testing release must not be presented as a
+signed production release.
+
+For a future signed production release, commit and review `channel: signed` in
+`desktop/distribution.json` before building with signing credentials. The signed
+CI job rejects the unsigned testing policy. Changing certificates alone does not
+change the app’s installation/update trust policy.
diff --git a/docs/dev/INSTALLER_PRIMARY_SOURCE_REVIEW.md b/docs/dev/INSTALLER_PRIMARY_SOURCE_REVIEW.md
new file mode 100644
index 0000000..4e1f170
--- /dev/null
+++ b/docs/dev/INSTALLER_PRIMARY_SOURCE_REVIEW.md
@@ -0,0 +1,227 @@
+# Rieke OS installer source review
+
+The later [Electron production specification](../design/electron-production-spec.md)
+records the selected architecture. The Sparkle recommendation below was the
+earlier alternative; the packaging, signing and service-lifetime constraints
+remain relevant.
+
+Reviewed 2026-09-29. Scope: Apple Silicon macOS, a local Python/Flask service,
+React assets, private MySQL, and releases on GitHub. This is planning evidence;
+no installer or signed update was built in this review.
+
+## Recommended architecture
+
+Use one immutable, Developer-ID-signed and notarized `Rieke OS.app`. A small
+native AppKit launcher owns the app lifetime, starts the bundled Python services,
+opens the local UI, and drains those services before quitting. Bundle the
+prebuilt frontend, portable Python with installed packages/parser/utilities, and
+the already-relocated MySQL dependency closure. Keep projects, SQL data,
+configuration, logs, update caches and receipts outside the app bundle.
+
+Use Sparkle as the **only desktop update authority**, replacing complete signed
+app bundles. Adapt Release / Publish to display its state and open its controls.
+Do not let the existing Python manager concurrently stage/activate desktop
+code. Keep that manager only for explicitly separate source installations if
+they remain supported.
+
+This supersedes the current custom source-activation design for desktop builds;
+it is a proposed change in update architecture, not functionality already shipped.
+
+This recommendation follows Apple's sealed-bundle rules and Sparkle's existing
+signed bundle-update machinery, rather than proving our bespoke source manager
+into a new macOS installer/updater. The repository's existing project locks,
+database-generation gates and staging tests remain useful specifications; they
+are not automatically preserved by adopting Sparkle.
+
+## Source facts and implications
+
+### Signing and notarization
+
+**Fact:** Apple requires Developer ID signatures, hardened runtime and secure
+timestamps for notarization, with valid signatures on distributed executables.
+Its notary service supplies a ticket that can be stapled to the distributed
+software. Current submission uses `notarytool`, not the retired `altool`
+workflow. [Apple notarization requirements](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution).
+
+**Fact:** Modern bundle signatures seal essentially all content. Apple says to
+treat signed bundles as read-only, sign nested code before the enclosing bundle,
+and put scripts/resources and executable code in appropriate locations.
+Absolute links to libraries outside the bundle can fail Gatekeeper.
+[Apple TN2206](https://developer.apple.com/library/archive/technotes/tn2206/_index.html).
+
+**Implication:** First-launch `pip` installation, Python bytecode written inside
+the bundle, frontend builds, conda prefix rewriting, ad-hoc re-signing and
+receipts inside `Contents` are incompatible with the intended sealed product.
+Finish code transformations in CI, then sign. Route bytecode/cache writes outside
+the bundle or disable bytecode writes. Never use a local ad-hoc signature as the
+distribution identity. Entitlements must be proven by the packaged imports and
+subprocesses; avoid broad exceptions by default.
+
+**Repository gap:** The custom source archive extractor rejects symlinks and
+limits archives to 512 MiB compressed / 2 GiB extracted. It cannot be reused
+unchanged for framework-containing `.app` bundles. Sparkle explicitly requires
+packaging to preserve symlinks and executable permissions.
+[Sparkle bundle integration](https://sparkle-project.org/documentation/).
+
+### Relocation and downloaded-app behavior
+
+**Fact:** App Translocation runs a downloaded app at a randomized path. Apple
+does not document all trigger conditions and provides no supported detection
+or way to recover the original path. Resources outside the bundle, found by
+walking from its path, can break. Apple recommends making the app work while
+translocated. [Apple DTS App Translocation Notes](https://developer.apple.com/forums/thread/724969).
+
+**Implication:** Resolve packaged resources from the actual bundle, never the
+shell's working directory or a build-time checkout. Test launch after browser
+download, from Downloads, after moving the app, and with spaces/non-ASCII paths.
+A supported install flow should put the app in a writable permanent location;
+do not rely on deleting quarantine attributes or reconstructing original paths.
+
+### Python packaging choices
+
+**Fact:** `python-build-standalone` publishes portable distributions, including
+`aarch64-apple-darwin`, with `install_only` archives for running Python. Its
+current release home is Astral's GitHub repository. [Project running guide](https://gregoryszorc.com/docs/python-build-standalone/main/running.html).
+
+**Fact:** Build-time absolute paths remain in configuration such as sysconfig
+data/Makefiles; uv fixes these for its installed distributions. Portability of
+the interpreter does not promise portability of packages installed beside it.
+[Project behavior quirks](https://github.com/astral-sh/python-build-standalone/blob/main/docs/quirks.rst).
+
+**Fact:** Python's ordinary virtual environments are not considered movable or
+copyable; script shebangs contain absolute interpreter paths.
+[Python venv documentation](https://docs.python.org/3/library/venv.html).
+
+**Inference:** First choice is a pinned standalone interpreter with packages
+installed at build time into a controlled private layout and launched by its
+explicit bundled executable. Build a wheel for the pinned parser rather than
+ship an editable install. Precompile its C/C++ utilities. Verify NumPy, SciPy,
+HDF5/h5py and every native extension against the supported macOS baseline and
+architecture. This remains a proof obligation, not a claim that copying today's
+`.venv` will work.
+
+**Alternative fact:** PyInstaller collects Python dependencies and supports
+macOS signing identity and entitlement configuration. Binary-path rewriting
+invalidates signatures, so binaries must be re-signed afterward. Its `onefile`
+mode extracts a payload to a temporary directory and uses launcher/child
+processes. [PyInstaller feature notes](https://pyinstaller.org/en/stable/feature-notes.html).
+
+**Inference:** PyInstaller `onedir` workers inside the native app are a viable
+fallback if the standalone layout proves costly. It still needs explicit
+collection of dynamic parser imports, data files and native utilities. Avoid
+`onefile` for long-lived detached project workers whose files must survive the
+bootstrap process. Neither approach automatically bundles MySQL or supplies
+app lifecycle/update coordination.
+
+### MySQL relocation before signing
+
+**Fact:** Oracle's macOS installer installs under `/usr/local`, offers launchd
+support and does not permit changing the installation location.
+[MySQL macOS package documentation](https://dev.mysql.com/doc/refman/8.4/en/macos-installation-pkg.html).
+
+**Fact:** MySQL supports explicit `--basedir`/`--datadir` for manual data-directory
+initialization. [MySQL initialization documentation](https://dev.mysql.com/doc/refman/8.4/en/data-directory-initialization.html).
+
+**Repository evidence:** `python/workspace_mysql_runtime.py` currently links
+conda packages at the destination prefix, changes Mach-O bytes and restores
+ad-hoc signatures. This is useful for source installs but cannot run inside a
+sealed desktop bundle.
+
+**Inference/proof obligation:** Prepare MySQL, `mysql`, `mysqldump`, plugins,
+OpenSSL and the complete dynamic-library closure in CI. Inspect dependencies
+and use supported relative Mach-O load paths. Verify strings/config files that
+encode the conda prefix and paths to plugin/share resources. Only then apply
+Developer ID signatures to final bytes. Run server/client/dump after moving the
+finished app to different users and paths; a successful `--version` probe alone
+does not prove TLS, authentication plugins, import or backup. Keep each project's
+datadir, sockets, error logs and credentials outside the app. Do not install
+Oracle's system daemon as the app-private runtime strategy.
+
+### Sparkle automatic update behavior
+
+**Fact:** Sparkle checks are opt-in by default and normally show a permission
+request on second launch. Set `SUEnableAutomaticChecks=YES` in `Info.plist` to
+enable checks without that request. Set `SUAutomaticallyUpdate=YES` to enable
+automatic download/install; its default is `NO`. Scheduled checks default to
+one day with a minimum one-hour interval. A downloaded update may prompt after
+the impatient interval (default one week), or need authorization.
+[Sparkle customization](https://sparkle-project.org/documentation/customization/).
+
+**Fact:** `willInstallUpdateOnQuit` is called when an automatic update is ready
+for silent installation on quit. Returning `YES` takes control and stalls future
+update cycles; its handler can install/relaunch without UI. In either case
+Sparkle still attempts installation when the app terminates.
+[Sparkle updater delegate](https://sparkle-project.org/documentation/api-reference/Protocols/SPUUpdaterDelegate.html).
+
+**Fact:** A custom `SPUUserDriver` must present UI; it cannot simply suppress all
+UI. Silent behavior comes from automatic-update configuration. Package updates
+always require authorization, and app updates may require it on managed Macs or
+for standard users in `/Applications`.
+[Sparkle custom UI contract](https://sparkle-project.org/documentation/custom-user-interfaces/).
+
+**Inference:** Prefer a user-owned installation such as `~/Applications` when
+unprompted subsequent updates are a requirement. A standard DMG drag/install or
+small installer still involves normal OS/download gestures; do not advertise a
+literally single click or bypass macOS first-open prompts. Bridge update state
+to the existing badge; retain a proper UI for manual actions and authorization.
+Test the impatient/critical-update paths before claiming no unsolicited update
+dialogs. Install on orderly app quit so the next ordinary launch uses the new
+version; do not update beneath active workers.
+
+**Fact:** Sparkle supports signed update archives and an appcast, uses an
+embedded EdDSA public key, and supports multiple archive formats including DMG
+and ZIP. [Sparkle setup and publication guide](https://sparkle-project.org/documentation/).
+
+**Inference:** Publish complete notarized app archives as GitHub Release assets
+and a stable appcast URL containing their exact URLs/signatures. Code signing,
+notarization and Sparkle's EdDSA signing are separate trust tasks. A Git push
+does not itself publish a valid update. Remove the desktop RSA-manager channel
+as an activation authority rather than maintain two independent trust/update
+pipelines for the same application.
+
+### Process and service ownership
+
+**Fact:** Python recommends fully qualified executable paths for subprocess
+reliability. `start_new_session`/`process_group` manage POSIX sessions/groups;
+they do not define application-level lifetime or request drainage.
+[Python subprocess documentation](https://docs.python.org/3/library/subprocess.html).
+
+**Fact:** Flask says not to use its development server in production.
+[Flask server guidance](https://flask.palletsprojects.com/en/stable/server/).
+
+**Inference:** Use a bundled production WSGI server on loopback, disable reload
+mode, establish readiness before opening the browser, handle duplicate launches
+and occupied ports, and let AppKit remain alive while any project service is
+active. Closing a browser tab must not be treated as shutdown acknowledgement.
+Stop accepting new work, preserve drafts, drain workers and shut down owned
+MySQL servers before the app terminates. If shutdown cannot complete safely,
+cancel termination and defer installation.
+
+## Bespoke manager fallback
+
+Retaining the existing manager is feasible only with additional work: give it
+its own bundled bootstrap interpreter that remains available independently of
+the active version; stage prebuilt artifacts without `setup` compilation;
+handle final signing/notarization, launcher replacement, manager/key upgrades,
+failure recovery and macOS authorization. The existing `sys.executable` and
+`#!/usr/bin/env python3` entry points do not establish that stable offline
+bootstrap contract. This route must also preserve bundle sealing by replacing
+complete signed payloads rather than modifying their code. It has more
+unimplemented desktop-specific responsibilities than the proposed Sparkle
+route. This comparison is engineering judgment based on the source contracts,
+not a completed packaging experiment.
+
+## Evidence required before release
+
+1. A moved, sealed app runs offline on a clean supported Apple Silicon Mac with
+ no Python, uv, Git, Node, Homebrew, compiler, Docker or MATLAB dependency.
+2. All compiled imports, parser utilities and real MySQL import/edit/export,
+ restart and dump/restore work from the packaged layout.
+3. Gatekeeper/notarization verification succeeds after a genuine browser
+ download; startup leaves bundle signatures valid.
+4. A real published old-to-new Sparkle update downloads quietly, waits for all
+ workers, installs on quit and preserves project data/preferences.
+5. Offline checks, corrupt signatures, interrupted download, disk-full, busy
+ workers, permission failure and incompatible database versions defer safely.
+6. Recovery and schema compatibility are tested explicitly; replacing an app
+ binary is not permission to migrate or downgrade project databases.
diff --git a/docs/dev/NATIVE_E2E_VALIDATION.md b/docs/dev/NATIVE_E2E_VALIDATION.md
index 9d9a8a6..2bafe9a 100644
--- a/docs/dev/NATIVE_E2E_VALIDATION.md
+++ b/docs/dev/NATIVE_E2E_VALIDATION.md
@@ -116,8 +116,36 @@ pauses expiry. Clicking View details opens the import workbench. Existing histor
is not replayed as new notifications after reload. Completion no longer opens a
modal automatically; the review dialog is available explicitly from Review import.
The browser verified a real duplicate import notification and automatic expiry,
-with no persistent header bar or automatic review dialog. App update notices also
-expire; their availability indicator remains in Release / Publish.
+with no persistent header bar or automatic review dialog. App updates now appear
+quietly as a persistent Update badge in Release / Publish, without a toast.
+
+## Quiet updates and publication audit — 2026-09-29
+
+- Checked both live GitHub repositories. The development checkout's updater
+ pointed at epicTreeGUI, whose release channel is empty. Rieke-OS is the actual
+ public distribution, with stable source releases through v0.1.2 and one source
+ installation verification workflow. Corrected discovery and release URLs to
+ Rieke-OS; a fresh backend check returns `update_available`, available `0.1.2`,
+ and `can_stage: false` for this development checkout.
+- Browser preview with simulated update responses verified the persistent
+ Update/Ready row, no update toast or automatic dialog, opening/closing the
+ panel without dismissing availability, retaining the known update after a
+ failed refresh, and clearing it after a successful up-to-date response.
+ [Simulated Ready preview](quiet-update-ready-preview.jpg).
+- 50 relevant updater, route, workspace installation and bootstrap tests pass;
+ frontend logic tests and production build pass. The manager subprocess test
+ uses minimal fixture applications to exercise real interprocess locks and
+ next-launch selection. It verifies deferral while another holder is open,
+ activation after closure, retention of the previous release, and preservation
+ of preferences. It is not a real old-to-new production application upgrade.
+- Prepared a tag-triggered signed build and protected publication job; its
+ publication condition allows only Rieke-OS. No workflow was dispatched and no
+ release was published. The updater and workflow still need to be carried into
+ the public distribution, with signing configured and real upgrade validation.
+- The one-click desktop installer remains unfinished. Existing source releases
+ bootstrap development tools and dependencies; they are not offline prebuilt
+ applications. MATLAB is required only for the optional EpicTreeGUI workflow,
+ not core app launch, import, or updating.
## Pinned query approval demonstration
@@ -158,3 +186,267 @@ Successful import completion now opens the global Review imported data dialog fr
Removed the duplicate Import progress toolbar button. Import H5s now changes to Importing H5s while work is active and opens the same import/history page. File selection and drop accept multiple H5s, queued at app scope, with only one submitted import at a time. Upload uncertainty or failed imports stop the queue; no automatic POST retries occur. Keep the tab open for browser-held pending files.
Browser file chooser confirmed multiple selection. Submitted a new synthetic H5 followed by an existing H5: first completed at 18:51:28 UTC, second started at 18:51:28.919 UTC and was skipped as duplicate. Review appeared after the queue drained, preserving the successful import review despite the last file being a duplicate. Frontend 211 tests and production build passed.
+
+## Project portability audit (2026-09-29)
+
+- Added explicit **Add new project** and **Start a brand new project** actions in
+ the left sidebar. The first recognizes an ordinary project or a received
+ transfer, and automatically shows the verified-copy restore form. The second
+ creates the selected exact project folder and its managed storage/database.
+- The browser created a disposable native project, imported a read-only real
+ Symphony recording (5 cells, 690 epochs), closed it, prepared a checked copy,
+ added that received copy through the ordinary project action, restored it into
+ a fresh folder and opened its overview and protocols. Both sidebar actions and
+ the verified restore confirmation were visually checked.
+ [Recipient restore](portable-project-restore-e2e.png) and
+ [recipient sidebar](portable-project-sidebar-e2e.png).
+- A production API integration test saved two named predicate versions, a query
+ run, an explorer baseline, authored tags, review/inclusion decisions, a tree
+ layout, recent-search shortcuts, protocol pins and a failed-job record. It
+ prepared a transfer, removed the entire disposable donor, restored to a fresh
+ runtime, then compared the actual recipient API responses and 16 waveform
+ samples. The recipient overview also opened successfully.
+- Packages now preserve durable import/app-job/error/storage history, portable
+ search/navigation preferences and every SQL table. Each new package records
+ table columns, row counts and content checksums; restore compares these before
+ updating current file locators. H5 files with external dependencies and damaged
+ preferences are rejected before a recipient database is started.
+- Native tests also exercise both never-opened and opened/closed empty projects,
+ independently restored copies, donor recording removal, safe rejection of
+ missing identity in populated projects and failed restore cleanup.
+- Browser pinning and running a History noise search wrote both choices into
+ `protocols/ui-state/project-preferences.json` (72 epochs, 2 cells). Failed old
+ browser-preference migration preserves its cache across refresh until a save
+ succeeds; stale incoming/browser values and concurrent changes have coverage.
+- Final backend workspace suite: **705 tests passed**, with native MySQL,
+ native transfer and production API portability gates enabled; no skips.
+ Frontend: **251 tests passed** and production build passed. Whitespace checks
+ passed. Original recordings and research projects were not modified.
+
+Reproduce the full backend checks with a suitable real Symphony fixture:
+
+```sh
+RIEKE_TEST_NATIVE_MYSQL=1 RIEKE_TEST_NATIVE_TRANSFER=1 \
+RIEKE_TEST_PORTABLE_APP=1 RIEKE_PORTABILITY_H5=/absolute/path/to/recording.h5 \
+PYTHONPATH=python .rieke-runtime/venv/bin/python -m unittest discover \
+ -s python/tests -p 'test_*workspace*.py'
+```
+
+These checks use fresh folders, credentials and native runtimes on one Apple
+Silicon Mac. A physical second-computer or other-platform transfer remains
+unverified. Restart existing app services to load the new backend before using
+the rebuilt interface.
+
+### Project UI design follow-up
+
+The chooser now uses two icon-led action cards, a six-icon project contents
+graphic, compact folder names and a collapsed preferred-location control.
+The closed-project notice is a compact status row. The project dialog uses
+**Open / Share / Receive** tabs and shows one task at a time, including concise
+file verification and restore results. Native sharing shows the close action
+before destination fields. Keyboard tab navigation and accessible control names
+are preserved.
+
+The real received-copy flow was exercised again through the redesigned dialog.
+Frontend: 251 tests passed and production build passed. Narrow layouts were
+visually checked; the closed-notice dismiss/copy controls stay on the same row.
+
+Final browser checks also covered invalid-folder rejection and refusal to
+restore into an existing destination. SHA-256 comparisons confirmed that the
+existing project's manifest, catalog, database credentials and guard file were
+unchanged. The new-project form created all managed folders, opened the empty
+production overview, and closed cleanly. Design screenshots are saved as
+`portable-project-chooser-design.png`, `portable-project-share-design.png` and
+`portable-project-restore-e2e.png` alongside this report.
+
+### Project roots anywhere and nearby-folder suggestions
+
+Project creation, open, verified restore and relocation now remember exact
+project roots in a local user-profile index. Inventories retain roots outside
+the preferred location across closing and launcher restart, preserve independent
+native copies by path, and show unavailable external drives. Exact creation and
+opening do not write app registry or lock files into the project's parent.
+Prepared transfer folders are excluded from live-project discovery.
+
+Folder inspection proposes the nearest fully validated containing root for
+a nested selection, or valid immediate children for a parent selection. Searches
+are bounded to eight ancestors and 128 immediate entries; no recursive drive
+scan, runtime start or automatic candidate selection occurs. The UI lists each
+candidate with **Use this folder**, followed by an explicit Open or Check action.
+
+The production service flow opened an external project after detecting its root
+from `imports/`, closed its database cleanly and retained that exact root in the
+launcher inventory. Receipt: `project-root-location-verification.json`.
+Final full native backend suite: **803 tests passed**; frontend: **266 tests
+passed** and production build passed. Live browser automation was unavailable
+for a visual recheck of the new suggestion cards; API and frontend logic checks
+passed. Refresh the preview after restarting its service to load the changes.
+
+### Final review before push
+
+The browser connection recovered for the final UI review. Naming guidance and
+the matching name/folder preview, Back and Escape, parent and nested folder
+suggestions, explicit candidate selection and prepared-copy detection all passed.
+Editing a received path clears its earlier verification. Current screenshots:
+`portable-project-naming-design.jpg`, `portable-project-root-suggestions.jpg`
+and `portable-project-receive-verified.jpg`.
+
+Review fixes reject missing package metadata even when a checksum is null,
+serialize managed and exact creation of the same folder, reapply sidebar move
+intent after a preference conflict, preserve completed operations when saving
+the local project index fails, and keep malformed old browser shortcuts from
+crashing the search screen. The original browser cache is retained until a
+successful save. Regression tests cover each failure.
+
+The push is prepared from a separate snapshot based on the current committed
+code plus only the project portability changes and required dependencies.
+Unrelated desktop, update, source-identity and performance edits are excluded.
+The initial snapshot passed 693 backend tests with all native portability gates
+enabled, 244 frontend tests and the production build. The final safeguards were
+then incorporated and checked again before commit.
+
+Final feature-only snapshot: **697 backend tests passed**, all native gates
+enabled with no skips; **245 frontend tests passed** and production build passed.
+Whitespace checks passed. The additional tests in the larger shared working
+tree belong to other ongoing changes and are not part of this push.
+
+### Project popup follow-up
+
+The project rail **+** now opens a native modal chooser above the existing app.
+Creation stays within that popup; the welcome screen also opens creation in a
+modal. A live disposable workspace verified that its overview URL and contents
+remain in place, **×** and **Escape** return focus to **+**, and forward/reverse
+Tab stay inside the popup. No project files were created by this UI check.
+Screenshots: `project-popup-over-workspace.jpg` and
+`project-create-popup-over-workspace.jpg`. The feature-only frontend suite and
+production build were checked again for this UI correction.
+
+## Project folder browser and creation actions — 2026-09-29
+
+Every editable project location now has Browse: creation, preferred location,
+opening, moving, sharing and receiving. The browser fallback navigates immediate
+folders with location shortcuts, parent navigation and explicit new-folder names.
+It reads folder metadata only and never creates files. The optional desktop
+chooser contract is covered by adapter tests; a native OS dialog was not exercised
+in this browser validation.
+
+A scoped checkout passed 253 frontend tests, the production build, 11 new folder
+API tests and 10 existing open-folder tests. Folder tests include pagination,
+permission errors, same-origin local access, missing destinations and occupied
+folders containing only hidden entries or files.
+
+Live browser validation confirmed the new-project popup has Cancel and Create &
+open above the existing workspace. Escape from the nested folder browser preserved
+the project name and folder value, kept setup open and returned focus to Browse.
+[Creation popup](project-create-browse-cancel.jpg) and
+[Folder navigator](project-browse-picker.jpg).
+
+Main Cancel closed setup and restored focus to its sidebar opener at the same
+overview URL. Tab/Shift+Tab remained in the nested picker. Choosing a proposed
+new folder filled the form without creating that folder on disk.
+
+Final audit regression: a missing proposed child inside an empty parent now stays
+a new-folder proposal rather than selecting its parent. The API reports whether
+the requested directory exists; the picker preserves the entered child name. A
+live check returned the exact proposed child path, while its parent stayed empty
+and the child was not created. Unfinished typed paths can still open Browse.
+10 focused folder-client tests, 12 folder API tests and the scoped production
+build pass. [Empty-parent check](project-browse-empty-parent-regression.jpg).
+
+## Whole UI file and folder audit — 2026-09-29
+
+Reviewed the entire component directory and App import/export pages, searching all
+inputs, file-picker triggers, editable paths and copy-path actions.
+
+| Flow | File or folder action |
+| --- | --- |
+| New project / preferred projects location | Browse folders |
+| Open / move / share / receive project | Browse sources and destination folders |
+| Recording import | Browse H5 files; multiple selection and drag/drop |
+| Tag and saved-query import | Native JSON file picker buttons |
+| Selection masks | Native UGM and JSON file picker buttons |
+| Exports | Download buttons; Open exports folder for local handoff |
+| Project files | In-app categories, folders and breadcrumbs |
+| Tag author profile | Author list and name; preferences saved automatically |
+
+The H5 chooser was a styled label around a hidden input, leaving it outside
+keyboard navigation. It now uses a real Browse button with the same file filters,
+multiple-file selection, queue behavior and disabled state. Cancel enqueues
+nothing; resetting the input allows choosing the same recording again.
+
+The local exports card previously offered only Copy folder path. Open exports
+folder now opens this project's existing exports folder in the system file
+manager. Its endpoint accepts an empty body only, verifies a local app request,
+and rejects missing folders and symlinks. It cannot choose an arbitrary location.
+Copies, exports and original recordings keep their current storage layout.
+
+A scoped checkout passed 255 frontend tests, 18 folder API tests, 10 existing
+open-folder tests and the production build. File-picker controls for tags, queries
+and masks already existed. Filename/path search filters and readonly diagnostic
+paths do not prompt for a filesystem selection.
+
+Live browser checks: Tab focused the H5 Browse button and Enter emitted a
+multiple-file chooser event. No files were selected and import history remained
+empty. Clicking Open exports folder completed without a visible error and
+re-enabled the button; success requires the OS opener to exit successfully for
+the fixed fixture exports path. The native computer-use service was unavailable
+for Finder, so the Finder window itself was not visually inspected.
+[H5 Browse](ui-browse-h5-audit.jpg) and
+[Open exports folder](ui-open-exports-audit.jpg).
+
+
+## Final unsigned Electron artifact validation — 2026-09-30 UTC
+
+The final local DMG and ZIP passed the complete packaged checks. See
+[the consolidated receipt](desktop-final-e2e-receipt.json) for exact artifact
+hashes, the runtime manifest and per-suite evidence. The app contains 40,172
+runtime resources and 798 native binaries.
+
+- 955 Python tests passed; 15 optional tests skipped.
+- 291 frontend and 27 desktop unit tests passed.
+- 21 real packaged UI/lifecycle checks and a separate broken-backend startup
+ case passed, including actual H5 import, bridge/native close deferral, draft
+ recovery, rapid project handoff, cold restart and renderer crash recovery.
+- 15 real scientific cases passed, including reference/SQLite/MAT numeric
+ fidelity, native portability, a separate legacy copy and monitor crash with
+ the importer retaining its project lease.
+- Extracted DMG/ZIP resource and native audits, 23 rejection faults, isolated
+ native restart/backup/restore and DMG-extracted recording workflow passed.
+- 10 pinned updater transport scenarios passed with zero unhandled rejections
+ and zero native installs, including a fresh-client retry after interruption.
+
+All scientific test services exited cleanly. Original H5 and packaged resource
+checksums stayed unchanged. Normal desktop operation requires no Docker. The
+legacy-copy fixture used a test Unix inspection transport with real native SQL,
+scientific data and history. No user source database or project was migrated.
+Frozen historical exports retain their original pointers; a new migrated export
+was separately verified against the copy’s H5 data.
+
+The user’s older installed app was left running and was not replaced. The
+reference bundle for the artifact audit was the built candidate; extracted DMG
+and ZIP copies were tested at isolated paths. This is an unsigned dirty-source
+local candidate. Signing/notarization, real signed old-to-new installation and
+recovery, a second clean machine/different user and macOS 14.0 remain release
+gates. No MATLAB engine was used; MAT structure and lazy numeric pointers were
+checked with bundled SciPy and H5. Native chooser results were stubbed at the OS
+dialog boundary. The receipts do not claim universal OS shutdown or power-loss
+qualification.
+
+The upper-right app icon update was rebuilt and rerun through the same packaged
+UI, startup-failure, scientific and DMG/ZIP audits. The actual bundled purple R
+loads at 48 px in the launcher and 32 px in the workspace. Native 1440 px and
+960 px window checks confirm that the icon remains visible without overlapping
+the toolbar. See the [workspace preview](desktop-ui-e2e/workspace-icon-1440.png)
+and [narrow preview](desktop-ui-e2e/workspace-icon-960.png). The final consolidated
+receipt records the refreshed artifact hashes; all five published files remained
+unchanged during verification.
+
+An additional [no-Docker recording workflow](desktop-no-docker-e2e.json) passed
+against the same final packaged runtime. Separate negative controls rejected
+three container command executions and two Docker socket connections before
+host access. With a fresh HOME and restricted PATH, project creation, real H5
+import, tags, query, reference export, reopen and orderly shutdown passed with
+zero container command or non-MySQL Unix socket attempts. Packaged resources
+remained unchanged; the user's Docker daemon and installed app were untouched.
+This uses Python process/socket instrumentation and the exact bundled native
+MySQL executable, rather than a kernel sandbox or clean-machine qualification.
diff --git a/docs/dev/TAG_SCOPE_AND_PROFILE_AUDIT.md b/docs/dev/TAG_SCOPE_AND_PROFILE_AUDIT.md
new file mode 100644
index 0000000..287a71c
--- /dev/null
+++ b/docs/dev/TAG_SCOPE_AND_PROFILE_AUDIT.md
@@ -0,0 +1,62 @@
+# Tag scope and user attribution
+
+Reviewed 2026-09-28. This records the integration contract, not an implemented migration.
+
+## Reference behavior
+
+Upstream: https://github.com/SamarjitK/datajoint
+Local fork: /Users/maxwellsdm/Documents/GitHub/datajoint (origin maxwellsdm1867, upstream SamarjitK).
+
+- next-app/api/schema.py:255: Tags attaches tag text and user to a table/object identity and its H5 UUID.
+- next-app/api/helpers/query.py:106–147: tag predicates restrict the selected hierarchy level; joins carry matching cells down to their epochs.
+- query.py:357–375: tagging selected cells writes cell records, not duplicated epoch records. Current deletion lacks a user restriction; do not copy that ownership behavior.
+- query.py:380–470: hierarchical JSON keyed by H5 UUID retains user/tag pairs. Push exports the current user's tags, pull imports other users, reset reloads all.
+- src/app/components/results/CustomTreeItem.js:65: compound chips show tag plus author.
+- src/app/components/setup/SetUser.js and api/app.py:189: selectable username; this is attribution, not authenticated identity.
+- Local fork query.py:473 and ResultsViewer.js:145: .ugm import translates deselection into the current user's excluded tag. This local extension is not asserted to exist upstream.
+
+## Current Rieke OS gaps
+
+workspace_curation.py stores tags at project + protocol + epoch scope. Bulk selection applies that scope to explicit epoch UUIDs. It is not a durable cell tag. Events record an actor, but current routes use the operating-system USER. The tag list does not retain per-tag authorship. Predicates expose explicitly named protocol tag fields in workspace_tag_predicates.py. No selectable profile or shared cell/epoch annotation layer exists yet.
+
+workspace_matlab_routes.py already validates .ugm against an exact completed export, source metadata, query and curation revisions. Inclusion updates are transactional, preserve tags and non-exported epochs, and log input hashes. Preserve these checks.
+
+## Target semantics
+
+| Entry | Stored target | Effective scope |
+|---|---|---|
+| Tag cell | project UUID + cell UUID | All epochs linked to that cell, across protocol datasets |
+| Tag epoch | project UUID + epoch UUID | That epoch only |
+| Tag selected epochs | explicit epoch UUID set | Only those selected epochs |
+| Protocol curation | existing protocol + epoch IDs | Existing dataset-specific decisions remain separate |
+
+Cell tags inherit through identity joins, not copied rows. Newly registered epochs linked to the same cell inherit automatically. A future epoch that belongs to a different cell named Cell1 must not inherit. Filtering a view must not silently narrow a cell-level tag to only its visible epochs.
+
+Deleting a cell tag removes its inherited appearance; an independently authored epoch tag of the same text remains. Cell and epoch tags authored by different users remain distinguishable. Tags are annotations, not implicit include/exclude commands.
+
+## Persistence and UI
+
+Add explicit Profile and Annotation bookkeeping tables. Profile: UUID, display name, created/updated timestamps. Annotation: project, target kind, target UUID, author UUID, exact tag text, revision, created/updated timestamps, retired state. Use a unique target/author/tag constraint and append-only change events. Resolve active profiles per client session/request, not a single server-global username. Historical event names must remain interpretable if a profile is renamed.
+
+Keep existing protocol tags in place with their original scope. Do not invent authors for historical tags or automatically promote them to cell annotations. Any explicit migration should preserve original event provenance and mark unknown authorship.
+
+Use the far-left profile control to select/create a local profile. In cell details, show Cell tags and their affected epoch count; in epoch details, show Direct epoch tags and Inherited from cell. Chips show tag, scope and author without repeating long UUIDs. Inherited chips link to the parent cell editor. Suggestions come from persisted project tags; show existing authorship rather than duplicate chips silently.
+
+Search predicate fields must distinguish Cell keywords, Epoch keywords, Effective tags, Author, and existing Protocol tags. Imported acquisition keywords remain identifiable as source metadata. Exact tag matching and text searching are different operators.
+
+Export the annotation snapshot with target IDs, authors, scope, and revisions. Wheeler SQLite and MATLAB handoffs must distinguish inherited cell tags from direct epoch tags and from selection masks. Frozen exports do not change when later annotations change.
+
+Mask imports should offer a preview of included/excluded counts, matched export and affected epochs. Apply only inclusion decisions to the intended protocol/export scope. Do not implement masks by deleting or overwriting user tags.
+
+## Required validation before shipping
+
+- Same Cell1 label on different dates never cross-tags.
+- Cell tags appear on all linked epochs across protocol and predicate views, including later-added epochs.
+- Direct epoch tags remain on only the intended epoch.
+- Removing inherited tags preserves independent tags with identical text.
+- Two users' same-text tags remain independently attributable.
+- Concurrent edits reject stale revisions and log committed changes once.
+- Cell/epoch/effective-tag and author predicates give exact UUID memberships.
+- Both exports retain attribution and inheritance while old export snapshots stay unchanged.
+- .ugm import preserves all tags, rejects unknown/mismatched/stale IDs and remains idempotent.
+- Paged epoch views retrieve inherited annotations in batches; no per-epoch SQL round trips or waveform loads.
diff --git a/docs/dev/portable-project-chooser-design.png b/docs/dev/portable-project-chooser-design.png
new file mode 100644
index 0000000..7d28d1f
Binary files /dev/null and b/docs/dev/portable-project-chooser-design.png differ
diff --git a/docs/dev/portable-project-naming-design.jpg b/docs/dev/portable-project-naming-design.jpg
new file mode 100644
index 0000000..2facd6e
Binary files /dev/null and b/docs/dev/portable-project-naming-design.jpg differ
diff --git a/docs/dev/portable-project-receive-verified.jpg b/docs/dev/portable-project-receive-verified.jpg
new file mode 100644
index 0000000..da073b5
Binary files /dev/null and b/docs/dev/portable-project-receive-verified.jpg differ
diff --git a/docs/dev/portable-project-restore-e2e.png b/docs/dev/portable-project-restore-e2e.png
new file mode 100644
index 0000000..c28c533
Binary files /dev/null and b/docs/dev/portable-project-restore-e2e.png differ
diff --git a/docs/dev/portable-project-root-suggestions.jpg b/docs/dev/portable-project-root-suggestions.jpg
new file mode 100644
index 0000000..f00abf3
Binary files /dev/null and b/docs/dev/portable-project-root-suggestions.jpg differ
diff --git a/docs/dev/portable-project-share-design.png b/docs/dev/portable-project-share-design.png
new file mode 100644
index 0000000..327ae18
Binary files /dev/null and b/docs/dev/portable-project-share-design.png differ
diff --git a/docs/dev/portable-project-sidebar-e2e.png b/docs/dev/portable-project-sidebar-e2e.png
new file mode 100644
index 0000000..45670a9
Binary files /dev/null and b/docs/dev/portable-project-sidebar-e2e.png differ
diff --git a/docs/dev/project-browse-empty-parent-regression.jpg b/docs/dev/project-browse-empty-parent-regression.jpg
new file mode 100644
index 0000000..c6ce33e
Binary files /dev/null and b/docs/dev/project-browse-empty-parent-regression.jpg differ
diff --git a/docs/dev/project-browse-picker.jpg b/docs/dev/project-browse-picker.jpg
new file mode 100644
index 0000000..bf6fa8a
Binary files /dev/null and b/docs/dev/project-browse-picker.jpg differ
diff --git a/docs/dev/project-create-browse-cancel.jpg b/docs/dev/project-create-browse-cancel.jpg
new file mode 100644
index 0000000..a5c7373
Binary files /dev/null and b/docs/dev/project-create-browse-cancel.jpg differ
diff --git a/docs/dev/project-create-popup-over-workspace.jpg b/docs/dev/project-create-popup-over-workspace.jpg
new file mode 100644
index 0000000..eaaa02d
Binary files /dev/null and b/docs/dev/project-create-popup-over-workspace.jpg differ
diff --git a/docs/dev/project-popup-over-workspace.jpg b/docs/dev/project-popup-over-workspace.jpg
new file mode 100644
index 0000000..d88c310
Binary files /dev/null and b/docs/dev/project-popup-over-workspace.jpg differ
diff --git a/docs/dev/project-root-location-verification.json b/docs/dev/project-root-location-verification.json
new file mode 100644
index 0000000..da5b02c
--- /dev/null
+++ b/docs/dev/project-root-location-verification.json
@@ -0,0 +1,9 @@
+{
+ "nested_folder_suggested_exact_root": true,
+ "external_root_opened": true,
+ "project_uuid": "96e2ddc1-f9ed-471b-86ce-6fa1f7751d51",
+ "database_closed_cleanly": true,
+ "external_root_remains_in_chooser": true,
+ "preferred_root": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-portability-ui-s1wdbe4f/projects",
+ "selected_root": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-portability-ui-s1wdbe4f/New design project"
+}
diff --git a/docs/dev/releases/0.1.3-source-validation.json b/docs/dev/releases/0.1.3-source-validation.json
new file mode 100644
index 0000000..df13b82
--- /dev/null
+++ b/docs/dev/releases/0.1.3-source-validation.json
@@ -0,0 +1,39 @@
+{
+ "format": "rieke-source-release-validation",
+ "version": 1,
+ "application_version": "0.1.3",
+ "channel": "unsigned-testing",
+ "checked_at": "2026-09-30T16:04:32.127523+00:00",
+ "source_files": 554,
+ "source_sha256": "80a5a14af9c4f0ce8205ef818cd37a33c46d9e2218d0460dcc180cfa8861bc8d",
+ "source_unchanged_during_validation": true,
+ "checks": {
+ "python_native_enabled": {
+ "passed": 1115,
+ "skipped": 1,
+ "subtests": 2792
+ },
+ "previously_skipped_real_recording_portability": {
+ "passed": 1,
+ "source_recording_read_only": true,
+ "disposable_projects": true
+ },
+ "frontend": {
+ "passed": 322
+ },
+ "frontend_production_build": {
+ "passed": true
+ },
+ "desktop": {
+ "passed": 61,
+ "source_unchanged_during_run": true
+ },
+ "coordinated_versions": {
+ "passed": true
+ },
+ "diff_whitespace": {
+ "passed": true
+ }
+ },
+ "artifact_qualification": "Performed separately against the rebuilt package from the canonical source commit. This source receipt does not certify installer bytes, Developer ID signing, or notarization."
+}
diff --git a/docs/dev/releasing-rieke-os.md b/docs/dev/releasing-rieke-os.md
index 9e5205d..96e11ad 100644
--- a/docs/dev/releasing-rieke-os.md
+++ b/docs/dev/releasing-rieke-os.md
@@ -4,6 +4,78 @@ The updater is a foundation for signed application releases, not `git pull`.
Development checkouts can check the official GitHub release channel but cannot
install into themselves. A missing GitHub release is reported as unavailable.
+## Publication to update discovery
+
+The application checks `https://api.github.com/repos/maxwellsdm1867/Rieke-OS/releases/latest`.
+PyPI and TestPyPI are not update sources. A Git push, version tag, or Actions
+candidate artifact alone does not make an update visible to users. The candidate
+must be promoted to a published stable GitHub Release with its signed manifest
+and matching platform bundle attached. Publish it as the latest release only
+after the validation gates below pass; drafts and prereleases are excluded.
+
+The app checks on startup, every 15 minutes while visible, and on return after
+that interval. A newer stable version highlights Release / Publish with an Update
+badge, without a toast or automatically opened dialog. Clicking it shows release
+notes and download progress. Trusted managed installations automatically download
+and verify the update. Transient check
+failures retain an already discovered update and explain the failure inside the
+panel. After the chooser and all project services have closed, the next launch
+through the stable manager activates the prepared release. A busy installation
+continues using its current version; updates never force-close another service.
+
+There are two distinct repositories, as checked on 2026-09-29:
+
+| Repository | Current publication state |
+| --- | --- |
+| `maxwellsdm1867/epicTreeGUI` (this development checkout) | Regression and signed-candidate workflows; no tags or releases, no configured environments or repository Actions secrets visible to this account. The previous updater incorrectly checked this empty release channel. |
+| `maxwellsdm1867/Rieke-OS` (public app distribution) | Stable versions through `v0.1.2`, with a source ZIP and SHA256SUMS. Its sole workflow verifies source installation. It has no updater module, signed update manifest, or prebuilt desktop installer. |
+
+This change points discovery, release links and future signed artifact URLs at
+Rieke-OS. The updater, metadata, UI and release workflow must be carried into that
+distribution before its users receive them. The publication job runs only in
+Rieke-OS, so a development tag cannot accidentally publish in epicTreeGUI. This
+checkout has no `release-signing-public.pem`; production signing and publication
+protection still need configuration.
+
+## One-click distribution target
+
+The current [Electron production specification](../design/electron-production-spec.md)
+selects a complete signed Electron app, bundled Python/MySQL and electron-updater
+as its sole desktop update authority. The [earlier gap map](../design/one-click-installer.md)
+preserves the audit and comparison of alternatives. The Python manager
+and source artifact described below remain the existing development mechanism.
+See the [primary-source review](INSTALLER_PRIMARY_SOURCE_REVIEW.md) for packaging
+and signing constraints. No desktop artifact was built during that planning pass.
+
+Users should download a self-contained desktop installer from GitHub Releases,
+open it once, and thereafter launch the installed app normally. The installer
+must contain Python and its locked packages, the pinned parser and compiled
+utilities, the built React assets, the private MySQL runtime, and the Electron
+lifetime owner/updater. The RSA manager/key below belong to source installations,
+not a second desktop updater. Setup must run offline without Git, uv,
+Node/npm, a compiler, Homebrew, Docker, or an existing Python installation.
+
+The current source artifact does not satisfy that contract: its Python runtime
+is created on the user's computer, and its editable parser/venv paths cannot
+simply be copied to another machine. The remaining packaging work is a relocatable
+Python runtime with resolved parser imports, offline native-runtime preparation,
+a native app entry point, and a signed/notarized macOS installer. Developer ID
+signing/notarization credentials are separate from the updater's RSA signing key.
+Validate the final installer on a clean machine without development tools and
+exercise a real old-to-new automatic update before advertising one-click install.
+Apple Silicon macOS is the only currently validated native artifact platform.
+
+### MATLAB is optional
+
+The web app does not launch MATLAB or require a MATLAB license/runtime. Its
+Python backend writes `.mat` exports with NumPy/SciPy and reads `.ugm` masks.
+MATLAB is needed only by users who choose to open the EpicTreeGUI export in
+MATLAB. Core install, launch, import, browsing, tags, query, and update validation
+must run without MATLAB. Licensed MATLAB validation belongs to the optional
+EpicTreeGUI interoperability claim, rather than a prerequisite for installing or
+shipping the core app. Continue running Python export/mask compatibility tests
+in the normal suite.
+
## Implemented contracts
- `rieke-release.json`, frontend package version and npm lockfile version agree.
@@ -24,6 +96,12 @@ install into themselves. A missing GitHub release is reported as unavailable.
requires exclusive lifetime locks; managed chooser and detached project
processes hold shared locks. Activation refuses incompatible database
generations. App updates never perform implicit project migrations.
+- Successful automatic checks start one background staging attempt per new
+ version per service session. Failed downloads can be retried in the panel.
+ Other services reuse only a completed receipt matching the signed manifest.
+ The stable manager applies prepared updates on the next launch when all
+ application locks are free. Workspace launchers created by a managed install
+ point to that manager instead of pinning a particular release directory.
- Managed launch uses built frontend assets. Source/development launch still
builds the frontend. Managed launch directly uses its Python service and needs
no Node/npm, Git or Docker command. The previous version remains available for recovery.
@@ -52,7 +130,9 @@ A maintainer must create an RSA release signing key in their chosen secret
management system and commit only its public PEM as `release-signing-public.pem`.
No private key or generated trust anchor is included by this change. Protect the
`release-signing` GitHub environment and set its `RELEASE_SIGNING_PRIVATE_KEY`
-secret. Review ownership and rotation procedures before distributing the key.
+secret. Configure required reviewers on `release-publishing` to verify the
+release-evidence gates below before public promotion. Review ownership and
+rotation procedures before distributing the key.
Publish the current reviewed application source first. The source and parser
commit must be fetchable by a clean machine. The current dirty development tree
@@ -63,17 +143,22 @@ is intentionally rejected by the release packager.
PR CI runs frontend tests/build, Python workspace tests and coordinated-version
validation. A separate Apple Silicon job prepares the private native runtime,
checks full project readiness and runs real database lifecycle and transfer tests
-without requiring a signing key or release tag. The manually dispatched
-`workspace-release-candidate.yml` checks out
-the requested reviewed version tag, exercises setup and tests on Apple Silicon,
+without requiring a signing key or release tag. Pushing a `v` tag
+triggers `workspace-release-candidate.yml`, which checks out
+the reviewed version tag, exercises setup and tests on Apple Silicon,
including real native MySQL lifecycle and native project transfer/copy tests,
-builds a signed artifact and uploads it as a GitHub Actions artifact. It does not
-publish a public GitHub Release.
+builds a signed artifact and uploads it as a GitHub Actions artifact. After the
+protected `release-publishing` environment approves promotion, a dependent job
+publishes those exact artifacts as the latest stable GitHub Release. Manual
+dispatch builds a candidate by default; its optional `publish` input requests
+the same protected promotion. Existing release assets are never overwritten.
Before promoting the candidate to a stable GitHub Release, require recorded
passing evidence for real MySQL/DataJoint import/edit/export/restart, complete
workspace transfer to a different path and machine, previous-to-current update
-and recovery, active-worker shutdown, and licensed MATLAB handoff regressions.
+and recovery, and active-worker shutdown. Licensed MATLAB handoff regressions are
+required when advertising the optional EpicTreeGUI interoperability feature;
+MATLAB is not a core app dependency.
Mocked/unit-only tests do not satisfy those gates. A missing runner is not a pass.
Only upload the exact signed artifact and `rieke-release-manifest.json` under its
matching `v` GitHub Release tag. Linux is not an advertised artifact yet.
@@ -90,8 +175,15 @@ python3 /path/to/RiekeOS/manager.py --installation /path/to/RiekeOS activate
python3 /path/to/RiekeOS/manager.py --installation /path/to/RiekeOS launch
```
-The managed app can stage a signed update while running. Its result includes the
-exact apply command. Close all chooser and project-service processes, then run:
+The managed app automatically stages a new signed update while running. Close all
+chooser and project-service processes, then launch normally through `manager.py`
+or a workspace launcher created by the managed app. It applies the prepared
+version before starting the chooser. No check/download/apply command is needed
+for subsequent updates. Existing manager copies and old workspace launchers must
+be upgraded once to gain this behavior; they are not rewritten in place by these
+source changes.
+
+For diagnostics, an explicit wait-and-apply command remains available:
```sh
python3 /path/to/RiekeOS/manager.py --installation /path/to/RiekeOS activate-and-launch
@@ -107,3 +199,55 @@ service. The UI must not present this as implemented. Incompatible database
updates likewise stay disabled until verified backup/migration/recovery exists.
Key rotation and automatic recovery from post-activation startup failure are
also not implemented; the previous release is retained, not silently restored.
+
+
+## Electron desktop qualification
+
+The Electron candidate uses bundled Python, parser and native MySQL. Normal
+launch, project creation, import and browsing require no Docker, external MySQL,
+Node or MATLAB. An older Docker-backed source project is inspected before any
+desktop service is started. Create a desktop copy in a new folder explicitly;
+the one-time source snapshot may read an already-running source Docker database.
+It never starts, stops or executes commands in Docker. Alternatively, prepare a
+portable copy with the source installation and receive it in the desktop app.
+The original source project is never silently converted in place.
+
+After packaging, run these suites against the packaged candidate:
+
+```sh
+npm test --prefix desktop
+npm test --prefix workspace-app
+npm run test:e2e:updater --prefix desktop
+npm run test:e2e --prefix desktop
+npm run test:e2e:startup-failure --prefix desktop
+python3 tools/desktop_artifact_e2e.py --installed 'desktop/dist/mac-arm64/Rieke OS.app'
+```
+
+The UI suite launches only scratch copies with isolated HOME and user data. It
+keeps Electron sandbox and CSP enabled and tests the real packaged ASAR. OS
+folder chooser return values are the explicit stubbed boundary. Set
+`RIEKE_E2E_H5` to a private local recording to additionally test real active
+import shutdown deferral. The scientific suite checks exact H5 sample values,
+annotations, masks, query membership, downloaded JSON/SQLite/MAT exports,
+restart and transfer through actual native services:
+
+```sh
+python3 tools/desktop_scientific_e2e.py --recording /absolute/path/to/fixture.h5
+python3 tools/desktop_artifact_e2e.py --installed 'desktop/dist/mac-arm64/Rieke OS.app' --recording /absolute/path/to/fixture.h5
+```
+
+Private recordings remain local and must never be attached to public evidence
+or build resources. Receipts bind checks to the packaged runtime manifest and
+artifact hashes. A failed receipt is retained as evidence, then the final suite
+is repeated after fixes. The candidate workflow runs updater transport faults,
+packaged UI and extracted-installer audits before inventorying a successful
+candidate, and uploads failure evidence separately.
+
+The updater suite exercises the pinned library and real Electron HTTP transport
+against a private fixture server. It covers malformed and unavailable metadata,
+version rejection, checksum mismatch, interrupted downloads, unwritable cache
+and unsigned candidates without authorizing a native installer. These checks
+do not qualify signed Squirrel installation, notarization, cross-machine
+Gatekeeper, or previous-to-current recovery. Those remain release gates when
+Developer ID credentials and a second clean machine are available. Unsigned
+local packages keep update installation disabled.
diff --git a/docs/dev/scale-audit-2026-09-29/backend_index_probe.py b/docs/dev/scale-audit-2026-09-29/backend_index_probe.py
new file mode 100644
index 0000000..89e6551
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/backend_index_probe.py
@@ -0,0 +1,79 @@
+"""Disposable current disk-index probe; run with PYTHONPATH=python.
+
+No scientific data, MySQL connection, acquisition H5, or production cache is read.
+Synthetic metadata comes from a lazy Mapping to avoid allocating all details.
+"""
+import argparse
+from collections.abc import Mapping
+import cProfile
+import gc
+import io
+import json
+from pathlib import Path
+import platform
+import pstats
+import resource
+import statistics
+import tempfile
+import time
+from workspace_disk_index import DiskMetadataIndex
+
+
+def run(count, parameter_fields, source_count, profile=False):
+ rows = [dict(epoch_uuid=f'e-{i}',cell_uuid=f'c-{i//100}',cell_label=f'Cell {i//100}',
+ date='2026-09-29',start_time=f'09/29/2026 12:00:{i%60:02d}:000000',cell_type='ON',
+ block_uuid=f'b-{i//20}',block_start_time=f'09/29/2026 12:00:{i%60:02d}:000000',
+ protocol_name='Synthetic',source_sha256=f's-{(i//100)%source_count}',group_uuid='g',
+ group_label='g',duration_seconds=1.,epoch_number=i%20+1) for i in range(count)]
+ sources = [{'source_sha256':f's-{i}'} for i in range(source_count)]
+ class Details(Mapping):
+ def __getitem__(self,key):
+ i=int(key[2:])
+ return {'parameters':{f'axis{k}':(i//(k+1))%7 for k in range(parameter_fields)},
+ 'metadata':{'cell':{'start_time':'09/29/2026 12:00:00:000000'}}}
+ def __len__(self): return count
+ def __iter__(self): return (r['epoch_uuid'] for r in rows)
+ result={'epochs':count,'parameter_fields':parameter_fields,'sources':source_count,
+ 'python':platform.python_version(),'fixture':'synthetic lazy details; no H5/SQL/browser IO',
+ 'measurements':[]}
+ def measure(name,fn,repeats=1):
+ timings=[];value=None
+ for _ in range(repeats):
+ started=time.perf_counter();value=fn();timings.append(time.perf_counter()-started)
+ item=dict(operation=name,seconds=timings,median_seconds=statistics.median(timings))
+ result['measurements'].append(item)
+ return value
+ with tempfile.TemporaryDirectory(prefix='rieke-audit-index-') as folder:
+ path=Path(folder)/'metadata.sqlite'
+ index=measure('build',lambda:DiskMetadataIndex.build(path,rows,Details(),sources,'audit','project'))
+ result['fields']=len(index.catalog()['fields']);result['db_bytes']=path.stat().st_size
+ index=measure('open_verified',lambda:DiskMetadataIndex.open(path,'audit','project'),3)
+ measure('cached_catalog',index.catalog,3)
+ ids=[row['epoch_uuid'] for row in rows if int(row['epoch_uuid'][2:])%7==3]
+ measure('scoped_catalog_one_seventh',lambda:index.catalog(ids),3)
+ measure('low_cardinality_predicate',lambda:index.match({'field':'parameters/axis0','operator':'eq','value':3}),3)
+ measure('epoch_uuid_equality',lambda:index.match({'field':'epoch','operator':'eq','value':f'e-{count//2}'}),3)
+ measure('all_source_projections_lazy',lambda:[index.source_projection(s['source_sha256'],lazy_details=True) for s in sources],3)
+ result['detail_cache_entries']=len(index.details.cache)
+ if profile:
+ profiler=cProfile.Profile();profiler.enable();index.catalog(ids);profiler.disable()
+ output=io.StringIO();pstats.Stats(profiler,stream=output).strip_dirs().sort_stats('cumtime').print_stats(30)
+ result['scoped_catalog_profile']=output.getvalue()
+ profiler=cProfile.Profile();profiler.enable();index.match({'field':'epoch','operator':'eq','value':f'e-{count//2}'});profiler.disable()
+ output=io.StringIO();pstats.Stats(profiler,stream=output).strip_dirs().sort_stats('cumtime').print_stats(25)
+ result['epoch_equality_profile']=output.getvalue()
+ # Do not retain the list of all-source projections across cleanup.
+ del index
+ gc.collect()
+ rss=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ result['peak_rss_mib']=rss/(1024**2 if platform.system()=='Darwin' else 1024)
+ return result
+
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser();parser.add_argument('--epochs',type=int,required=True)
+ parser.add_argument('--fields',type=int,required=True);parser.add_argument('--sources',type=int,required=True)
+ parser.add_argument('--profile',action='store_true');parser.add_argument('--output',type=Path,required=True)
+ args=parser.parse_args();result=run(args.epochs,args.fields,args.sources,args.profile)
+ args.output.write_text(json.dumps(result,indent=2)+'\n')
+ print(json.dumps({key:value for key,value in result.items() if not key.endswith('_profile')},indent=2))
diff --git a/docs/dev/scale-audit-2026-09-29/backend_source_dispatch_probe.py b/docs/dev/scale-audit-2026-09-29/backend_source_dispatch_probe.py
new file mode 100644
index 0000000..c2dd417
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/backend_source_dispatch_probe.py
@@ -0,0 +1,50 @@
+"""Isolate ChainMap source dispatch during metadata-index generation rebuild.
+
+All rows, SQLite, and OS-stat files are disposable synthetic fixtures.
+"""
+from collections import ChainMap
+from pathlib import Path
+import json
+import platform
+import random
+import statistics
+import tempfile
+import time
+from unittest.mock import patch
+import workspace_disk_index as disk
+from workspace_disk_index import DiskMetadataIndex
+
+
+def run():
+ output={'fixture':'synthetic, no H5 or MySQL','epochs':1000,'cells':10,
+ 'python':platform.python_version(),'measurements':[]}
+ rows=[dict(epoch_uuid=f'e-{i}',cell_uuid=f'c-{i//100}',cell_label='Cell',date='2026-09-29',
+ source_sha256='s',protocol_name='Synthetic',block_uuid='b',group_uuid='g') for i in range(1000)]
+ details={row['epoch_uuid']:{'parameters':{'axis':i%7}} for i,row in enumerate(rows)}
+ ids=[row['epoch_uuid'] for row in rows]
+ random.Random(1).shuffle(ids)
+ with tempfile.TemporaryDirectory(prefix='rieke-source-dispatch-') as folder:
+ index=DiskMetadataIndex.build(Path(folder)/'index.sqlite',rows,details,[{'source_sha256':'s'}],'audit','project')
+ for scopes in (1,10,100,500):
+ memberships=[dict.fromkeys(ids[i::scopes]) for i in range(scopes)]
+ chain=ChainMap(*(disk._ScopedDetails(index.details,bucket) for bucket in memberships))
+ start=time.perf_counter()
+ with patch.object(index,'_check',wraps=index._check) as checks:
+ for identity in ids: chain[identity]
+ calls=checks.call_count
+ elapsed=time.perf_counter()-start
+ output['measurements'].append(dict(source_scopes=scopes,lookup_seconds=elapsed,
+ index_stat_checks=calls,notes='Mock call recording adds timing overhead; structural call count is exact.'))
+ # Timings without instrumentation; same detail cache bounded to 64.
+ elapsed=[]
+ for repeat in range(3):
+ start=time.perf_counter()
+ for identity in ids: chain[identity]
+ elapsed.append(time.perf_counter()-start)
+ output['measurements'][-1].update(uninstrumented_seconds=elapsed,median_seconds=statistics.median(elapsed))
+ return output
+
+
+if __name__=='__main__':
+ result=run();path=Path('docs/dev/scale-audit-2026-09-29/backend-source-dispatch.json')
+ path.write_text(json.dumps(result,indent=2)+'\n');print(json.dumps(result,indent=2))
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_catalog_identity.py b/docs/dev/scale-audit-2026-09-29/benchmark_catalog_identity.py
new file mode 100644
index 0000000..6c69c2d
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_catalog_identity.py
@@ -0,0 +1,69 @@
+"""Measure the complete ownership verifier with synthetic indexed SQL doubles.
+
+No database, file parsing or waveform reads. The fake semi-join uses a hash
+membership lookup so it does not introduce a quadratic test-only nested loop.
+"""
+import argparse
+import hashlib
+import json
+from pathlib import Path
+import resource
+import time
+
+import workspace_catalog_identity as validator
+from test_workspace_catalog_identity import Rows, fixture
+
+
+class IndexedRows(Rows):
+ def __and__(self, restriction):
+ conditions = restriction.rows if isinstance(restriction, Rows) else restriction if isinstance(restriction, list) else [restriction]
+ groups = {}
+ for condition in conditions:
+ keys = tuple(sorted(condition))
+ groups.setdefault(keys, set()).add(tuple(condition[key] for key in keys))
+ selected = [row for row in self.rows if any(tuple(row.get(key) for key in keys) in values
+ for keys, values in groups.items())]
+ return IndexedRows(selected, self.fetches, self.name)
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs', type=int, default=100000)
+ parser.add_argument('--output', type=Path, required=True)
+ args = parser.parse_args()
+ if args.epochs < 2 or args.epochs % 2:
+ parser.error('epochs must be positive and even')
+ if args.output.exists():
+ parser.error('Choose a new output file; preserve earlier evidence')
+ path = Path(validator.__file__)
+ before = hashlib.sha256(path.read_bytes()).hexdigest()
+ started = time.perf_counter()
+ source, catalog, rows, fetches = fixture(args.epochs // 2)
+ for name, relation in vars(catalog).items():
+ setattr(catalog, name, IndexedRows(relation.rows, relation.fetches, relation.name))
+ construction = time.perf_counter() - started
+ started = time.perf_counter()
+ counts = validator.validate_catalog_identity(source, catalog, 7)
+ elapsed = time.perf_counter() - started
+ fetch_count = len(fetches)
+ rows['EpochGroup'][0]['parent_id'], rows['EpochGroup'][1]['parent_id'] = rows['EpochGroup'][1]['parent_id'], rows['EpochGroup'][0]['parent_id']
+ try:
+ validator.validate_catalog_identity(source, catalog, 7)
+ except validator.CatalogIdentityConflict as error:
+ rejection = error.conflict['kind']
+ else:
+ raise AssertionError('Swapped-parent corruption accepted')
+ after = hashlib.sha256(path.read_bytes()).hexdigest()
+ report = dict(epochs=args.epochs, counts=counts, fixture_seconds=construction,
+ validation_seconds=elapsed, table_fetches=fetch_count,
+ peak_process_rss_native_units=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss,
+ rss_units='bytes on macOS, KiB on Linux; entire process including fixture and deep-copy fetches',
+ swapped_parent_rejection=rejection, helper_sha256=before, source_unchanged=before == after,
+ environment='Synthetic indexed relational doubles with deep-copy fetch; no native SQL/H5 parsing; one timed sample')
+ assert before == after and counts['Epoch'] == args.epochs and rejection == 'catalog_parent'
+ args.output.write_text(json.dumps(report, indent=2) + '\n')
+ print(json.dumps(report, indent=2))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_curation_vocabulary.py b/docs/dev/scale-audit-2026-09-29/benchmark_curation_vocabulary.py
new file mode 100644
index 0000000..5a6e770
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_curation_vocabulary.py
@@ -0,0 +1,36 @@
+import hashlib,json,time,uuid
+from pathlib import Path
+from types import SimpleNamespace
+from test_workspace_state_generation import NativeGenerationTests as T
+from workspace_recovery_generation import RecoveryTracker
+from workspace_curation_vocabulary import CurationVocabulary
+paths=[Path('python')/name for name in ('workspace_curation_vocabulary.py','workspace_shared_tag_index.py','workspace_recovery_generation.py')]
+hashes=lambda:{str(p):hashlib.sha256(p.read_bytes()).hexdigest() for p in paths}
+report={'epochs':100000,'protocols':2,'tags_per_record':5,'scope':'Isolated native vocabulary: canonical JSON curation, generation triggers and derived SQLite; excludes metadata/shared/recovery mirror workloads','hashes_before':hashes()}
+T.setUpClass();index=None
+try:
+ c=T.connection;p=str(uuid.uuid4());protocols=[str(uuid.uuid4()) for _ in range(2)];epochs=[str(uuid.UUID(int=i+1)) for i in range(100000)]
+ started=time.perf_counter()
+ with c._conn.cursor() as cur:
+ for position,protocol in enumerate(protocols):
+ tags=json.dumps([f'dataset:{position}']+[f'dataset-tag:{i}' for i in range(1,5)])
+ for offset in range(0,len(epochs),1000):
+ cur.executemany('INSERT INTO recording_workspace.curation VALUES (%s,%s,%s,%s,1)',[(p,protocol,e,tags) for e in epochs[offset:offset+1000]])
+ report['seed_seconds']=time.perf_counter()-started
+ tracker=RecoveryTracker(c,p).bootstrap();assert tracker.ready,tracker.reason
+ index=CurationVocabulary(SimpleNamespace(project_uuid=p,dj=SimpleNamespace(conn=lambda:c)),tracker)
+ def status():return dict(c.query("SHOW SESSION STATUS WHERE Variable_name IN ('Handler_read_next','Handler_read_key','Rows_sent','Bytes_sent')").fetchall())
+ def measure(label):
+ before=status();t=time.perf_counter();result=index.suggestions('dataset',30);elapsed=time.perf_counter()-t;after=status()
+ report[label]={'seconds':elapsed,'sql':{k:int(after[k])-int(before[k]) for k in before},'stats':dict(index.stats)}
+ assert len(result['tags'])==6 and all(x['count']==100000 for x in result['tags']),result
+ measure('cold');measure('warm')
+ c.query('UPDATE recording_workspace.curation SET tags=JSON_ARRAY_APPEND(tags,%s,%s) WHERE project_uuid=%s AND protocol_uuid=%s AND epoch_uuid=%s',('$','edited',p,protocols[0],epochs[0]))
+ measure('after_edit')
+ assert index.suggestions('edited',30)['tags']==[{'tag':'edited','count':1}]
+ report['storage']=index.storage_stats();report['hashes_after']=hashes();report['passed']=report['hashes_before']==report['hashes_after']
+ print(json.dumps(report,indent=2),flush=True)
+ Path('docs/dev/scale-audit-2026-09-29/curation-vocabulary-200000-scalar-keyset.json').write_text(json.dumps(report,indent=2)+'\n')
+finally:
+ if index:index.close()
+ T.tearDownClass()
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_dense_protocol_state.py b/docs/dev/scale-audit-2026-09-29/benchmark_dense_protocol_state.py
new file mode 100644
index 0000000..d6b55ad
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_dense_protocol_state.py
@@ -0,0 +1,73 @@
+"""Dense saved-curation revision fallback, disposable100k SQL-double project."""
+import argparse
+import json
+from pathlib import Path
+import tempfile
+from unittest.mock import patch
+
+from benchmark_service_scale import fixture,Details
+from benchmark_epoch_pages import measure
+from workspace_disk_index import DiskMetadataIndex
+from workspace_service import WorkspaceService
+from test_workspace_api import WorkspaceAPITests
+
+
+def run(count):
+ api=WorkspaceAPITests();api.setUp()
+ try:
+ service,protocol=fixture(count)
+ service.project['project_uuid']=api.service.project['project_uuid']
+ with tempfile.TemporaryDirectory(prefix='rieke-dense-state-') as folder:
+ index=DiskMetadataIndex.build(Path(folder)/'index.sqlite',service.rows,Details(service.rows),
+ service.sources,'dense-state-benchmark',service.project['project_uuid'])
+ target=api.service
+ target.__class__=WorkspaceService
+ for name in ('rows','cells','sources','_fingerprints'):
+ setattr(target,name,getattr(service,name))
+ target.disk_index,target.details=index,index.details
+ target.protocols={target.protocol_id:service.protocols[protocol]}
+ target.protocols[target.protocol_id]['result']['protocol_uuid']=target.protocol_id
+ api.curation.rows.extend({'project_uuid':target.project['project_uuid'],
+ 'protocol_uuid':target.protocol_id,'epoch_uuid':key,'included':True,
+ 'tags':['checked'],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':target._fingerprints[key]} for key in target.rows)
+ reader=api.app.extensions['protocol_state_reader']
+ target.epoch_page(target.protocol_id,limit=60)
+ path=api.base+'/epochs?limit=60'
+ read_counts=[]
+ def request():
+ before=len(api.curation.read_log)
+ response=api.client.get(path)
+ assert response.status_code==200,response.get_json()
+ read_counts.append(len(api.curation.read_log)-before)
+ return response.get_json()
+ def uncached(protocol,ids):
+ result,states,revision=reader.full_state(protocol)
+ return {key:states[key] for key in ids},revision,result.get('dataset_binding',{}).get('version',0)
+ with patch.object(reader,'read_selected',side_effect=uncached):
+ legacy,old=measure(request)
+ legacy_reads=read_counts[:];read_counts.clear()
+ cold,new=measure(request,1)
+ cold_reads=read_counts[:];read_counts.clear()
+ repeated,again=measure(request)
+ assert old==new==again
+ return {'epochs':count,'saved_curation_rows':count,'tags_per_row':1,
+ 'scope':'Actual Flask GET plus JSON, real sealed SQLite metadata index, transactional SQL-double curation table with one saved tag on every epoch. Excludes real MySQL/network, browser, shared annotations and waveform I/O.',
+ 'legacy_full_revision_http':legacy,'cache_attempt_cold_http':cold,
+ 'cache_attempt_repeated_http':repeated,'full_curation_reads':{
+ 'legacy_samples':legacy_reads,'cold_sample':cold_reads,'repeated_samples':read_counts},
+ 'retained_revision_scopes':len(reader.cache),
+ 'estimated_retained_revision_bytes':sum(item['size'] for item in reader.cache.values()),
+ 'exact_legacy_response_parity':True}
+ finally:
+ api.doCleanups()
+
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs',type=int,default=100000)
+ parser.add_argument('--output',type=Path,required=True)
+ args=parser.parse_args()
+ result=run(args.epochs)
+ args.output.write_text(json.dumps(result,indent=2)+'\n')
+ print(json.dumps(result,indent=2),flush=True)
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_epoch_pages.py b/docs/dev/scale-audit-2026-09-29/benchmark_epoch_pages.py
new file mode 100644
index 0000000..e54d67e
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_epoch_pages.py
@@ -0,0 +1,84 @@
+"""Disposable exact before/after protocol-page benchmark, including Flask overhead.
+
+Uses the existing scale fixture and real sealed SQLite index. HTTP routes use
+transactional SQL doubles with no saved tags. No real catalog or H5 data is used.
+"""
+import argparse
+import gc
+import json
+from pathlib import Path
+import statistics
+import tempfile
+import time
+from unittest.mock import patch
+
+from benchmark_service_scale import fixture, Details
+from workspace_disk_index import DiskMetadataIndex
+from test_workspace_api import WorkspaceAPITests
+
+
+def legacy_page(service, protocol, filters=None, offset=0, limit=100, anchor_uuid=None):
+ rows=service.filtered_rows(protocol,filters)
+ if anchor_uuid is not None:
+ position=next(index for index,row in enumerate(rows) if row['epoch_uuid']==anchor_uuid)
+ offset=position//limit*limit
+ return {'total':len(rows),'offset':offset,'limit':limit,'epochs':rows[offset:offset+limit]}
+
+
+def measure(operation,repeats=3):
+ samples=[]
+ for _ in range(repeats):
+ gc.collect()
+ started=time.perf_counter();result=operation();samples.append(time.perf_counter()-started)
+ return {'median_seconds':statistics.median(samples),'samples_seconds':samples},result
+
+
+def run(count):
+ service,protocol=fixture(count)
+ with tempfile.TemporaryDirectory(prefix='rieke-epoch-page-benchmark-') as folder:
+ started=time.perf_counter()
+ index=DiskMetadataIndex.build(Path(folder)/'index.sqlite',service.rows,Details(service.rows),
+ service.sources,'epoch-page-benchmark',service.project['project_uuid'])
+ service.disk_index,service.details=index,index.details
+ report={'epochs':count,'index_build_seconds':time.perf_counter()-started,
+ 'scope':'Synthetic one-protocol project. Real sealed SQLite index; no waveform or lab catalog I/O. Flask measurements include JSON and current global query-revision calculation but use SQL doubles with empty saved-curation tables, so exclude database/network latency and dense annotation cost.'}
+ for label,filters in [('all',None),('one_cell',{'cell_uuid':next(iter(service.cells))})]:
+ before,old=measure(lambda:legacy_page(service,protocol,filters,limit=60))
+ cold,new=measure(lambda:service.epoch_page(protocol,filters,limit=60),1)
+ after,new=measure(lambda:service.epoch_page(protocol,filters,limit=60))
+ assert old==new
+ report[label]={'legacy':before,'new_cold':cold,'new_warm':after,'exact_page_parity':True}
+ # Use the actual production Flask route to expose its remaining full
+ # state/revision computation, separately from service-only timings.
+ api=WorkspaceAPITests();api.setUp()
+ try:
+ target=api.service
+ for name in ('rows','details','cells','sources','_fingerprints','disk_index'):
+ setattr(target,name,getattr(service,name))
+ target.protocols={target.protocol_id:service.protocols[protocol]}
+ target.protocols[target.protocol_id]['result']['protocol_uuid']=target.protocol_id
+ path=api.base+'/epochs?limit=60'
+ def get_page():
+ response=api.client.get(path)
+ assert response.status_code==200,response.get_json()
+ return response.get_json()
+ with patch.object(target,'epoch_page',side_effect=lambda *args,**kwargs:legacy_page(target,*args,**kwargs)):
+ before,old=measure(get_page)
+ after,new=measure(get_page)
+ assert old==new
+ report['flask_page_empty_curation_sql_double']={'legacy':before,'new':after,'exact_response_parity':True}
+ finally:
+ api.doCleanups()
+ report['retained_epoch_scopes']={'count':len(service._epoch_page_cache[1]),
+ 'estimated_entry_bytes':sum(item[2] for item in service._epoch_page_cache[1].values())}
+ return report
+
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs',type=int,default=100000)
+ parser.add_argument('--output',type=Path,required=True)
+ args=parser.parse_args()
+ report=run(args.epochs)
+ args.output.write_text(json.dumps(report,indent=2)+'\n')
+ print(json.dumps(report,indent=2),flush=True)
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_native_dense_tags.py b/docs/dev/scale-audit-2026-09-29/benchmark_native_dense_tags.py
new file mode 100644
index 0000000..c319c49
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_native_dense_tags.py
@@ -0,0 +1,288 @@
+"""Native dense-tag API workload in a fresh disposable project.
+
+Actual MySQL, triggers, SQLite indexes, Flask routes and save/checkpoint hooks.
+Acquisition metadata is synthetic; no H5 parsing/waveform/NAS/browser claim.
+"""
+import argparse
+import copy
+import cProfile
+import datetime as dt
+import hashlib
+import json
+import math
+import pstats
+from pathlib import Path
+import resource
+import statistics
+import tempfile
+import time
+import traceback
+import uuid
+
+from benchmark_service_scale import fixture, Details, uid
+from recording_workspace import connect, workspace_tables
+from workspace_projects import create_project
+from workspace_native_mysql import ensure_native_database, stop_native_database
+from workspace_disk_index import DiskMetadataIndex
+from workspace_api import create_app
+
+HERE = Path(__file__).resolve().parent
+ROOT = HERE.parents[2]
+FILES = ['workspace_api.py', 'workspace_service.py', 'workspace_curation.py',
+ 'workspace_protocol_state.py', 'workspace_state_generation.py',
+ 'workspace_annotations.py', 'workspace_shared_tag_index.py', 'workspace_state_snapshot.py',
+ 'workspace_recovery_store.py', 'workspace_recovery_generation.py', 'workspace_curation_vocabulary.py']
+
+
+def hashes():
+ return {name: hashlib.sha256((ROOT / 'python' / name).read_bytes()).hexdigest() for name in FILES}
+
+
+def tags_for(ordinal, profile, size, *, cell=False):
+ initial = (['inherited', f'cell-bucket:{ordinal % 13}', f'profile:{profile}', 'same', 'cell-tag'] if cell else
+ ['all', f'bucket:{ordinal % 97}', 'ON' if ordinal % 2 == 0 else 'on',
+ 'é' if ordinal % 3 == 0 else 'e\u0301', f'profile:{profile}'])
+ return initial[:size] + [f'extra:{profile}:{index}' for index in range(len(initial), size)]
+
+
+def percentiles(values):
+ ordered = sorted(values)
+ return {'samples_seconds': values, 'p50_seconds': statistics.median(values),
+ 'p95_seconds': ordered[max(0, math.ceil(.95 * len(ordered)) - 1)],
+ 'max_seconds': max(values)}
+
+
+def disk_bytes(path):
+ files = [path] if path.is_file() else [p for p in path.rglob('*') if p.is_file() and not p.is_symlink()]
+ return {'apparent_bytes': sum(p.stat().st_size for p in files),
+ 'allocated_bytes': sum(p.stat().st_blocks * 512 for p in files), 'files': len(files)}
+
+
+def run(args, report):
+ app = None; dj = None
+ with tempfile.TemporaryDirectory(prefix='rieke-native-dense-tags-') as directory:
+ root = Path(create_project(Path(directory) / 'projects', 'Disposable dense tag benchmark')['path'])
+ try:
+ ensure_native_database(root)
+ dj = connect({'kind': 'native-project'}, project_dir=root)
+ service, protocol = fixture(args.epochs)
+ service.project_dir = root
+ service.project = json.loads((root / 'project.json').read_text())
+ service.config = json.loads((root / 'catalog.json').read_text())
+ service.dj = dj
+ other = uid(-3)
+ service.protocols[other] = copy.deepcopy(service.protocols[protocol])
+ service.protocols[other]['definition']['protocol_uuid'] = other
+ service.protocols[other]['result']['protocol_uuid'] = other
+ for value in service.protocols.values():
+ value['definition']['project_uuid'] = service.project['project_uuid']
+ value['definition']['query'] = {'version': 1, 'all': [
+ {'field': 'EpochBlock.protocol_name', 'operator': 'eq', 'value': 'Synthetic'}]}
+ index = DiskMetadataIndex.build(root / 'cache' / 'dense.sqlite', service.rows, Details(service.rows),
+ service.sources, 'native-dense-synthetic-acquisition', service.project['project_uuid'])
+ service.disk_index = index; service.details = index.details
+ Project, _, _, _ = workspace_tables(dj)
+ Project.insert1({'project_uuid': service.project['project_uuid'], 'name': service.project['name'], 'directory': str(root)})
+ app = create_app(root, ROOT / '.rieke-runtime/retinanalysis', service=service)
+ assert service._recovery_tracker.ready, service._recovery_tracker.reason
+ tracker = app.extensions['state_generation']
+ assert tracker.ready, tracker.reason
+ assert tracker.token(protocol) is not None, tracker.reason
+ report['mysql_version'] = dj.conn().query('SELECT VERSION()').fetchone()[0]
+ store = app.extensions['curation_store']; shared = app.extensions['shared_annotations']
+ authors = [uid(-100 - i) for i in range(args.profiles)]
+ now = dt.datetime(2026, 6, 11, 12)
+ project_id = service.project['project_uuid']
+ started = time.perf_counter()
+ with dj.conn().transaction:
+ shared.Profile.insert([{'project_uuid': project_id, 'profile_uuid': author,
+ 'display_name': f'Scientist {p}', 'created_at': now, 'created_by': 'native benchmark'}
+ for p, author in enumerate(authors)])
+ batch = []
+ for kind, identities in (('epoch', service.rows), ('cell', service.cells)):
+ for ordinal, identity in enumerate(identities):
+ for profile, author in enumerate(authors):
+ batch.append({'project_uuid': project_id, 'target_kind': kind, 'target_uuid': identity,
+ 'profile_uuid': author, 'tags': tags_for(ordinal, profile, args.tags, cell=kind == 'cell'),
+ 'author_name': f'Scientist {profile}', 'revision': 1, 'updated_at': now})
+ if len(batch) == 500:
+ shared.Annotation.insert(batch); batch.clear()
+ if batch: shared.Annotation.insert(batch); batch.clear()
+ for position, current_protocol in enumerate((protocol, other)):
+ dataset_tags = [f'dataset:{position}'] + [f'dataset-tag:{i}' for i in range(1, args.tags)]
+ for identity in service.rows:
+ batch.append({'project_uuid': project_id, 'protocol_uuid': current_protocol,
+ 'epoch_uuid': identity, 'included': True, 'tags': dataset_tags,
+ 'review_state': 'unreviewed', 'revision': 1,
+ 'metadata_fingerprint': service._fingerprints[identity]})
+ if len(batch) == 500: store.Curation.insert(batch); batch.clear()
+ if batch: store.Curation.insert(batch); batch.clear()
+ report['seed_seconds'] = time.perf_counter() - started
+ report['shared_rows'] = (len(service.rows) + len(service.cells)) * args.profiles
+ report['dataset_curation_rows'] = 2 * args.epochs
+ print('Native tags seeded', flush=True)
+ from workspace_state_snapshot import save as save_recovery
+ started = time.perf_counter()
+ report['seed_checkpoint'] = save_recovery(root, dj.conn(), service=service)
+ report['seed_checkpoint']['seconds'] = time.perf_counter() - started
+ print('Seed recovery checkpoint durable', flush=True)
+ client = app.test_client(); base = f'/api/protocols/{protocol}'
+ headers = {'X-Workspace-Request': '1', 'Origin': 'http://localhost:8766'}
+ report['operations'] = []
+
+ def sql_status():
+ return {key: int(value) for key, value in dj.conn().query(
+ "SHOW SESSION STATUS WHERE Variable_name IN ('Rows_sent','Bytes_sent','Questions')").fetchall()}
+
+ def get(path, query=None):
+ response = client.get(path, query_string=query)
+ value = response.get_json()
+ assert response.status_code == 200, (response.status_code, value)
+ return value
+
+ def measure(name, callback, repeat=None):
+ durations = []; sql = []; result = None
+ profile = cProfile.Profile() if args.profile and any(value in name for value in ('summary', 'overview', 'suggestions')) else None
+ for _ in range(args.samples if repeat is None else repeat):
+ before = sql_status(); start = time.perf_counter()
+ if profile is not None: profile.enable()
+ result = callback()
+ if profile is not None: profile.disable()
+ durations.append(time.perf_counter() - start)
+ after = sql_status(); sql.append({key: after[key] - before[key] for key in before})
+ record = {'name': name, **percentiles(durations), 'sql_session_deltas': sql}
+ if profile is not None:
+ profile_path = args.output.with_name(args.output.stem + '.' + name + '.prof')
+ profile.dump_stats(str(profile_path))
+ stats = pstats.Stats(profile)
+ record['profile'] = [{'file': key[0], 'line': key[1], 'function': key[2],
+ 'primitive_calls': value[0], 'calls': value[1], 'own_seconds': value[2],
+ 'cumulative_seconds': value[3]} for key, value in
+ sorted(stats.stats.items(), key=lambda item: item[1][3], reverse=True)[:60]]
+ report['operations'].append(record)
+ print(json.dumps({'operation': name, 'p50': record['p50_seconds'], 'p95': record['p95_seconds']}), flush=True)
+ return result
+
+ page = measure('page_cold', lambda: get(base + '/epochs', {'limit': 60}), 1)
+ assert len(page['epochs']) == min(60, args.epochs) and page['total'] == args.epochs
+ for row in page['epochs']:
+ ordinal = service.rows[row['epoch_uuid']]['synthetic_index']
+ expected = sorted((tag, authors[p]) for p in range(args.profiles) for tag in tags_for(ordinal, p, args.tags))
+ actual = sorted((item['tag'], item['profile_uuid']) for item in row['annotations']['epoch_tags'])
+ assert actual == expected
+ assert len(row['curation']['tags']) == args.tags
+ measure('page_warm', lambda: get(base + '/epochs', {'limit': 60}))
+ for tag, check in [('ON', lambda i: i % 2 == 0), ('on', lambda i: i % 2 != 0),
+ ('bucket:7', lambda i: i % 97 == 7), ('inherited', lambda i: True)]:
+ expected = [key for key, row in service.rows.items() if check(row['synthetic_index'])]
+ result = measure('filter_cold_' + tag, lambda tag=tag: get(base + '/epochs', {'limit': 60, 'tag': tag}), 1)
+ assert result['total'] == len(expected), (tag, result['total'], len(expected))
+ assert [row['epoch_uuid'] for row in result['epochs']] == expected[:60]
+ measure('filter_warm_' + tag, lambda tag=tag: get(base + '/epochs', {'limit': 60, 'tag': tag}))
+ measure('protocol_summary_after_tagged_load', lambda: get(base), 1)
+ measure('overview_after_tagged_load', lambda: get('/api/overview'), 1)
+ measure('dataset_tag_suggestions_cold', lambda: get('/api/tags', {'q': 'dataset', 'limit': 30}), 1)
+ measure('dataset_tag_suggestions_warm', lambda: get('/api/tags', {'q': 'dataset', 'limit': 30}), 3)
+ identity = next(iter(service.rows)); revision = 1
+ for iteration in range(args.edits):
+ current = get(base + '/epochs', {'limit': 1})
+ body = {'epoch_uuids': [identity], 'query_revision': current['query_revision'],
+ 'expected_revisions': {identity: revision}, 'expected_binding_version': 0,
+ 'changes': {'tags_add': [f'edited:{iteration}']}}
+ def edit():
+ response = client.post(base + '/curation', json=body, headers=headers)
+ assert response.status_code == 200, (response.status_code, response.get_json())
+ return response.get_json()
+ measure('edit_with_durable_backup_' + str(iteration), edit, 1)
+ revision += 1
+ result = measure('first_page_after_edit_' + str(iteration), lambda: get(base + '/epochs', {'limit': 60}), 1)
+ assert result['epochs'][0]['curation']['revision'] == revision
+ assert f'edited:{iteration}' in result['epochs'][0]['curation']['tags']
+ measure('protocol_summary_after_edit_' + str(iteration), lambda: get(base), 1)
+ measure('overview_after_edit_' + str(iteration), lambda: get('/api/overview'), 1)
+ measure('dataset_tag_suggestions_after_edit_' + str(iteration),
+ lambda: get('/api/tags', {'q': 'dataset', 'limit': 30}), 1)
+ for iteration in range(args.edits):
+ body = {'target_kind': 'epoch', 'target_uuids': [identity], 'profile_uuid': authors[0],
+ 'tags_add': [f'shared-edited:{iteration}'], 'expected_revisions': {identity: iteration + 1}}
+ def shared_edit():
+ response = client.post('/api/annotations', json=body, headers=headers)
+ assert response.status_code == 200, (response.status_code, response.get_json())
+ return response.get_json()
+ measure('shared_edit_with_durable_backup_' + str(iteration), shared_edit, 1)
+ result = measure('shared_filter_after_edit_' + str(iteration), lambda: get(base + '/epochs',
+ {'limit': 60, 'tag': f'shared-edited:{iteration}'}), 1)
+ assert result['total'] == 1 and result['epochs'][0]['epoch_uuid'] == identity
+ measure('shared_summary_after_edit_' + str(iteration), lambda: get(base), 1)
+ report['shared_index_stats'] = getattr(getattr(shared, '_shared_tag_index', None), 'stats', None)
+ vocabulary = getattr(store, '_curation_vocabulary_index', None)
+ report['curation_vocabulary_stats'] = getattr(vocabulary, 'stats', None)
+ if vocabulary is not None:
+ report['curation_vocabulary_storage'] = vocabulary.storage_stats()
+ index = getattr(shared, '_shared_tag_index', None)
+ index_path = getattr(index, 'path', None)
+ report['storage'] = {'project': disk_bytes(root), 'native_database': disk_bytes(root / 'database'),
+ 'source_metadata_index': disk_bytes(root / 'cache'), 'recovery': disk_bytes(root / 'backups')}
+ if index_path is not None:
+ report['storage']['shared_tag_index'] = disk_bytes(Path(index_path))
+ if index is not None:
+ report['storage']['shared_tag_index_sqlite'] = index.storage_stats()
+ report['storage']['sql_tables'] = dj.conn().query(
+ "SELECT TABLE_NAME,TABLE_ROWS,DATA_LENGTH,INDEX_LENGTH,DATA_FREE FROM information_schema.tables "
+ "WHERE TABLE_SCHEMA='recording_workspace' ORDER BY TABLE_NAME", as_dict=True).fetchall()
+ report['storage']['note'] = 'SQL row/byte estimates may lag; filesystem apparent and allocated totals include native redo/preallocation. Synthetic acquisition has no real waveform-size denominator.'
+ report['oracle_passed'] = True
+ finally:
+ if app is not None:
+ shared = app.extensions.get('shared_annotations')
+ index = getattr(shared, '_shared_tag_index', None)
+ if index is not None: index.close()
+ lock = app.extensions.get('app_state_session_lock')
+ if lock is not None: lock.close()
+ if dj is not None: dj.conn().close()
+ stop_native_database(root)
+ report['owned_runtime_stopped'] = True
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs', type=int, default=1000)
+ parser.add_argument('--tags', type=int, choices=(5, 20), default=5)
+ parser.add_argument('--profiles', type=int, default=3)
+ parser.add_argument('--samples', type=int, default=20)
+ parser.add_argument('--edits', type=int, default=3)
+ parser.add_argument('--output', type=Path, required=True)
+ parser.add_argument('--profile', action='store_true', help='Profile summary, overview and suggestion operations; timings include profiler overhead')
+ parser.add_argument('--mysql-runtime-root', type=Path,
+ help='Verified external runtime for copied-source validation; no live database reuse')
+ args = parser.parse_args()
+ if args.output.exists(): parser.error('Choose a new receipt path')
+ if args.mysql_runtime_root is not None:
+ from workspace_mysql_runtime import _probe, runtime_spec
+ import workspace_native_mysql as native
+ runtime = _probe(args.mysql_runtime_root.resolve(strict=True), runtime_spec(ROOT)[0]['mysql_version'])
+ def runtime_binary(name='mysqld'):
+ if name not in {'mysqld', 'mysql', 'mysqldump'}:
+ raise ValueError('Unsupported external native executable')
+ return Path(runtime[name])
+ native.native_binary = runtime_binary
+ report = {'epochs': args.epochs, 'tags_per_record': args.tags, 'profiles': args.profiles,
+ 'protocols': 2, 'source_hashes_before': hashes(), 'release_ready': False,
+ 'external_mysql_runtime': str(args.mysql_runtime_root) if args.mysql_runtime_root else None,
+ 'scope': 'Actual native MySQL + production Flask/save hooks + sealed SQLite synthetic metadata; excludes physical H5 parsing, waveform/NAS I/O and browser rendering.'}
+ try:
+ run(args, report)
+ except Exception as error:
+ report['error'] = {'type': type(error).__name__, 'message': str(error), 'traceback': traceback.format_exc()}
+ print(traceback.format_exc(), flush=True)
+ report['source_hashes_after'] = hashes()
+ report['source_changed_during_run'] = [name for name, value in report['source_hashes_before'].items()
+ if value != report['source_hashes_after'][name]]
+ report['peak_rss_bytes_macos'] = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['passed'] = bool(report.get('oracle_passed')) and not report['source_changed_during_run']
+ args.output.write_text(json.dumps(report, indent=2) + '\n')
+ return 0 if report['passed'] else 1
+
+
+if __name__ == '__main__':
+ raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py b/docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py
new file mode 100644
index 0000000..7bc0fab
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py
@@ -0,0 +1,465 @@
+"""Persistent native tag lookup: tag-ten, tag-another-ten, filter-twenty and cell/epoch hierarchy.
+
+Every project/database is disposable. Acquisition metadata is synthetic; actual
+MySQL, triggers, Flask routes, derived indexes and each version's save hooks
+are used. Native SQL commits are checked immediately; any background recovery
+mirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude
+oracle work, and do not model browser concurrency/rendering. Sequential bundle
+times are sums of their separately measured requests. Profiles are separate.
+"""
+import argparse
+import copy
+import cProfile
+import datetime as dt
+import hashlib
+import json
+import math
+import os
+from pathlib import Path
+import pstats
+import resource
+import statistics
+import sys
+import tempfile
+import time
+import traceback
+
+
+def digest(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
+def percentiles(values):
+ ordered=sorted(values)
+ return {'samples_seconds':values,'p50_seconds':statistics.median(values),'p95_seconds':ordered[math.ceil(.95*len(ordered))-1],
+ 'p99_seconds':ordered[math.ceil(.99*len(ordered))-1],'maximum_seconds':max(values),'quantile_method':'nearest_rank'}
+def rule(tag,scope='effective'):return {'field':'annotations/'+scope+'/tags','operator':'contains','value':tag}
+def tags(ordinal,profile,*,cell=False):
+ if cell:return ['cell-common',f'cell-bucket:{ordinal%5}',f'cell-author:{profile}','Inherited' if profile==0 else 'inherited',f'cell-type:{ordinal%2}']
+ return ['all',f'gate:{profile}' if ordinal%(profile+2)==0 else f'rest:{profile}','QC' if ordinal%2==0 else 'qc',
+ 'é' if ordinal%3==0 else 'e\u0301',f'bucket:{ordinal%17}']
+def source_inventory(root):
+ return {str(path.relative_to(root)):digest(path) for path in sorted((root/'python').rglob('*.py'))}
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--source-root',type=Path,required=True)
+ ap.add_argument('--mysql-runtime-root',type=Path,required=True);ap.add_argument('--epochs',type=int,required=True)
+ ap.add_argument('--samples',type=int,default=5);ap.add_argument('--targets',default='tag10_then10_filter20_and_cell_epoch_hierarchy')
+ ap.add_argument('--output',type=Path,required=True);ap.add_argument('--profile',action='store_true')
+ ap.add_argument('--page-cells',action='store_true',default=True)
+ args=ap.parse_args()
+ args.output=args.output.resolve();root=args.source_root.resolve(strict=True)
+ if args.output.exists():ap.error('Choose an unused output receipt')
+ if args.epochs<200 or args.samples<1:ap.error('Use at least200 epochs and one sample')
+ # Product and fixture imports must come from the selected immutable tree.
+ sys.path[:0]=[str(root/'python'),str(root/'docs/dev/scale-audit-2026-09-29')]
+ os.chdir(root)
+ from benchmark_service_scale import fixture,Details,uid
+ import benchmark_service_scale
+ from recording_workspace import connect,workspace_tables
+ from workspace_projects import create_project
+ import workspace_native_mysql as native
+ from workspace_mysql_runtime import _probe,runtime_spec
+ from workspace_disk_index import DiskMetadataIndex
+ from workspace_api import create_app
+ import workspace_state_snapshot as snapshot
+ import importlib.util
+ from workspace_annotations import SharedAnnotations,annotation_tables
+ from workspace_curation import curation_tables
+ sqlite_tag_calls={'membership_index':0,'refresh':0}
+ original_membership=getattr(SharedAnnotations,'_membership_index',None)
+ if original_membership is not None:
+ def observed_membership(*args,**kwargs):
+ sqlite_tag_calls['membership_index']+=1
+ return original_membership(*args,**kwargs)
+ SharedAnnotations._membership_index=observed_membership
+ if importlib.util.find_spec('workspace_shared_tag_index') is not None:
+ from workspace_shared_tag_index import SharedTagIndex
+ original_refresh=SharedTagIndex.refresh
+ def observed_refresh(*args,**kwargs):
+ sqlite_tag_calls['refresh']+=1
+ return original_refresh(*args,**kwargs)
+ SharedTagIndex.refresh=observed_refresh
+ runtime=_probe(args.mysql_runtime_root.resolve(strict=True),runtime_spec(root)[0]['mysql_version'])
+ def runtime_binary(name='mysqld'):
+ if name not in {'mysqld','mysql','mysqldump'}:raise ValueError('Unsupported native executable')
+ return Path(runtime[name])
+ native.native_binary=runtime_binary
+ report={'scope':__doc__,'epochs':args.epochs,'profiles':3,'tags_per_annotation':5,'protocols':2,'samples':args.samples,
+ 'source_root':str(root),'source_inventory_before':source_inventory(root),'fixture_module':str(Path(benchmark_service_scale.__file__).resolve()),
+ 'harness_sha256':digest(__file__),'started_at':dt.datetime.now(dt.timezone.utc).isoformat(),'release_ready':False,'operations':{},'checks':[],
+ 'page_cells':True,'target_specification':args.targets,'baseline_harness_sha256':digest(Path(__file__).with_name('benchmark_scientist_tag_workflow.py'))}
+ samples={};sql_samples={};app=None;dj=None;project_root=None;index=None;closed=False
+ def check(name,condition,**detail):
+ report['checks'].append({'name':name,'passed':bool(condition),**detail})
+ if not condition:raise AssertionError(name)
+ def log(message):print(message,flush=True)
+ started=time.perf_counter()
+ try:
+ with tempfile.TemporaryDirectory(prefix='rieke-native-tag-sequence-') as directory:
+ project_root=Path(create_project(Path(directory)/'projects','Disposable scientist tag workflow')['path'])
+ try:
+ native.ensure_native_database(project_root);dj=connect({'kind':'native-project'},project_dir=project_root)
+ service,protocol=fixture(args.epochs);service.project_dir=project_root
+ service.project=json.loads((project_root/'project.json').read_text());service.config=json.loads((project_root/'catalog.json').read_text());service.dj=dj
+ service.manifests={item['source_sha256']:{'source_path':'/synthetic/'+item['filename']} for item in service.sources}
+ other=uid(-3);service.protocols[other]=copy.deepcopy(service.protocols[protocol])
+ service.protocols[other]['definition']['protocol_uuid']=other;service.protocols[other]['result']['protocol_uuid']=other
+ for value in service.protocols.values():
+ value['definition']['project_uuid']=service.project['project_uuid']
+ value['definition']['query']={'version':1,'all':[{'field':'EpochBlock.protocol_name','operator':'eq','value':'Synthetic'}]}
+ index=DiskMetadataIndex.build(project_root/'cache'/'metadata.sqlite',service.rows,Details(service.rows),service.sources,'tag-workflow-fixture-v1',service.project['project_uuid'])
+ service.disk_index=index;service.details=index.details
+ Project,_,_,_=workspace_tables(dj);project=service.project['project_uuid']
+ Project.insert1({'project_uuid':project,'name':service.project['name'],'directory':str(project_root)})
+ # Construct only canonical fixture tables before application
+ # startup. Normal startup then performs the actual populated
+ # native lookup/dictionary migration once; all timed writes
+ # retain the installed product triggers.
+ ProfileTable,AnnotationTable=annotation_tables(dj)
+ CurationTable,_=curation_tables(dj)
+ derived_tables=dj.conn().query("SELECT table_name FROM information_schema.tables WHERE table_schema='recording_workspace' AND table_name IN ('app_shared_tag_lookup','app_shared_tag_dictionary','app_shared_tag_authors')").fetchall()
+ assert not derived_tables,derived_tables
+ authors=[uid(-100-i) for i in range(3)]
+ author_names=['Same Scientist','Same Scientist','Scientist C'];stamp=dt.datetime(2026,9,29,12)
+ ids=tuple(service.rows);ordinals={identity:i for i,identity in enumerate(ids)};cell_ordinals={identity:i for i,identity in enumerate(service.cells)}
+ report['mysql_version']=dj.conn().query('SELECT VERSION()').fetchone()[0]
+ seed=time.perf_counter();log(json.dumps({'stage':'seed','epochs':args.epochs,'source_root':str(root)}))
+ with dj.conn().transaction:
+ ProfileTable.insert([{'project_uuid':project,'profile_uuid':author,'display_name':author_names[p],'created_at':stamp,'created_by':'workflow oracle'} for p,author in enumerate(authors)])
+ batch=[]
+ for kind,identities in [('epoch',ids),('cell',tuple(service.cells))]:
+ for ordinal,identity in enumerate(identities):
+ for p,author in enumerate(authors):
+ batch.append({'project_uuid':project,'target_kind':kind,'target_uuid':identity,'profile_uuid':author,
+ 'tags':tags(ordinal,p,cell=kind=='cell'),'author_name':author_names[p],'revision':1,'updated_at':stamp})
+ if len(batch)==500:AnnotationTable.insert(batch);batch=[]
+ if batch:AnnotationTable.insert(batch);batch=[]
+ for p,current in enumerate((protocol,other)):
+ for identity in ids:
+ batch.append({'project_uuid':project,'protocol_uuid':current,'epoch_uuid':identity,'included':True,
+ 'tags':[f'dataset:{p}',*[f'dataset-tag:{i}' for i in range(4)]],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':service._fingerprints[identity]})
+ if len(batch)==500:CurationTable.insert(batch);batch=[]
+ if batch:CurationTable.insert(batch);batch=[]
+ report['seed_seconds']=time.perf_counter()-seed
+ log(json.dumps({'stage':'populated_native_startup','epochs':args.epochs,'canonical_seed_seconds':report['seed_seconds']}))
+ began=time.perf_counter()
+ app=create_app(project_root,root/'.rieke-runtime/retinanalysis',service=service)
+ report['populated_native_startup_seconds']=time.perf_counter()-began
+ assert service._recovery_tracker.ready,service._recovery_tracker.reason
+ store=app.extensions['curation_store'];shared=app.extensions['shared_annotations']
+ report['fixture_construction']={'strategy':'canonical seed before normal app startup and populated native migration',
+ 'native_derived_tables_absent_during_seed':not derived_tables,
+ 'canonical_shared_records':(args.epochs+len(service.cells))*3,
+ 'canonical_curation_records':args.epochs*2,
+ 'product_triggers_disabled_during_timed_operations':False}
+ check('populated_native_migration_ready_before_timed_operations',service.annotation_preparation.get('status')=='ready' and service.annotation_preparation.get('storage')=='native_sql')
+ checkpoint=time.perf_counter();snapshot.save(project_root,dj.conn(),service=service);report['seed_checkpoint_seconds']=time.perf_counter()-checkpoint
+ log(json.dumps({'stage':'timed_operations','epochs':args.epochs,'seed_seconds':report['seed_seconds']}))
+ client=app.test_client();base=f'/api/protocols/{protocol}';headers={'X-Workspace-Request':'1','Origin':'http://localhost:8766'}
+
+
+ import inspect
+ import threading
+ app_db_lock=inspect.getclosurevars(app.view_functions['annotation_batch_read']).nonlocals['db_lock']
+ report['native_lookup_startup_preparation']=copy.deepcopy(getattr(service,'annotation_preparation',None))
+ first=ids[:10];second=ids[100:110];selected=first+second;cell=service.rows[first[0]]['cell_uuid']
+ children=tuple(key for key in ids if service.rows[key]['cell_uuid']==cell)
+ assert len(children)==100 and service.rows[second[0]]['cell_uuid']!=cell
+ epoch_marker='sequence-selected';cell_marker='sequence-cell'
+ epoch_predicate={'all':[rule(epoch_marker,'epoch')]}
+ cell_predicate={'all':[rule(cell_marker,'cell')]}
+ hierarchy={'all':[rule(cell_marker,'cell'),rule(epoch_marker,'epoch')]}
+ model={(kind,key,p):set() for kind,keys in [('epoch',selected),('cell',(cell,))] for key in keys for p in range(3)}
+ revisions={key:1 for key in model}
+ report['selected_epoch_uuids']={'first10':first,'second10':second,'cell':cell,'cell_children':children}
+ report['backup_receipts']=[]
+ def sql_status():
+ with app_db_lock:return {key:int(value) for key,value in dj.conn().query("SHOW SESSION STATUS WHERE Variable_name IN ('Bytes_sent','Questions')").fetchall()}
+ def request(method,path,*,query=None,body=None):
+ response=client.get(path,query_string=query) if method=='GET' else client.post(path,json=body,headers=headers)
+ value=response.get_json()
+ if response.status_code!=200:raise AssertionError((method,path,response.status_code,value))
+ return value
+ def measure(name,callback):
+ before=sql_status();began=time.perf_counter();value=callback();elapsed=time.perf_counter()-began;after=sql_status()
+ samples.setdefault(name,[]).append(elapsed);sql_samples.setdefault(name,[]).append({key:after[key]-before[key] for key in before})
+ return value,elapsed
+ def wanted_tags(kind,key,p):
+ ordinal=ordinals[key] if kind=='epoch' else cell_ordinals[key]
+ return sorted([*tags(ordinal,p,cell=kind=='cell'),*model.get((kind,key,p),())])
+ def validate_target(row,kind,key):
+ assert row['target_uuid']==key and row['target_kind']==kind
+ assert row['revisions']=={author:revisions.get((kind,key,p),1) for p,author in enumerate(authors)}
+ assert sorted((chip['tag'],chip['profile_uuid']) for chip in row['tags'])==sorted((tag,author) for p,author in enumerate(authors) for tag in wanted_tags(kind,key,p))
+ def preflight(label,keys,p,*,timed=True):
+ call=lambda:request('POST','/api/annotations/read',body={'target_kind':'epoch','target_uuids':list(keys)})
+ value,_=measure(label,call) if timed else (call(),0)
+ assert set(value['targets'])==set(keys)
+ for key,row in value['targets'].items():validate_target(row,'epoch',key)
+ return {key:value['targets'][key]['revisions'][authors[p]] for key in keys}
+ def change(label,kind,keys,p,marker,add,expected=None,*,timed=True):
+ body={'target_kind':kind,'target_uuids':list(keys),'profile_uuid':authors[p],
+ 'tags_add':[marker] if add else [],'tags_remove':[] if add else [marker],
+ 'expected_revisions':expected or {key:revisions[(kind,key,p)] for key in keys}}
+ call=lambda:request('POST','/api/annotations',body=body)
+ value,_=measure(label,call) if timed else (call(),0)
+ assert value['changed']==len(keys)
+ if 'persistence' in value:
+ assert value['persistence']['database']=='committed',value
+ report['backup_receipts'].append({'operation':label,'persistence':value['persistence']})
+ for key in keys:
+ revisions[(kind,key,p)]+=1
+ if add:model[(kind,key,p)].add(marker)
+ else:model[(kind,key,p)].remove(marker)
+ assert set(value['targets'])==set(keys)
+ for key,row in value['targets'].items():validate_target(row,kind,key)
+ # The oracle uses the application's actual SQL lock so the
+ # candidate's background mirror cannot race this connection.
+ with app_db_lock:
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':kind,'profile_uuid':authors[p]}&[{'target_uuid':key} for key in keys]).to_dicts()
+ assert {row['target_uuid'] for row in rows}==set(keys)
+ for row in rows:
+ key=row['target_uuid'];assert row['revision']==revisions[(kind,key,p)] and sorted(row['tags'])==wanted_tags(kind,key,p)
+ return value
+ def query(predicate,**extra):return {'tag_predicate':json.dumps(predicate,separators=(',',':')),**extra}
+ def page(label,predicate,wanted,*,offset=0,timed=True):
+ call=lambda:request('GET',base+'/epochs',query=query(predicate,limit=60,offset=offset,include_cells='true'))
+ value,elapsed=measure(label,call) if timed else (call(),0)
+ assert value['total']==len(wanted)
+ assert tuple(row['epoch_uuid'] for row in value['epochs'])==wanted[offset:offset+60]
+ counts={}
+ for key in wanted:
+ target_cell=service.rows[key]['cell_uuid'];counts[target_cell]=counts.get(target_cell,0)+1
+ assert {row['cell_uuid']:row['epochs'] for row in value['cells']}==counts
+ for row in value['epochs']:
+ key=row['epoch_uuid'];target_cell=service.rows[key]['cell_uuid']
+ for kind,field,target in [('epoch','epoch_tags',key),('cell','cell_tags',target_cell)]:
+ assert sorted((chip['tag'],chip['profile_uuid']) for chip in row['annotations'][field])==sorted((tag,author) for p,author in enumerate(authors) for tag in wanted_tags(kind,target,p))
+ assert row['curation']['revision']==1
+ assert sorted(row['curation']['tags'])==sorted(['dataset:0',*[f'dataset-tag:{j}' for j in range(4)]])
+ with app_db_lock:
+ exact,_=service._epoch_page_identities(protocol,query(predicate));assert tuple(exact)==wanted
+ return value,elapsed
+ def child_sql():
+ with app_db_lock:
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'}&[{'target_uuid':key} for key in children]).to_dicts()
+ return sorted((row['target_uuid'],row['profile_uuid'],row['revision'],tuple(sorted(row['tags'])),row['author_name'],str(row['updated_at'])) for row in rows)
+ # Native lookup is persistent. The first filter is requested
+ # only after the two timed writes, without warming a tag cache.
+ prepare_project_annotations=None
+ report['preparation_index_seconds']=0
+ report['lookup_mode']='persistent_native_sql_no_post_seed_tag_cache_preparation'
+ report['bundle_definitions']={
+ 'tag10_then10_filter20_http_total':['first10_revision_preflight','first10_save','second10_revision_preflight','second10_save','filter20'],
+ 'cell_tag_then_cell_filter_http_total':['cell_save','filter_cell100'],
+ 'first10_selected_action_http_total':['first10_revision_preflight','first10_save'],
+ 'second10_selected_action_http_total':['second10_revision_preflight','second10_save']}
+ for iteration in range(args.samples):
+ p=iteration%3
+ expected=preflight('first10_revision_preflight',first,p)
+ change('first10_save','epoch',first,p,epoch_marker,True,expected)
+ expected=preflight('second10_revision_preflight',second,p)
+ change('second10_save','epoch',second,p,epoch_marker,True,expected)
+ value,_=page('filter20',epoch_predicate,selected)
+ before_children=child_sql()
+ # The rendered page already carries cell revisions, as the
+ # direct-cell editor does; no selected-epoch preflight.
+ cell_expected={cell:value['epochs'][0]['annotations']['revisions']['cell'][authors[p]]}
+ change('cell_save','cell',(cell,),p,cell_marker,True,cell_expected)
+ page('filter_cell100',cell_predicate,children)
+ page('filter_cell100_last_page',cell_predicate,children,offset=40,timed=False)
+ page('filter_cell_and_epoch10',hierarchy,first)
+ page('oracle_cell_marker_not_direct',{'all':[rule(cell_marker,'epoch')]},(),timed=False)
+ assert child_sql()==before_children,'Cell add changed child annotation records'
+ expected=preflight('remove_first10_revision_preflight',first,p)
+ change('remove_first10_save','epoch',first,p,epoch_marker,False,expected)
+ page('filter_hierarchy_after_epoch_remove',hierarchy,())
+ page('filter_remaining10',epoch_predicate,second)
+ expected=preflight('remove_second10_revision_preflight',second,p)
+ change('remove_second10_save','epoch',second,p,epoch_marker,False,expected)
+ page('filter_after_all_epoch_removals',epoch_predicate,())
+ before_children=child_sql()
+ change('remove_cell_save','cell',(cell,),p,cell_marker,False)
+ page('filter_after_cell_removal',cell_predicate,())
+ assert child_sql()==before_children,'Cell removal changed child annotation records'
+ with app_db_lock:assert len(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'})==args.epochs*3
+ for label,parts in report['bundle_definitions'].items():samples.setdefault(label,[]).append(sum(samples[part][-1] for part in parts))
+ log(json.dumps({'stage':'sequence_cycle','epochs':args.epochs,'iteration':iteration+1}))
+ def flush_backup():
+ scheduler=app.extensions.get('backup_scheduler')
+ if scheduler is not None:
+ scheduler.flush();report['final_backup_status']=scheduler.status()
+ # Separate one-sample diagnostics; neither request warms the
+ # measured write/filter sequence above.
+ flush_backup()
+ autocomplete_explain={};report['autocomplete_sql']={};report['autocomplete_requests']={}
+ from collections import Counter,defaultdict
+ expected_counts=Counter();expected_authors=defaultdict(set)
+ for kind,count in (('epoch',args.epochs),('cell',len(service.cells))):
+ for ordinal in range(count):
+ target_tags=set()
+ for p in range(3):
+ seeded=tags(ordinal,p,cell=kind=='cell');target_tags.update(seeded)
+ for tag in seeded:expected_authors[tag].add(p)
+ expected_counts.update(target_tags)
+ empty_candidates=sorted(expected_counts,key=lambda tag:(-expected_counts[tag],tag.casefold(),tag))
+ for label,search in (('autocomplete_cold_diagnostic','gate'),('autocomplete_warm_diagnostic','gate'),('autocomplete_empty_diagnostic','')):
+ connection=dj.conn();original_query=connection.query;owner=threading.get_ident();selected_queries=[]
+ def trace_autocomplete_sql(statement,*positional,**keywords):
+ began=time.perf_counter();cursor=None
+ try:
+ cursor=original_query(statement,*positional,**keywords)
+ return cursor
+ finally:
+ if threading.get_ident()==owner and str(statement).lstrip().upper().startswith('SELECT') and any(table in str(statement) for table in ('`app_shared_tag_lookup`','`app_shared_tag_dictionary`','`app_shared_tag_authors`','`shared_annotation`')):
+ selected_queries.append({'sql':str(statement),'seconds':time.perf_counter()-began,'result_rows':getattr(cursor,'rowcount',None)})
+ autocomplete_explain[str(statement)]=positional[0] if positional else keywords.get('args')
+ connection.query=trace_autocomplete_sql
+ try:value,_=measure(label,lambda:request('GET','/api/annotation-tags',query={'q':search,'limit':12}))
+ finally:connection.query=original_query
+ report['autocomplete_sql'][label]=selected_queries
+ report['autocomplete_requests'][label]={'q':search,'limit':12,'total':value['total']}
+ if search:
+ expected=[{'tag':f'gate:{p}','count':(args.epochs+p+1)//(p+2),
+ 'authors':[{'profile_uuid':authors[p],'display_name':author_names[p]}]} for p in range(3)]
+ expected_total=3
+ else:
+ expected=[{'tag':tag,'count':expected_counts[tag],
+ 'authors':sorted([{'profile_uuid':authors[p],'display_name':author_names[p]} for p in expected_authors[tag]],key=lambda author:author['profile_uuid'])}
+ for tag in empty_candidates[:12]]
+ expected_total=len(empty_candidates)
+ assert value['tags']==expected and value['total']==expected_total,(label,value,expected)
+ with app_db_lock:
+ report['autocomplete_query_plans']=[{'sql':statement,'explain':list(connection.query('EXPLAIN '+statement,parameters,as_dict=True,reconnect=False).fetchall())}
+ for statement,parameters in autocomplete_explain.items()]
+ check('native_autocomplete_exact_counts_and_profile_authors',True)
+ import re
+ autocomplete_queries=[item for values in report['autocomplete_sql'].values() for item in values]
+ membership_queries=[item for item in autocomplete_queries if '`app_shared_tag_lookup`' in item['sql']]
+ check('autocomplete_has_no_membership_aggregation',all(
+ not re.search(r'\b(?:COUNT|SUM|MAX|MIN|AVG)\s*\(|\bGROUP\s+BY\b|\bDISTINCT\b',item['sql'],re.I)
+ and re.search(r'\bLIMIT\s+1\b',item['sql'],re.I)
+ and item['result_rows'] in (0,1) for item in membership_queries),
+ membership_selects=len(membership_queries),maximum_rows_per_seek=max((item['result_rows'] for item in membership_queries),default=0))
+ dictionary_queries=[item for item in autocomplete_queries if '`app_shared_tag_dictionary`' in item['sql']]
+ check('autocomplete_compact_dictionary_fetches_are_bounded',bool(dictionary_queries) and all(
+ re.search(r'\bCOUNT\s*\(',item['sql'],re.I) or re.search(r'\bLIMIT\b',item['sql'],re.I)
+ for item in dictionary_queries),dictionary_selects=len(dictionary_queries),
+ scope='Counts may scan matching compact entries; no unbounded dictionary rows are fetched for Python prefix filtering.')
+ membership_plans=[row for item in report['autocomplete_query_plans'] if '`app_shared_tag_lookup`' in item['sql'] for row in item['explain']]
+ check('autocomplete_membership_winner_seeks_are_indexed',all(row.get('key')=='by_tag' and row.get('type') in {'const','ref','range','eq_ref'} for row in membership_plans),plans=membership_plans)
+ if args.profile:
+ for name,keys in [('first10',first),('second10',second)]:
+ expected=preflight('profile_'+name+'_preflight',keys,0,timed=False)
+ change('profile_'+name+'_save','epoch',keys,0,epoch_marker,True,expected,timed=False)
+ flush_backup()
+ import re
+ connection=dj.conn();original_query=connection.query;owner=threading.get_ident();sql_calls=[];lookup_selects=[]
+ def traced_query(statement,*positional,**keywords):
+ began=time.perf_counter()
+ try:return original_query(statement,*positional,**keywords)
+ finally:
+ is_filter=threading.get_ident()==owner
+ sql_calls.append({'thread':'filter' if is_filter else 'background','sql_shape':re.sub(r"'[^']*'","'?'",str(statement))[:240],'seconds':time.perf_counter()-began})
+ if is_filter and str(statement).lstrip().upper().startswith('SELECT') and '`app_shared_tag_lookup`' in str(statement):
+ lookup_selects.append((str(statement),positional[0] if positional else keywords.get('args')))
+ profiler=cProfile.Profile();connection.query=traced_query
+ try:profiled=profiler.runcall(lambda:request('GET',base+'/epochs',query=query(epoch_predicate,limit=60,offset=0,include_cells='true')))
+ finally:connection.query=original_query
+ assert profiled['total']==20 and tuple(row['epoch_uuid'] for row in profiled['epochs'])==selected
+ stats=pstats.Stats(profiler);rows=[]
+ for key,value in sorted(stats.stats.items(),key=lambda item:item[1][3],reverse=True):
+ path=Path(key[0]);rows.append({'file':str(path.relative_to(root)) if path.is_absolute() and path.is_relative_to(root) else key[0],
+ 'line':key[1],'function':key[2],'primitive_calls':value[0],'calls':value[1],'self_seconds':value[2],'cumulative_seconds':value[3]})
+ report['filter20_cpu_profile']={'top_functions':rows[:100],'native_sql_calls':sql_calls,'limits':'Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap.'}
+ assert lookup_selects,'The fresh filter did not execute a native tag lookup SELECT'
+ plans=[]
+ with app_db_lock:
+ for statement,parameters in lookup_selects:
+ explanation=list(connection.query('EXPLAIN '+statement,parameters,as_dict=True,reconnect=False).fetchall())
+ assert any(row.get('key')=='by_tag' and row.get('type') in ('ref','range','const','eq_ref') for row in explanation),explanation
+ plans.append({'sql':statement,'explain':explanation})
+ report['native_lookup_query_plans']=plans
+ check('actual_filter_uses_persistent_by_tag_sql_index',True,queries=len(plans))
+ page('profile_filter20_verify',epoch_predicate,selected,timed=False)
+ from workspace_annotation_preparation import prepare_project_annotations as prepare_native_lookup
+ flush_backup();began=time.perf_counter()
+ with app_db_lock:
+ checkpoint=prepare_native_lookup(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'))
+ assert checkpoint['status']=='ready' and checkpoint['storage']=='native_sql',checkpoint
+ shared.native_tag_lookup=None
+ reopened=prepare_native_lookup(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'),reuse=True)
+ assert reopened['status']=='ready' and reopened['storage']=='native_sql' and reopened['reused'],reopened
+ report['same_server_native_lookup_reuse']={'checkpoint':checkpoint,'reopened':reopened,'seconds':time.perf_counter()-began,
+ 'scope':'Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart.'}
+ page('same_server_native_reuse_filter20',epoch_predicate,selected)
+ check('persistent_native_lookup_reused_without_rebuild',True)
+ if prepare_project_annotations is not None:
+ flush_backup();began=time.perf_counter()
+ with app_db_lock:
+ checkpoint=prepare_project_annotations(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'))
+ assert checkpoint['status']=='ready' and checkpoint['checkpoint']['saved'],checkpoint
+ previous_builds=shared._shared_tag_index.stats['full_builds']
+ shared._shared_tag_index.close()
+ reopened=prepare_project_annotations(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'),reuse=True)
+ assert reopened['status']=='ready' and reopened['checkpoint_restore']['restored'],reopened
+ after_builds=shared._shared_tag_index.stats['full_builds']
+ assert after_builds in (0,previous_builds),(previous_builds,after_builds)
+ report['same_server_checkpoint_reopen']={'seconds':time.perf_counter()-began,'checkpoint':checkpoint,'reopened':reopened,'full_builds_before':previous_builds,'full_builds_after':after_builds,'scope':'Shared derived index closed and reopened within the same native server; not a native process restart.'}
+ page('same_server_reopened_filter20',epoch_predicate,selected)
+ check('same_server_checkpoint_restores_exact_twenty_without_rebuild',True)
+ for name,keys in [('first10',first),('second10',second)]:
+ change('profile_remove_'+name,'epoch',keys,0,epoch_marker,False,timed=False)
+ # Untimed exactness checks run after the measured workflow.
+ unicode_oracles=[
+ ('case_upper',{'all':[rule('QC','epoch')]},lambda i:i%2==0),
+ ('case_lower',{'all':[rule('qc','epoch')]},lambda i:i%2==1),
+ ('unicode_nfc',{'all':[rule('é','epoch')]},lambda i:i%3==0),
+ ('unicode_nfd',{'all':[rule('e\u0301','epoch')]},lambda i:i%3!=0),
+ ('or',{'any':[rule('QC','epoch'),rule('é','epoch')]},lambda i:i%2==0 or i%3==0),
+ ('not_with_inheritance',{'all':[rule('cell-common','cell'),{'not':rule('QC','epoch')}]},lambda i:i%2==1),
+ ('inherited_excluded_from_direct',{'all':[rule('cell-common','epoch')]},lambda i:False)]
+ for label,predicate,accept in unicode_oracles:
+ wanted=tuple(identity for i,identity in enumerate(ids) if accept(i))
+ page('oracle_'+label,predicate,wanted,timed=False)
+ check('exact_native_'+label,True,count=len(wanted))
+ report['sqlite_tag_cache_calls']=dict(sqlite_tag_calls)
+ check('no_sqlite_tag_membership_or_refresh_used',not any(sqlite_tag_calls.values()),calls=sqlite_tag_calls)
+ flush_backup()
+ check('canonical_final_markers_removed',not any(model.values()))
+ report['workflow_peak_rss_before_durable_oracle_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ began=time.perf_counter();state=snapshot.load(project_root/'app-state.json');report['durable_snapshot_verify_seconds']=time.perf_counter()-began
+ saved={(row['target_kind'],row['target_uuid'],row['profile_uuid']):row for row in state['tables']['shared_annotation']}
+ for (kind,key,p),revision in revisions.items():
+ row=saved[(kind,key,authors[p])];assert row['revision']==revision and sorted(row['tags'])==wanted_tags(kind,key,p)
+ check('flushed_recovery_mirror_exact_revisions_and_removal',True,records=len(revisions))
+ check('one_canonical_cell_record_no_child_copies',sum(row['target_kind']=='epoch' for row in saved.values())==args.epochs*3,child_epochs=len(children))
+ check('independent_protocol_curation_untouched',len(state['tables']['curation'])==args.epochs*2 and all(row['revision']==1 for row in state['tables']['curation']))
+ loaded={name:str(Path(module.__file__).resolve()) for name,module in sys.modules.items() if (name.startswith('workspace_') or name=='recording_workspace') and getattr(module,'__file__',None)}
+ check('all_product_modules_loaded_from_selected_source',all(Path(path).is_relative_to(root) for path in loaded.values()));report['loaded_product_modules']=loaded
+ finally:
+ if app is not None:
+ scheduler=app.extensions.get('backup_scheduler')
+ if scheduler is not None:
+ scheduler.flush()
+ close=getattr(scheduler,'close',None)
+ if close is not None:close(flush=False)
+ shared=app.extensions.get('shared_annotations');derived=getattr(shared,'_shared_tag_index',None)
+ if derived is not None:derived.close()
+ lock=app.extensions.get('app_state_session_lock')
+ if lock is not None:lock.close()
+ if index is not None:index.close()
+ if dj is not None:dj.conn().close()
+ if project_root is not None:closed=native.stop_native_database(project_root)
+ report['owned_native_runtime_stopped']=closed
+ except Exception as error:
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};log(traceback.format_exc())
+ report['operations']={name:{**percentiles(values),'sql_session_deltas':sql_samples.get(name,[])} for name,values in samples.items()}
+ report['source_inventory_after']=source_inventory(root);report['source_unchanged']=report['source_inventory_before']==report['source_inventory_after']
+ report['harness_unchanged']=digest(__file__)==report['harness_sha256']
+ report['seconds']=time.perf_counter()-started;report['finished_at']=dt.datetime.now(dt.timezone.utc).isoformat();report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['passed']=not report.get('error') and closed and report['source_unchanged'] and report['harness_unchanged'] and all(item['passed'] for item in report['checks'])
+ args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');log(json.dumps({'stage':'done','epochs':args.epochs,'passed':report['passed'],'seconds':report['seconds'],'output':str(args.output)}))
+ return int(not report['passed'])
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence_post_profile.py b/docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence_post_profile.py
new file mode 100644
index 0000000..949db4c
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence_post_profile.py
@@ -0,0 +1,413 @@
+"""Persistent native tag lookup: tag-ten, tag-another-ten, filter-twenty and cell/epoch hierarchy.
+
+Every project/database is disposable. Acquisition metadata is synthetic; actual
+MySQL, triggers, Flask routes, derived indexes and each version's save hooks
+are used. Native SQL commits are checked immediately; any background recovery
+mirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude
+oracle work, and do not model browser concurrency/rendering. Sequential bundle
+times are sums of their separately measured requests. Profiles are separate.
+"""
+import argparse
+import copy
+import cProfile
+import datetime as dt
+import hashlib
+import json
+import math
+import os
+from pathlib import Path
+import pstats
+import resource
+import statistics
+import sys
+import tempfile
+import time
+import traceback
+
+
+def digest(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
+def percentiles(values):
+ ordered=sorted(values)
+ return {'samples_seconds':values,'p50_seconds':statistics.median(values),'p95_seconds':ordered[math.ceil(.95*len(ordered))-1],
+ 'p99_seconds':ordered[math.ceil(.99*len(ordered))-1],'maximum_seconds':max(values),'quantile_method':'nearest_rank'}
+def rule(tag,scope='effective'):return {'field':'annotations/'+scope+'/tags','operator':'contains','value':tag}
+def tags(ordinal,profile,*,cell=False):
+ if cell:return ['cell-common',f'cell-bucket:{ordinal%5}',f'cell-author:{profile}','Inherited' if profile==0 else 'inherited',f'cell-type:{ordinal%2}']
+ return ['all',f'gate:{profile}' if ordinal%(profile+2)==0 else f'rest:{profile}','QC' if ordinal%2==0 else 'qc',
+ 'é' if ordinal%3==0 else 'e\u0301',f'bucket:{ordinal%17}']
+def source_inventory(root):
+ return {str(path.relative_to(root)):digest(path) for path in sorted((root/'python').rglob('*.py'))}
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--source-root',type=Path,required=True)
+ ap.add_argument('--mysql-runtime-root',type=Path,required=True);ap.add_argument('--epochs',type=int,required=True)
+ ap.add_argument('--samples',type=int,default=5);ap.add_argument('--targets',default='tag10_then10_filter20_and_cell_epoch_hierarchy')
+ ap.add_argument('--output',type=Path,required=True);ap.add_argument('--profile',action='store_true')
+ ap.add_argument('--page-cells',action='store_true',default=True)
+ args=ap.parse_args()
+ args.output=args.output.resolve();root=args.source_root.resolve(strict=True)
+ if args.output.exists():ap.error('Choose an unused output receipt')
+ if args.epochs<200 or args.samples<1:ap.error('Use at least200 epochs and one sample')
+ # Product and fixture imports must come from the selected immutable tree.
+ sys.path[:0]=[str(root/'python'),str(root/'docs/dev/scale-audit-2026-09-29')]
+ os.chdir(root)
+ from benchmark_service_scale import fixture,Details,uid
+ import benchmark_service_scale
+ from recording_workspace import connect,workspace_tables
+ from workspace_projects import create_project
+ import workspace_native_mysql as native
+ from workspace_mysql_runtime import _probe,runtime_spec
+ from workspace_disk_index import DiskMetadataIndex
+ from workspace_api import create_app
+ import workspace_state_snapshot as snapshot
+ import importlib.util
+ from workspace_annotations import SharedAnnotations
+ sqlite_tag_calls={'membership_index':0,'refresh':0}
+ original_membership=getattr(SharedAnnotations,'_membership_index',None)
+ if original_membership is not None:
+ def observed_membership(*args,**kwargs):
+ sqlite_tag_calls['membership_index']+=1
+ return original_membership(*args,**kwargs)
+ SharedAnnotations._membership_index=observed_membership
+ if importlib.util.find_spec('workspace_shared_tag_index') is not None:
+ from workspace_shared_tag_index import SharedTagIndex
+ original_refresh=SharedTagIndex.refresh
+ def observed_refresh(*args,**kwargs):
+ sqlite_tag_calls['refresh']+=1
+ return original_refresh(*args,**kwargs)
+ SharedTagIndex.refresh=observed_refresh
+ runtime=_probe(args.mysql_runtime_root.resolve(strict=True),runtime_spec(root)[0]['mysql_version'])
+ def runtime_binary(name='mysqld'):
+ if name not in {'mysqld','mysql','mysqldump'}:raise ValueError('Unsupported native executable')
+ return Path(runtime[name])
+ native.native_binary=runtime_binary
+ report={'scope':__doc__,'epochs':args.epochs,'profiles':3,'tags_per_annotation':5,'protocols':2,'samples':args.samples,
+ 'source_root':str(root),'source_inventory_before':source_inventory(root),'fixture_module':str(Path(benchmark_service_scale.__file__).resolve()),
+ 'harness_sha256':digest(__file__),'started_at':dt.datetime.now(dt.timezone.utc).isoformat(),'release_ready':False,'operations':{},'checks':[],
+ 'page_cells':True,'target_specification':args.targets,'baseline_harness_sha256':digest(Path(__file__).with_name('benchmark_scientist_tag_workflow.py'))}
+ samples={};sql_samples={};app=None;dj=None;project_root=None;index=None;closed=False
+ def check(name,condition,**detail):
+ report['checks'].append({'name':name,'passed':bool(condition),**detail})
+ if not condition:raise AssertionError(name)
+ def log(message):print(message,flush=True)
+ started=time.perf_counter()
+ try:
+ with tempfile.TemporaryDirectory(prefix='rieke-native-tag-sequence-') as directory:
+ project_root=Path(create_project(Path(directory)/'projects','Disposable scientist tag workflow')['path'])
+ try:
+ native.ensure_native_database(project_root);dj=connect({'kind':'native-project'},project_dir=project_root)
+ service,protocol=fixture(args.epochs);service.project_dir=project_root
+ service.project=json.loads((project_root/'project.json').read_text());service.config=json.loads((project_root/'catalog.json').read_text());service.dj=dj
+ service.manifests={item['source_sha256']:{'source_path':'/synthetic/'+item['filename']} for item in service.sources}
+ other=uid(-3);service.protocols[other]=copy.deepcopy(service.protocols[protocol])
+ service.protocols[other]['definition']['protocol_uuid']=other;service.protocols[other]['result']['protocol_uuid']=other
+ for value in service.protocols.values():
+ value['definition']['project_uuid']=service.project['project_uuid']
+ value['definition']['query']={'version':1,'all':[{'field':'EpochBlock.protocol_name','operator':'eq','value':'Synthetic'}]}
+ index=DiskMetadataIndex.build(project_root/'cache'/'metadata.sqlite',service.rows,Details(service.rows),service.sources,'tag-workflow-fixture-v1',service.project['project_uuid'])
+ service.disk_index=index;service.details=index.details
+ Project,_,_,_=workspace_tables(dj);project=service.project['project_uuid']
+ Project.insert1({'project_uuid':project,'name':service.project['name'],'directory':str(project_root)})
+ app=create_app(project_root,root/'.rieke-runtime/retinanalysis',service=service)
+ assert service._recovery_tracker.ready,service._recovery_tracker.reason
+ store=app.extensions['curation_store'];shared=app.extensions['shared_annotations'];authors=[uid(-100-i) for i in range(3)]
+ author_names=['Same Scientist','Same Scientist','Scientist C'];stamp=dt.datetime(2026,9,29,12)
+ ids=tuple(service.rows);ordinals={identity:i for i,identity in enumerate(ids)};cell_ordinals={identity:i for i,identity in enumerate(service.cells)}
+ report['mysql_version']=dj.conn().query('SELECT VERSION()').fetchone()[0]
+ seed=time.perf_counter();log(json.dumps({'stage':'seed','epochs':args.epochs,'source_root':str(root)}))
+ with dj.conn().transaction:
+ shared.Profile.insert([{'project_uuid':project,'profile_uuid':author,'display_name':author_names[p],'created_at':stamp,'created_by':'workflow oracle'} for p,author in enumerate(authors)])
+ batch=[]
+ for kind,identities in [('epoch',ids),('cell',tuple(service.cells))]:
+ for ordinal,identity in enumerate(identities):
+ for p,author in enumerate(authors):
+ batch.append({'project_uuid':project,'target_kind':kind,'target_uuid':identity,'profile_uuid':author,
+ 'tags':tags(ordinal,p,cell=kind=='cell'),'author_name':author_names[p],'revision':1,'updated_at':stamp})
+ if len(batch)==500:shared.Annotation.insert(batch);batch=[]
+ if batch:shared.Annotation.insert(batch);batch=[]
+ for p,current in enumerate((protocol,other)):
+ for identity in ids:
+ batch.append({'project_uuid':project,'protocol_uuid':current,'epoch_uuid':identity,'included':True,
+ 'tags':[f'dataset:{p}',*[f'dataset-tag:{i}' for i in range(4)]],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':service._fingerprints[identity]})
+ if len(batch)==500:store.Curation.insert(batch);batch=[]
+ if batch:store.Curation.insert(batch);batch=[]
+ report['seed_seconds']=time.perf_counter()-seed
+ checkpoint=time.perf_counter();snapshot.save(project_root,dj.conn(),service=service);report['seed_checkpoint_seconds']=time.perf_counter()-checkpoint
+ log(json.dumps({'stage':'timed_operations','epochs':args.epochs,'seed_seconds':report['seed_seconds']}))
+ client=app.test_client();base=f'/api/protocols/{protocol}';headers={'X-Workspace-Request':'1','Origin':'http://localhost:8766'}
+
+
+ import inspect
+ import threading
+ app_db_lock=inspect.getclosurevars(app.view_functions['annotation_batch_read']).nonlocals['db_lock']
+ report['native_lookup_startup_preparation']=copy.deepcopy(getattr(service,'annotation_preparation',None))
+ first=ids[:10];second=ids[100:110];selected=first+second;cell=service.rows[first[0]]['cell_uuid']
+ children=tuple(key for key in ids if service.rows[key]['cell_uuid']==cell)
+ assert len(children)==100 and service.rows[second[0]]['cell_uuid']!=cell
+ epoch_marker='sequence-selected';cell_marker='sequence-cell'
+ epoch_predicate={'all':[rule(epoch_marker,'epoch')]}
+ cell_predicate={'all':[rule(cell_marker,'cell')]}
+ hierarchy={'all':[rule(cell_marker,'cell'),rule(epoch_marker,'epoch')]}
+ model={(kind,key,p):set() for kind,keys in [('epoch',selected),('cell',(cell,))] for key in keys for p in range(3)}
+ revisions={key:1 for key in model}
+ report['selected_epoch_uuids']={'first10':first,'second10':second,'cell':cell,'cell_children':children}
+ report['backup_receipts']=[]
+ def sql_status():
+ with app_db_lock:return {key:int(value) for key,value in dj.conn().query("SHOW SESSION STATUS WHERE Variable_name IN ('Bytes_sent','Questions')").fetchall()}
+ def request(method,path,*,query=None,body=None):
+ response=client.get(path,query_string=query) if method=='GET' else client.post(path,json=body,headers=headers)
+ value=response.get_json()
+ if response.status_code!=200:raise AssertionError((method,path,response.status_code,value))
+ return value
+ def measure(name,callback):
+ before=sql_status();began=time.perf_counter();value=callback();elapsed=time.perf_counter()-began;after=sql_status()
+ samples.setdefault(name,[]).append(elapsed);sql_samples.setdefault(name,[]).append({key:after[key]-before[key] for key in before})
+ return value,elapsed
+ def wanted_tags(kind,key,p):
+ ordinal=ordinals[key] if kind=='epoch' else cell_ordinals[key]
+ return sorted([*tags(ordinal,p,cell=kind=='cell'),*model.get((kind,key,p),())])
+ def validate_target(row,kind,key):
+ assert row['target_uuid']==key and row['target_kind']==kind
+ assert row['revisions']=={author:revisions.get((kind,key,p),1) for p,author in enumerate(authors)}
+ assert sorted((chip['tag'],chip['profile_uuid']) for chip in row['tags'])==sorted((tag,author) for p,author in enumerate(authors) for tag in wanted_tags(kind,key,p))
+ def preflight(label,keys,p,*,timed=True):
+ call=lambda:request('POST','/api/annotations/read',body={'target_kind':'epoch','target_uuids':list(keys)})
+ value,_=measure(label,call) if timed else (call(),0)
+ assert set(value['targets'])==set(keys)
+ for key,row in value['targets'].items():validate_target(row,'epoch',key)
+ return {key:value['targets'][key]['revisions'][authors[p]] for key in keys}
+ def change(label,kind,keys,p,marker,add,expected=None,*,timed=True):
+ body={'target_kind':kind,'target_uuids':list(keys),'profile_uuid':authors[p],
+ 'tags_add':[marker] if add else [],'tags_remove':[] if add else [marker],
+ 'expected_revisions':expected or {key:revisions[(kind,key,p)] for key in keys}}
+ call=lambda:request('POST','/api/annotations',body=body)
+ if label=='profile_first10_save':
+ import re
+ connection=dj.conn();original_query=connection.query;sql_details=[];profile_owner=threading.get_ident()
+ def trace_post_sql(statement,*positional,**keywords):
+ began=time.perf_counter()
+ try:return original_query(statement,*positional,**keywords)
+ finally:
+ if threading.get_ident()==profile_owner:sql_details.append({'sql_shape':re.sub(r"'[^']*'","'?'",str(statement))[:400],'seconds':time.perf_counter()-began})
+ profiler=cProfile.Profile();connection.query=trace_post_sql
+ try:value=profiler.runcall(call)
+ finally:connection.query=original_query
+ statistics_profile=pstats.Stats(profiler)
+ functions=[]
+ for key,entry in sorted(statistics_profile.stats.items(),key=lambda item:item[1][3],reverse=True):
+ path=Path(key[0]);functions.append({'file':str(path.relative_to(root)) if path.is_absolute() and path.is_relative_to(root) else key[0],
+ 'line':key[1],'function':key[2],'calls':entry[1],'self_seconds':entry[2],'cumulative_seconds':entry[3]})
+ report['actual_post10_profile']={'functions':functions[:160],'sql':sql_details}
+ else:value,_=measure(label,call) if timed else (call(),0)
+ assert value['changed']==len(keys)
+ if 'persistence' in value:
+ assert value['persistence']['database']=='committed',value
+ report['backup_receipts'].append({'operation':label,'persistence':value['persistence']})
+ for key in keys:
+ revisions[(kind,key,p)]+=1
+ if add:model[(kind,key,p)].add(marker)
+ else:model[(kind,key,p)].remove(marker)
+ assert set(value['targets'])==set(keys)
+ for key,row in value['targets'].items():validate_target(row,kind,key)
+ # The oracle uses the application's actual SQL lock so the
+ # candidate's background mirror cannot race this connection.
+ with app_db_lock:
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':kind,'profile_uuid':authors[p]}&[{'target_uuid':key} for key in keys]).to_dicts()
+ assert {row['target_uuid'] for row in rows}==set(keys)
+ for row in rows:
+ key=row['target_uuid'];assert row['revision']==revisions[(kind,key,p)] and sorted(row['tags'])==wanted_tags(kind,key,p)
+ return value
+ def query(predicate,**extra):return {'tag_predicate':json.dumps(predicate,separators=(',',':')),**extra}
+ def page(label,predicate,wanted,*,offset=0,timed=True):
+ call=lambda:request('GET',base+'/epochs',query=query(predicate,limit=60,offset=offset,include_cells='true'))
+ value,elapsed=measure(label,call) if timed else (call(),0)
+ assert value['total']==len(wanted)
+ assert tuple(row['epoch_uuid'] for row in value['epochs'])==wanted[offset:offset+60]
+ counts={}
+ for key in wanted:
+ target_cell=service.rows[key]['cell_uuid'];counts[target_cell]=counts.get(target_cell,0)+1
+ assert {row['cell_uuid']:row['epochs'] for row in value['cells']}==counts
+ for row in value['epochs']:
+ key=row['epoch_uuid'];target_cell=service.rows[key]['cell_uuid']
+ for kind,field,target in [('epoch','epoch_tags',key),('cell','cell_tags',target_cell)]:
+ assert sorted((chip['tag'],chip['profile_uuid']) for chip in row['annotations'][field])==sorted((tag,author) for p,author in enumerate(authors) for tag in wanted_tags(kind,target,p))
+ assert row['curation']['revision']==1
+ assert sorted(row['curation']['tags'])==sorted(['dataset:0',*[f'dataset-tag:{j}' for j in range(4)]])
+ with app_db_lock:
+ exact,_=service._epoch_page_identities(protocol,query(predicate));assert tuple(exact)==wanted
+ return value,elapsed
+ def child_sql():
+ with app_db_lock:
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'}&[{'target_uuid':key} for key in children]).to_dicts()
+ return sorted((row['target_uuid'],row['profile_uuid'],row['revision'],tuple(sorted(row['tags'])),row['author_name'],str(row['updated_at'])) for row in rows)
+ # Native lookup is persistent. The first filter is requested
+ # only after the two timed writes, without warming a tag cache.
+ prepare_project_annotations=None
+ report['preparation_index_seconds']=0
+ report['lookup_mode']='persistent_native_sql_no_post_seed_tag_cache_preparation'
+ report['bundle_definitions']={
+ 'tag10_then10_filter20_http_total':['first10_revision_preflight','first10_save','second10_revision_preflight','second10_save','filter20'],
+ 'cell_tag_then_cell_filter_http_total':['cell_save','filter_cell100'],
+ 'first10_selected_action_http_total':['first10_revision_preflight','first10_save'],
+ 'second10_selected_action_http_total':['second10_revision_preflight','second10_save']}
+ for iteration in range(args.samples):
+ p=iteration%3
+ expected=preflight('first10_revision_preflight',first,p)
+ change('first10_save','epoch',first,p,epoch_marker,True,expected)
+ expected=preflight('second10_revision_preflight',second,p)
+ change('second10_save','epoch',second,p,epoch_marker,True,expected)
+ value,_=page('filter20',epoch_predicate,selected)
+ before_children=child_sql()
+ # The rendered page already carries cell revisions, as the
+ # direct-cell editor does; no selected-epoch preflight.
+ cell_expected={cell:value['epochs'][0]['annotations']['revisions']['cell'][authors[p]]}
+ change('cell_save','cell',(cell,),p,cell_marker,True,cell_expected)
+ page('filter_cell100',cell_predicate,children)
+ page('filter_cell100_last_page',cell_predicate,children,offset=40,timed=False)
+ page('filter_cell_and_epoch10',hierarchy,first)
+ page('oracle_cell_marker_not_direct',{'all':[rule(cell_marker,'epoch')]},(),timed=False)
+ assert child_sql()==before_children,'Cell add changed child annotation records'
+ expected=preflight('remove_first10_revision_preflight',first,p)
+ change('remove_first10_save','epoch',first,p,epoch_marker,False,expected)
+ page('filter_hierarchy_after_epoch_remove',hierarchy,())
+ page('filter_remaining10',epoch_predicate,second)
+ expected=preflight('remove_second10_revision_preflight',second,p)
+ change('remove_second10_save','epoch',second,p,epoch_marker,False,expected)
+ page('filter_after_all_epoch_removals',epoch_predicate,())
+ before_children=child_sql()
+ change('remove_cell_save','cell',(cell,),p,cell_marker,False)
+ page('filter_after_cell_removal',cell_predicate,())
+ assert child_sql()==before_children,'Cell removal changed child annotation records'
+ with app_db_lock:assert len(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'})==args.epochs*3
+ for label,parts in report['bundle_definitions'].items():samples.setdefault(label,[]).append(sum(samples[part][-1] for part in parts))
+ log(json.dumps({'stage':'sequence_cycle','epochs':args.epochs,'iteration':iteration+1}))
+ def flush_backup():
+ scheduler=app.extensions.get('backup_scheduler')
+ if scheduler is not None:
+ scheduler.flush();report['final_backup_status']=scheduler.status()
+ # Separate one-sample diagnostics; neither request warms the
+ # measured write/filter sequence above.
+ flush_backup()
+ for label in ('autocomplete_cold_diagnostic','autocomplete_warm_diagnostic'):
+ value,_=measure(label,lambda:request('GET','/api/annotation-tags',query={'q':'gate','limit':12}))
+ expected=[{'tag':f'gate:{p}','count':(args.epochs+p+1)//(p+2),
+ 'authors':[{'profile_uuid':authors[p],'display_name':author_names[p]}]} for p in range(3)]
+ assert value['tags']==expected and value['total']==3,(label,value,expected)
+ check('native_autocomplete_exact_counts_and_profile_authors',True)
+ if args.profile:
+ for name,keys in [('first10',first),('second10',second)]:
+ expected=preflight('profile_'+name+'_preflight',keys,0,timed=False)
+ change('profile_'+name+'_save','epoch',keys,0,epoch_marker,True,expected,timed=False)
+ flush_backup()
+ import re
+ connection=dj.conn();original_query=connection.query;owner=threading.get_ident();sql_calls=[];lookup_selects=[]
+ def traced_query(statement,*positional,**keywords):
+ began=time.perf_counter()
+ try:return original_query(statement,*positional,**keywords)
+ finally:
+ is_filter=threading.get_ident()==owner
+ sql_calls.append({'thread':'filter' if is_filter else 'background','sql_shape':re.sub(r"'[^']*'","'?'",str(statement))[:240],'seconds':time.perf_counter()-began})
+ if is_filter and str(statement).lstrip().upper().startswith('SELECT') and '`app_shared_tag_lookup`' in str(statement):
+ lookup_selects.append((str(statement),positional[0] if positional else keywords.get('args')))
+ profiler=cProfile.Profile();connection.query=traced_query
+ try:profiled=profiler.runcall(lambda:request('GET',base+'/epochs',query=query(epoch_predicate,limit=60,offset=0,include_cells='true')))
+ finally:connection.query=original_query
+ assert profiled['total']==20 and tuple(row['epoch_uuid'] for row in profiled['epochs'])==selected
+ stats=pstats.Stats(profiler);rows=[]
+ for key,value in sorted(stats.stats.items(),key=lambda item:item[1][3],reverse=True):
+ path=Path(key[0]);rows.append({'file':str(path.relative_to(root)) if path.is_absolute() and path.is_relative_to(root) else key[0],
+ 'line':key[1],'function':key[2],'primitive_calls':value[0],'calls':value[1],'self_seconds':value[2],'cumulative_seconds':value[3]})
+ report['filter20_cpu_profile']={'top_functions':rows[:100],'native_sql_calls':sql_calls,'limits':'Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap.'}
+ assert lookup_selects,'The fresh filter did not execute a native tag lookup SELECT'
+ plans=[]
+ with app_db_lock:
+ for statement,parameters in lookup_selects:
+ explanation=list(connection.query('EXPLAIN '+statement,parameters,as_dict=True,reconnect=False).fetchall())
+ assert any(row.get('key')=='by_tag' and row.get('type') in ('ref','range','const','eq_ref') for row in explanation),explanation
+ plans.append({'sql':statement,'explain':explanation})
+ report['native_lookup_query_plans']=plans
+ check('actual_filter_uses_persistent_by_tag_sql_index',True,queries=len(plans))
+ page('profile_filter20_verify',epoch_predicate,selected,timed=False)
+ from workspace_annotation_preparation import prepare_project_annotations as prepare_native_lookup
+ flush_backup();began=time.perf_counter()
+ with app_db_lock:
+ checkpoint=prepare_native_lookup(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'))
+ assert checkpoint['status']=='ready' and checkpoint['storage']=='native_sql',checkpoint
+ shared.native_tag_lookup=None
+ reopened=prepare_native_lookup(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'),reuse=True)
+ assert reopened['status']=='ready' and reopened['storage']=='native_sql' and reopened['reused'],reopened
+ report['same_server_native_lookup_reuse']={'checkpoint':checkpoint,'reopened':reopened,'seconds':time.perf_counter()-began,
+ 'scope':'Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart.'}
+ page('same_server_native_reuse_filter20',epoch_predicate,selected)
+ check('persistent_native_lookup_reused_without_rebuild',True)
+ if prepare_project_annotations is not None:
+ flush_backup();began=time.perf_counter()
+ with app_db_lock:
+ checkpoint=prepare_project_annotations(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'))
+ assert checkpoint['status']=='ready' and checkpoint['checkpoint']['saved'],checkpoint
+ previous_builds=shared._shared_tag_index.stats['full_builds']
+ shared._shared_tag_index.close()
+ reopened=prepare_project_annotations(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'),reuse=True)
+ assert reopened['status']=='ready' and reopened['checkpoint_restore']['restored'],reopened
+ after_builds=shared._shared_tag_index.stats['full_builds']
+ assert after_builds in (0,previous_builds),(previous_builds,after_builds)
+ report['same_server_checkpoint_reopen']={'seconds':time.perf_counter()-began,'checkpoint':checkpoint,'reopened':reopened,'full_builds_before':previous_builds,'full_builds_after':after_builds,'scope':'Shared derived index closed and reopened within the same native server; not a native process restart.'}
+ page('same_server_reopened_filter20',epoch_predicate,selected)
+ check('same_server_checkpoint_restores_exact_twenty_without_rebuild',True)
+ for name,keys in [('first10',first),('second10',second)]:
+ change('profile_remove_'+name,'epoch',keys,0,epoch_marker,False,timed=False)
+ # Untimed exactness checks run after the measured workflow.
+ unicode_oracles=[
+ ('case_upper',{'all':[rule('QC','epoch')]},lambda i:i%2==0),
+ ('case_lower',{'all':[rule('qc','epoch')]},lambda i:i%2==1),
+ ('unicode_nfc',{'all':[rule('é','epoch')]},lambda i:i%3==0),
+ ('unicode_nfd',{'all':[rule('e\u0301','epoch')]},lambda i:i%3!=0),
+ ('or',{'any':[rule('QC','epoch'),rule('é','epoch')]},lambda i:i%2==0 or i%3==0),
+ ('not_with_inheritance',{'all':[rule('cell-common','cell'),{'not':rule('QC','epoch')}]},lambda i:i%2==1),
+ ('inherited_excluded_from_direct',{'all':[rule('cell-common','epoch')]},lambda i:False)]
+ for label,predicate,accept in unicode_oracles:
+ wanted=tuple(identity for i,identity in enumerate(ids) if accept(i))
+ page('oracle_'+label,predicate,wanted,timed=False)
+ check('exact_native_'+label,True,count=len(wanted))
+ report['sqlite_tag_cache_calls']=dict(sqlite_tag_calls)
+ check('no_sqlite_tag_membership_or_refresh_used',not any(sqlite_tag_calls.values()),calls=sqlite_tag_calls)
+ flush_backup()
+ check('canonical_final_markers_removed',not any(model.values()))
+ report['workflow_peak_rss_before_durable_oracle_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ began=time.perf_counter();state=snapshot.load(project_root/'app-state.json');report['durable_snapshot_verify_seconds']=time.perf_counter()-began
+ saved={(row['target_kind'],row['target_uuid'],row['profile_uuid']):row for row in state['tables']['shared_annotation']}
+ for (kind,key,p),revision in revisions.items():
+ row=saved[(kind,key,authors[p])];assert row['revision']==revision and sorted(row['tags'])==wanted_tags(kind,key,p)
+ check('flushed_recovery_mirror_exact_revisions_and_removal',True,records=len(revisions))
+ check('one_canonical_cell_record_no_child_copies',sum(row['target_kind']=='epoch' for row in saved.values())==args.epochs*3,child_epochs=len(children))
+ check('independent_protocol_curation_untouched',len(state['tables']['curation'])==args.epochs*2 and all(row['revision']==1 for row in state['tables']['curation']))
+ loaded={name:str(Path(module.__file__).resolve()) for name,module in sys.modules.items() if (name.startswith('workspace_') or name=='recording_workspace') and getattr(module,'__file__',None)}
+ check('all_product_modules_loaded_from_selected_source',all(Path(path).is_relative_to(root) for path in loaded.values()));report['loaded_product_modules']=loaded
+ finally:
+ if app is not None:
+ scheduler=app.extensions.get('backup_scheduler')
+ if scheduler is not None:
+ scheduler.flush()
+ close=getattr(scheduler,'close',None)
+ if close is not None:close(flush=False)
+ shared=app.extensions.get('shared_annotations');derived=getattr(shared,'_shared_tag_index',None)
+ if derived is not None:derived.close()
+ lock=app.extensions.get('app_state_session_lock')
+ if lock is not None:lock.close()
+ if index is not None:index.close()
+ if dj is not None:dj.conn().close()
+ if project_root is not None:closed=native.stop_native_database(project_root)
+ report['owned_native_runtime_stopped']=closed
+ except Exception as error:
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};log(traceback.format_exc())
+ report['operations']={name:{**percentiles(values),'sql_session_deltas':sql_samples.get(name,[])} for name,values in samples.items()}
+ report['source_inventory_after']=source_inventory(root);report['source_unchanged']=report['source_inventory_before']==report['source_inventory_after']
+ report['harness_unchanged']=digest(__file__)==report['harness_sha256']
+ report['seconds']=time.perf_counter()-started;report['finished_at']=dt.datetime.now(dt.timezone.utc).isoformat();report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['passed']=not report.get('error') and closed and report['source_unchanged'] and report['harness_unchanged'] and all(item['passed'] for item in report['checks'])
+ args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');log(json.dumps({'stage':'done','epochs':args.epochs,'passed':report['passed'],'seconds':report['seconds'],'output':str(args.output)}))
+ return int(not report['passed'])
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_protocol_state.py b/docs/dev/scale-audit-2026-09-29/benchmark_protocol_state.py
new file mode 100644
index 0000000..c01183b
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_protocol_state.py
@@ -0,0 +1,130 @@
+"""Actual Flask100k page before/after exact revision reuse, disposable SQL doubles."""
+import argparse
+import copy
+import json
+import sys
+from pathlib import Path
+import tempfile
+from unittest.mock import patch
+
+from benchmark_service_scale import fixture,Details
+from benchmark_epoch_pages import measure
+from workspace_disk_index import DiskMetadataIndex
+from workspace_service import WorkspaceService
+from workspace_protocol_state import _exact_equal
+from test_workspace_api import WorkspaceAPITests
+
+
+def run(count):
+ api=WorkspaceAPITests();api.setUp()
+ try:
+ service,protocol=fixture(count)
+ service.project['project_uuid']=api.service.project['project_uuid']
+ with tempfile.TemporaryDirectory(prefix='rieke-state-benchmark-') as folder:
+ index=DiskMetadataIndex.build(Path(folder)/'index.sqlite',service.rows,Details(service.rows),
+ service.sources,'state-benchmark',service.project['project_uuid'])
+ target=api.service
+ target.__class__=WorkspaceService
+ for name in ('rows','cells','sources','_fingerprints'):
+ setattr(target,name,getattr(service,name))
+ target.disk_index,target.details=index,index.details
+ target.protocols={target.protocol_id:service.protocols[protocol]}
+ target.protocols[target.protocol_id]['result']['protocol_uuid']=target.protocol_id
+ reader=api.app.extensions['protocol_state_reader']
+ path=api.base+'/epochs?limit=60'
+ target.epoch_page(target.protocol_id,limit=60) # Both HTTP cases use the optimized page service.
+ def request():
+ response=api.client.get(path)
+ assert response.status_code==200,response.get_json()
+ return response.get_json()
+ def uncached(protocol,ids):
+ result,states,revision=reader.full_state(protocol)
+ return {key:states[key] for key in ids},revision,result.get('dataset_binding',{}).get('version',0)
+ with patch.object(reader,'read_selected',side_effect=uncached):
+ baseline,old=measure(request)
+ cold,new=measure(request,1)
+ assert old==new,'Cold cached path must preserve the exact legacy JSON response'
+ assert target.protocol_id in reader.cache,'100k scope must fit the admission budget'
+ with patch.object(reader,'full_state',side_effect=AssertionError('No full-state hydration on warm read')):
+ warm,again=measure(request)
+ calls=[]
+ def profile(frame,event,arg):
+ if event=='call' and frame.f_code is api.store._state.__code__:calls.append(1)
+ sys.setprofile(profile)
+ try:structural=request()
+ finally:sys.setprofile(None)
+ assert len(calls)==60,len(calls)
+ assert again==old==structural
+ first=next(iter(target.rows))
+ api.store.update(target.protocol_id,[first],{'tags_add':['after-save']},{first:0},
+ {first:target._fingerprints[first]},'fixture')
+ changed=request()
+ _,current,revision=reader.full_state(target.protocol_id)
+ assert changed['query_revision']==revision!=old['query_revision']
+ assert changed['epochs'][0]['curation']['tags']==['after-save']
+ sparse,_=measure(request)
+ # Same-version direct SQL edits cannot hide behind a revision counter.
+ api.curation.rows[0]['tags']=['same-version-change']
+ edited=request()
+ assert edited['query_revision']==reader.full_state(target.protocol_id)[2]!=revision
+ previous=edited['query_revision']
+ target._fingerprints[first]='c'*64
+ metadata=request()
+ assert metadata['query_revision']==reader.full_state(target.protocol_id)[2]!=previous
+ previous=metadata['query_revision']
+ api.curation.rows.clear()
+ deleted=request()
+ assert deleted['query_revision']==reader.full_state(target.protocol_id)[2]!=previous
+ proof=reader._metadata(target.protocol_id)
+ retained=reader.cache[target.protocol_id]['metadata']
+ deepcopy_cost,_=measure(lambda:copy.deepcopy(proof))
+ comparison_cost,equal=measure(lambda:reader._metadata_equal(proof,retained))
+ assert equal
+ query_comparison_cost,equal=measure(lambda:_exact_equal(proof['query'],retained['query']))
+ assert equal
+ fingerprint_cost,equal=measure(lambda:proof['fingerprints']==retained['fingerprints'])
+ assert equal
+ # Independently exercise a pinned100k dataset: header-only polls
+ # must preserve the immutable recipe and exact legacy revision.
+ preview=target.explore_preview({'all':[]},'cell',include_tree=False,include_catalog_summary=False)
+ saved=api.explorer_history.create(preview,target.sources,'catalog.json','fixture')
+ api.explorer_history.bind(saved['revision_uuid'],target.protocol_id,0,'fixture',{},count)
+ target.epoch_page(target.protocol_id,limit=60)
+ reader.cache.clear()
+ with patch.object(reader,'read_selected',side_effect=uncached):
+ bound_baseline,bound_old=measure(request)
+ bound_cold,bound_new=measure(request,1)
+ assert bound_old==bound_new
+ assert target.protocol_id in reader.cache
+ with patch.object(reader,'full_state',side_effect=AssertionError('Bound warm read must not hydrate recipe')):
+ bound_warm,bound_new=measure(request)
+ assert bound_old==bound_new
+ bound_proof=reader._metadata(target.protocol_id)
+ bound_cell_cost,_=measure(lambda:reader._bound_cells(reader.cache[target.protocol_id]['members'],bound_proof))
+ return {'epochs':count,'response_epochs':60,
+ 'scope':'Actual production Flask GET route and JSON serialization with a real sealed SQLite metadata index. SQL catalog is a disposable transactional double; initial saved-curation/shared-tag rows are empty, then one saved curation row is added. Excludes real MySQL/network latency, dense tags, browser paint and physical waveform I/O.',
+ 'legacy_full_revision_http':baseline,'verified_cache_cold_http':cold,
+ 'verified_cache_warm_http':warm,'verified_cache_one_saved_row_warm_http':sparse,
+ 'cold_proof_deepcopy':deepcopy_cost,'warm_metadata_equality':comparison_cost,
+ 'warm_typed_query_comparison':query_comparison_cost,'warm_fingerprint_dictionary_equality':fingerprint_cost,
+ 'bound_protocol':{'legacy_full_revision_http':bound_baseline,'verified_cache_cold_http':bound_cold,
+ 'verified_cache_warm_http':bound_warm,'warm_cell_ownership_check':bound_cell_cost,
+ 'exact_legacy_response_parity':True},
+ 'warm_hydrated_curation_states':len(calls),
+ 'exact_legacy_response_parity':True,'mutation_checks':{
+ 'saved_curation_write':True,'same_version_sql_tag_edit':True,'in_place_metadata_fingerprint_change':True,
+ 'saved_curation_deletion':True,'pinned_binding':True},
+ 'cache_scopes':len(reader.cache),'estimated_revision_cache_bytes':sum(item['size'] for item in reader.cache.values()),
+ 'remaining_linear_checks':'Full fingerprint dictionary equality and typed protocol-metadata comparison occur on each hit. Bound protocols additionally check current cell ownership. Current sparse saved curation and exact shared-annotation snapshot are read every request.'}
+ finally:
+ api.doCleanups()
+
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs',type=int,default=100000)
+ parser.add_argument('--output',type=Path,required=True)
+ args=parser.parse_args()
+ report=run(args.epochs)
+ args.output.write_text(json.dumps(report,indent=2)+'\n')
+ print(json.dumps(report,indent=2),flush=True)
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_scientist_tag_workflow.py b/docs/dev/scale-audit-2026-09-29/benchmark_scientist_tag_workflow.py
new file mode 100644
index 0000000..fef555e
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_scientist_tag_workflow.py
@@ -0,0 +1,255 @@
+"""Native scientist tag/filter workflow against an explicitly selected source tree.
+
+Every project/database is disposable. Acquisition metadata is synthetic; actual
+MySQL, triggers, Flask routes, derived indexes and synchronous durable save hooks
+are used. HTTP times include Flask serialization/client JSON decoding, exclude
+oracle work, and do not model browser concurrency/rendering. Sequential bundle
+times are sums of their separately measured requests. Profiles are separate.
+"""
+import argparse
+import copy
+import cProfile
+import datetime as dt
+import hashlib
+import json
+import math
+import os
+from pathlib import Path
+import pstats
+import resource
+import statistics
+import sys
+import tempfile
+import time
+import traceback
+
+
+def digest(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
+def percentiles(values):
+ ordered=sorted(values)
+ return {'samples_seconds':values,'p50_seconds':statistics.median(values),'p95_seconds':ordered[math.ceil(.95*len(ordered))-1],
+ 'p99_seconds':ordered[math.ceil(.99*len(ordered))-1],'maximum_seconds':max(values),'quantile_method':'nearest_rank'}
+def rule(tag,scope='effective'):return {'field':'annotations/'+scope+'/tags','operator':'contains','value':tag}
+def tags(ordinal,profile,*,cell=False):
+ if cell:return ['cell-common',f'cell-bucket:{ordinal%5}',f'cell-author:{profile}','Inherited' if profile==0 else 'inherited',f'cell-type:{ordinal%2}']
+ return ['all',f'gate:{profile}' if ordinal%(profile+2)==0 else f'rest:{profile}','QC' if ordinal%2==0 else 'qc',
+ 'é' if ordinal%3==0 else 'e\u0301',f'bucket:{ordinal%17}']
+def source_inventory(root):
+ return {str(path.relative_to(root)):digest(path) for path in sorted((root/'python').rglob('*.py'))}
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--source-root',type=Path,required=True)
+ ap.add_argument('--mysql-runtime-root',type=Path,required=True);ap.add_argument('--epochs',type=int,required=True)
+ ap.add_argument('--samples',type=int,default=20);ap.add_argument('--edit-count',type=int,default=10)
+ ap.add_argument('--output',type=Path,required=True);ap.add_argument('--profile',action='store_true');args=ap.parse_args()
+ args.output=args.output.resolve();root=args.source_root.resolve(strict=True)
+ if args.output.exists():ap.error('Choose an unused output receipt')
+ if args.epochs<12 or args.samples<1:ap.error('Use at least12 epochs and one sample')
+ # Product and fixture imports must come from the selected immutable tree.
+ sys.path[:0]=[str(root/'python'),str(root/'docs/dev/scale-audit-2026-09-29')]
+ os.chdir(root)
+ from benchmark_service_scale import fixture,Details,uid
+ import benchmark_service_scale
+ from recording_workspace import connect,workspace_tables
+ from workspace_projects import create_project
+ import workspace_native_mysql as native
+ from workspace_mysql_runtime import _probe,runtime_spec
+ from workspace_disk_index import DiskMetadataIndex
+ from workspace_api import create_app
+ import workspace_state_snapshot as snapshot
+ runtime=_probe(args.mysql_runtime_root.resolve(strict=True),runtime_spec(root)[0]['mysql_version'])
+ def runtime_binary(name='mysqld'):
+ if name not in {'mysqld','mysql','mysqldump'}:raise ValueError('Unsupported native executable')
+ return Path(runtime[name])
+ native.native_binary=runtime_binary
+ report={'scope':__doc__,'epochs':args.epochs,'profiles':3,'tags_per_annotation':5,'protocols':2,'samples':args.samples,
+ 'source_root':str(root),'source_inventory_before':source_inventory(root),'fixture_module':str(Path(benchmark_service_scale.__file__).resolve()),
+ 'harness_sha256':digest(__file__),'started_at':dt.datetime.now(dt.timezone.utc).isoformat(),'release_ready':False,'operations':{},'checks':[]}
+ samples={};sql_samples={};app=None;dj=None;project_root=None;index=None;closed=False
+ def check(name,condition,**detail):
+ report['checks'].append({'name':name,'passed':bool(condition),**detail})
+ if not condition:raise AssertionError(name)
+ def log(message):print(message,flush=True)
+ started=time.perf_counter()
+ try:
+ with tempfile.TemporaryDirectory(prefix='rieke-scientist-tag-workflow-') as directory:
+ project_root=Path(create_project(Path(directory)/'projects','Disposable scientist tag workflow')['path'])
+ try:
+ native.ensure_native_database(project_root);dj=connect({'kind':'native-project'},project_dir=project_root)
+ service,protocol=fixture(args.epochs);service.project_dir=project_root
+ service.project=json.loads((project_root/'project.json').read_text());service.config=json.loads((project_root/'catalog.json').read_text());service.dj=dj
+ other=uid(-3);service.protocols[other]=copy.deepcopy(service.protocols[protocol])
+ service.protocols[other]['definition']['protocol_uuid']=other;service.protocols[other]['result']['protocol_uuid']=other
+ for value in service.protocols.values():
+ value['definition']['project_uuid']=service.project['project_uuid']
+ value['definition']['query']={'version':1,'all':[{'field':'EpochBlock.protocol_name','operator':'eq','value':'Synthetic'}]}
+ index=DiskMetadataIndex.build(project_root/'cache'/'metadata.sqlite',service.rows,Details(service.rows),service.sources,'tag-workflow-fixture-v1',service.project['project_uuid'])
+ service.disk_index=index;service.details=index.details
+ Project,_,_,_=workspace_tables(dj);project=service.project['project_uuid']
+ Project.insert1({'project_uuid':project,'name':service.project['name'],'directory':str(project_root)})
+ app=create_app(project_root,root/'.rieke-runtime/retinanalysis',service=service)
+ assert service._recovery_tracker.ready,service._recovery_tracker.reason
+ store=app.extensions['curation_store'];shared=app.extensions['shared_annotations'];authors=[uid(-100-i) for i in range(3)]
+ author_names=['Same Scientist','Same Scientist','Scientist C'];stamp=dt.datetime(2026,9,29,12)
+ ids=tuple(service.rows);ordinals={identity:i for i,identity in enumerate(ids)};cell_ordinals={identity:i for i,identity in enumerate(service.cells)}
+ report['mysql_version']=dj.conn().query('SELECT VERSION()').fetchone()[0]
+ seed=time.perf_counter();log(json.dumps({'stage':'seed','epochs':args.epochs,'source_root':str(root)}))
+ with dj.conn().transaction:
+ shared.Profile.insert([{'project_uuid':project,'profile_uuid':author,'display_name':author_names[p],'created_at':stamp,'created_by':'workflow oracle'} for p,author in enumerate(authors)])
+ batch=[]
+ for kind,identities in [('epoch',ids),('cell',tuple(service.cells))]:
+ for ordinal,identity in enumerate(identities):
+ for p,author in enumerate(authors):
+ batch.append({'project_uuid':project,'target_kind':kind,'target_uuid':identity,'profile_uuid':author,
+ 'tags':tags(ordinal,p,cell=kind=='cell'),'author_name':author_names[p],'revision':1,'updated_at':stamp})
+ if len(batch)==500:shared.Annotation.insert(batch);batch=[]
+ if batch:shared.Annotation.insert(batch);batch=[]
+ for p,current in enumerate((protocol,other)):
+ for identity in ids:
+ batch.append({'project_uuid':project,'protocol_uuid':current,'epoch_uuid':identity,'included':True,
+ 'tags':[f'dataset:{p}',*[f'dataset-tag:{i}' for i in range(4)]],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':service._fingerprints[identity]})
+ if len(batch)==500:store.Curation.insert(batch);batch=[]
+ if batch:store.Curation.insert(batch);batch=[]
+ report['seed_seconds']=time.perf_counter()-seed
+ checkpoint=time.perf_counter();snapshot.save(project_root,dj.conn(),service=service);report['seed_checkpoint_seconds']=time.perf_counter()-checkpoint
+ log(json.dumps({'stage':'timed_operations','epochs':args.epochs,'seed_seconds':report['seed_seconds']}))
+ client=app.test_client();base=f'/api/protocols/{protocol}';headers={'X-Workspace-Request':'1','Origin':'http://localhost:8766'}
+ predicates={
+ 'A':{'all':[rule('gate:0','epoch'),rule('gate:1','epoch'),rule('cell-common','cell'),{'not':rule('gate:2')}]},
+ 'B':{'any':[rule('qc','epoch'),rule('cell-bucket:3','cell'),rule('é','epoch')]},
+ 'C':{'all':[{'not':rule('QC')},rule('e\u0301','epoch'),rule('cell-common','cell')]},
+ 'direct_inheritance_excluded':{'all':[rule('cell-common','epoch')]}}
+ expected={
+ 'A':tuple(ids[i] for i in range(args.epochs) if i%2==0 and i%3==0 and i%4!=0),
+ 'B':tuple(ids[i] for i in range(args.epochs) if i%2==1 or (i//100)%5==3 or i%3==0),
+ 'C':tuple(ids[i] for i in range(args.epochs) if i%2==1 and i%3!=0),
+ 'direct_inheritance_excluded':()}
+ edited=expected['A'][:args.edit_count];marker='workflow-selected';dataset_marker='workflow-dataset'
+ predicates['edited']={'all':[rule(marker,'epoch'),*predicates['A']['all']]};expected['edited']=()
+ report['filter_contract']={name:{'predicate':predicate,'expected_count':len(expected[name]),'membership_sha256':hashlib.sha256(''.join(expected[name]).encode()).hexdigest()} for name,predicate in predicates.items()}
+ report['edited_epoch_uuids']=edited;shared_revisions={(key,p):1 for key in edited for p in range(3)};curation_revisions={key:1 for key in edited}
+ shared_active=None;curation_active=False
+ def sql_status():return {key:int(value) for key,value in dj.conn().query("SHOW SESSION STATUS WHERE Variable_name IN ('Rows_sent','Bytes_sent','Questions')").fetchall()}
+ def request(method,path,*,query=None,body=None):
+ response=client.get(path,query_string=query) if method=='GET' else client.post(path,json=body,headers=headers)
+ value=response.get_json()
+ if response.status_code!=200:raise AssertionError((method,path,response.status_code,value))
+ return value
+ def measure(name,callback):
+ before=sql_status();began=time.perf_counter();value=callback();elapsed=time.perf_counter()-began;after=sql_status()
+ samples.setdefault(name,[]).append(elapsed);sql_samples.setdefault(name,[]).append({key:after[key]-before[key] for key in before})
+ return value,elapsed
+ def query(name,**extra):return {'tag_predicate':json.dumps(predicates[name],ensure_ascii=False,separators=(',',':')),**extra}
+ def validate_page(value,name,offset=0):
+ wanted=expected[name];shown=wanted[offset:offset+60]
+ assert value['total']==len(wanted) and tuple(row['epoch_uuid'] for row in value['epochs'])==shown,(name,value['total'],len(wanted))
+ for row in value['epochs']:
+ identity=row['epoch_uuid'];i=ordinals[identity];wanted_chips=[]
+ for p,author in enumerate(authors):
+ values=tags(i,p)+([marker] if shared_active==p and identity in edited else [])
+ wanted_chips.extend((tag,author) for tag in values)
+ actual=sorted((item['tag'],item['profile_uuid']) for item in row['annotations']['epoch_tags'])
+ assert actual==sorted(wanted_chips),(name,identity,'author tag chips')
+ cell_i=cell_ordinals[service.rows[identity]['cell_uuid']]
+ wanted_inherited=sorted((tag,author) for p,author in enumerate(authors) for tag in tags(cell_i,p,cell=True))
+ assert sorted((item['tag'],item['profile_uuid']) for item in row['annotations']['cell_tags'])==wanted_inherited
+ assert row['curation']['revision']==curation_revisions.get(identity,1)
+ assert sorted(row['curation']['tags'])==sorted(['dataset:0',*[f'dataset-tag:{j}' for j in range(4)]]+([dataset_marker] if curation_active and identity in edited else []))
+ def page(label,name,offset=0):
+ value,elapsed=measure(label,lambda:request('GET',base+'/epochs',query=query(name,limit=60,offset=offset)))
+ validate_page(value,name,offset);return value,elapsed
+ def summary(label,name):
+ value,elapsed=measure(label,lambda:request('GET',base,query=query(name)))
+ assert value['counts']['epochs']==len(expected[name]),(name,value['counts'],len(expected[name]));return value,elapsed
+ def bundle(label,name):
+ _,a=summary(label+'_summary',name);value,b=page(label+'_page',name);samples.setdefault(label+'_server_bundle',[]).append(a+b);return value
+ def suggest(label,prefix):
+ value,elapsed=measure(label,lambda:request('GET','/api/annotation-tags',query={'q':prefix,'limit':12}))
+ if prefix=='gate':
+ wanted=[{'tag':f'gate:{p}','count':(args.epochs+p+1)//(p+2),'authors':[{'profile_uuid':authors[p],'display_name':author_names[p]}]} for p in range(3)]
+ else:wanted=[] if shared_active is None else [{'tag':marker,'count':len(edited),'authors':[{'profile_uuid':authors[shared_active],'display_name':author_names[shared_active]}]}]
+ assert value['tags']==wanted and value['total']==len(wanted),(prefix,value,wanted)
+ return value
+ for name in predicates:
+ if name=='edited':continue
+ page('cold_filter_'+name,name)
+ last=max(0,len(expected[name])-60);page('oracle_boundary_'+name,name,last)
+ exact,_=service._epoch_page_identities(protocol,query(name))
+ check('full_membership_'+name,tuple(exact)==expected[name],count=len(exact))
+ suggest('suggestions_cold','gate')
+ for _ in range(args.samples):suggest('suggestions_warm','gate')
+ def shared_change(add,p,*,label=None):
+ nonlocal shared_active
+ body={'target_kind':'epoch','target_uuids':list(edited),'profile_uuid':authors[p],
+ 'tags_add' if add else 'tags_remove':[marker],'expected_revisions':{key:shared_revisions[(key,p)] for key in edited}}
+ call=lambda:request('POST','/api/annotations',body=body)
+ value=measure(label,call)[0] if label else call()
+ assert value['changed']==len(edited)
+ for key in edited:shared_revisions[(key,p)]+=1
+ shared_active=p if add else None;expected['edited']=edited if add else ()
+ def curation_change(add,current_page,*,label):
+ nonlocal curation_active
+ body={'epoch_uuids':list(edited),'query_revision':current_page['query_revision'],'expected_binding_version':0,
+ 'expected_revisions':dict(curation_revisions),'changes':{'tags_add' if add else 'tags_remove':[dataset_marker]}}
+ measure(label,lambda:request('POST',base+'/curation',body=body))
+ for key in edited:curation_revisions[key]+=1
+ curation_active=add
+ for iteration in range(args.samples):
+ before=time.perf_counter();bundle('filter_A','A');bundle('filter_B','B');current=bundle('return_A','A')
+ samples.setdefault('A_B_A_sequential_server_bundle',[]).append(sum(samples[key][-1] for key in ('filter_A_server_bundle','filter_B_server_bundle','return_A_server_bundle')))
+ p=iteration%3
+ shared_change(True,p,label='shared_add_durable_save');current=bundle('first_after_shared_add','A')
+ page('added_tag_filter','edited');suggest('suggestions_after_shared_add','workflow')
+ shared_change(False,p,label='shared_remove_durable_save');current=bundle('first_after_shared_remove','A')
+ page('removed_tag_filter','edited');suggest('suggestions_after_shared_remove','workflow')
+ curation_change(True,current,label='dataset_add_durable_save');current=bundle('first_after_dataset_add','A')
+ curation_change(False,current,label='dataset_remove_durable_save');bundle('first_after_dataset_remove','A')
+ samples.setdefault('complete_workflow_wall_with_oracles',[]).append(time.perf_counter()-before)
+ log(json.dumps({'stage':'cycle','epochs':args.epochs,'iteration':iteration+1,'seconds':samples['complete_workflow_wall_with_oracles'][-1]}))
+ def profile(name,callback):
+ profiler=cProfile.Profile();value=profiler.runcall(callback);stats=pstats.Stats(profiler)
+ report.setdefault('cpu_profiles',{})[name]=[{'file':str(Path(key[0]).relative_to(root)) if Path(key[0]).is_absolute() and Path(key[0]).is_relative_to(root) else key[0],
+ 'line':key[1],'function':key[2],'primitive_calls':value[0],'calls':value[1],'self_seconds':value[2],'cumulative_seconds':value[3]}
+ for key,value in sorted(stats.stats.items(),key=lambda item:item[1][3],reverse=True)[:40]]
+ return value
+ if args.profile:
+ value=profile('warm_multitag_page',lambda:request('GET',base+'/epochs',query=query('A',limit=60)));validate_page(value,'A')
+ def post_save_loop():
+ shared_change(True,0)
+ value=request('GET',base,query=query('A'));assert value['counts']['epochs']==len(expected['A'])
+ result=request('GET',base+'/epochs',query=query('A',limit=60));validate_page(result,'A')
+ request('GET','/api/annotation-tags',query={'q':'workflow','limit':12})
+ profile('post_shared_save_summary_page_suggestions',post_save_loop);shared_change(False,0)
+ check('final_exact_membership_restored',all(tuple(service._epoch_page_identities(protocol,query(name))[0])==expected[name] for name in predicates))
+ other_page=request('GET',f'/api/protocols/{other}/epochs',query={'limit':60})
+ check('other_protocol_curation_untouched',all(row['curation']['revision']==1 and 'dataset:1' in row['curation']['tags'] and dataset_marker not in row['curation']['tags'] for row in other_page['epochs']))
+ began=time.perf_counter();state=snapshot.load(project_root/'app-state.json');report['durable_snapshot_verify_seconds']=time.perf_counter()-began
+ saved={(row['target_uuid'],row['profile_uuid']):row for row in state['tables']['shared_annotation'] if row['target_kind']=='epoch' and row['target_uuid'] in edited}
+ check('durable_mirror_exact_tag_removal_and_author_revisions',all(sorted(saved[(key,authors[p])]['tags'])==sorted(tags(ordinals[key],p)) and saved[(key,authors[p])]['revision']==shared_revisions[(key,p)] for key in edited for p in range(3)))
+ check('durable_mirror_keeps_two_protocol_scopes',len(state['tables']['curation'])==args.epochs*2)
+ report['shared_index_stats']=getattr(getattr(shared,'_shared_tag_index',None),'stats',None)
+ loaded={name:str(Path(module.__file__).resolve()) for name,module in sys.modules.items() if (name.startswith('workspace_') or name=='recording_workspace') and getattr(module,'__file__',None)}
+ check('all_product_modules_loaded_from_selected_source',all(Path(path).is_relative_to(root) for path in loaded.values()))
+ report['loaded_product_modules']=loaded
+ finally:
+ if app is not None:
+ shared=app.extensions.get('shared_annotations');derived=getattr(shared,'_shared_tag_index',None)
+ if derived is not None:derived.close()
+ lock=app.extensions.get('app_state_session_lock')
+ if lock is not None:lock.close()
+ if index is not None:index.close()
+ if dj is not None:dj.conn().close()
+ if project_root is not None:closed=native.stop_native_database(project_root)
+ report['owned_native_runtime_stopped']=closed
+ except Exception as error:
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};log(traceback.format_exc())
+ report['operations']={name:{**percentiles(values),'sql_session_deltas':sql_samples.get(name,[])} for name,values in samples.items()}
+ report['source_inventory_after']=source_inventory(root);report['source_unchanged']=report['source_inventory_before']==report['source_inventory_after']
+ report['seconds']=time.perf_counter()-started;report['finished_at']=dt.datetime.now(dt.timezone.utc).isoformat();report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['passed']=not report.get('error') and closed and report['source_unchanged'] and all(item['passed'] for item in report['checks'])
+ args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');log(json.dumps({'stage':'done','epochs':args.epochs,'passed':report['passed'],'seconds':report['seconds'],'output':str(args.output)}))
+ return int(not report['passed'])
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_scientist_tag_workflow_v2.py b/docs/dev/scale-audit-2026-09-29/benchmark_scientist_tag_workflow_v2.py
new file mode 100644
index 0000000..2c4bcf2
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_scientist_tag_workflow_v2.py
@@ -0,0 +1,329 @@
+"""Native scientist tag/filter workflow against an explicitly selected source tree.
+
+Every project/database is disposable. Acquisition metadata is synthetic; actual
+MySQL, triggers, Flask routes, derived indexes and synchronous durable save hooks
+are used. HTTP times include Flask serialization/client JSON decoding, exclude
+oracle work, and do not model browser concurrency/rendering. Sequential bundle
+times are sums of their separately measured requests. Profiles are separate.
+"""
+import argparse
+import copy
+import cProfile
+import datetime as dt
+import hashlib
+import json
+import math
+import os
+from pathlib import Path
+import pstats
+import resource
+import statistics
+import sys
+import tempfile
+import time
+import traceback
+
+
+def digest(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
+def percentiles(values):
+ ordered=sorted(values)
+ return {'samples_seconds':values,'p50_seconds':statistics.median(values),'p95_seconds':ordered[math.ceil(.95*len(ordered))-1],
+ 'p99_seconds':ordered[math.ceil(.99*len(ordered))-1],'maximum_seconds':max(values),'quantile_method':'nearest_rank'}
+def rule(tag,scope='effective'):return {'field':'annotations/'+scope+'/tags','operator':'contains','value':tag}
+def tags(ordinal,profile,*,cell=False):
+ if cell:return ['cell-common',f'cell-bucket:{ordinal%5}',f'cell-author:{profile}','Inherited' if profile==0 else 'inherited',f'cell-type:{ordinal%2}']
+ return ['all',f'gate:{profile}' if ordinal%(profile+2)==0 else f'rest:{profile}','QC' if ordinal%2==0 else 'qc',
+ 'é' if ordinal%3==0 else 'e\u0301',f'bucket:{ordinal%17}']
+def source_inventory(root):
+ return {str(path.relative_to(root)):digest(path) for path in sorted((root/'python').rglob('*.py'))}
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--source-root',type=Path,required=True)
+ ap.add_argument('--mysql-runtime-root',type=Path,required=True);ap.add_argument('--epochs',type=int,required=True)
+ ap.add_argument('--samples',type=int,default=20);ap.add_argument('--edit-count',type=int,default=10)
+ ap.add_argument('--output',type=Path,required=True);ap.add_argument('--profile',action='store_true')
+ ap.add_argument('--page-cells',action='store_true',help='Validate optional filtered cell counts and run the new page-first workflow')
+ args=ap.parse_args()
+ args.output=args.output.resolve();root=args.source_root.resolve(strict=True)
+ if args.output.exists():ap.error('Choose an unused output receipt')
+ if args.epochs<12 or args.samples<1:ap.error('Use at least12 epochs and one sample')
+ # Product and fixture imports must come from the selected immutable tree.
+ sys.path[:0]=[str(root/'python'),str(root/'docs/dev/scale-audit-2026-09-29')]
+ os.chdir(root)
+ from benchmark_service_scale import fixture,Details,uid
+ import benchmark_service_scale
+ from recording_workspace import connect,workspace_tables
+ from workspace_projects import create_project
+ import workspace_native_mysql as native
+ from workspace_mysql_runtime import _probe,runtime_spec
+ from workspace_disk_index import DiskMetadataIndex
+ from workspace_api import create_app
+ import workspace_state_snapshot as snapshot
+ runtime=_probe(args.mysql_runtime_root.resolve(strict=True),runtime_spec(root)[0]['mysql_version'])
+ def runtime_binary(name='mysqld'):
+ if name not in {'mysqld','mysql','mysqldump'}:raise ValueError('Unsupported native executable')
+ return Path(runtime[name])
+ native.native_binary=runtime_binary
+ report={'scope':__doc__,'epochs':args.epochs,'profiles':3,'tags_per_annotation':5,'protocols':2,'samples':args.samples,
+ 'source_root':str(root),'source_inventory_before':source_inventory(root),'fixture_module':str(Path(benchmark_service_scale.__file__).resolve()),
+ 'harness_sha256':digest(__file__),'started_at':dt.datetime.now(dt.timezone.utc).isoformat(),'release_ready':False,'operations':{},'checks':[],
+ 'page_cells':args.page_cells,'baseline_harness_sha256':digest(Path(__file__).with_name('benchmark_scientist_tag_workflow.py'))}
+ samples={};sql_samples={};app=None;dj=None;project_root=None;index=None;closed=False
+ def check(name,condition,**detail):
+ report['checks'].append({'name':name,'passed':bool(condition),**detail})
+ if not condition:raise AssertionError(name)
+ def log(message):print(message,flush=True)
+ started=time.perf_counter()
+ try:
+ with tempfile.TemporaryDirectory(prefix='rieke-scientist-tag-workflow-') as directory:
+ project_root=Path(create_project(Path(directory)/'projects','Disposable scientist tag workflow')['path'])
+ try:
+ native.ensure_native_database(project_root);dj=connect({'kind':'native-project'},project_dir=project_root)
+ service,protocol=fixture(args.epochs);service.project_dir=project_root
+ service.project=json.loads((project_root/'project.json').read_text());service.config=json.loads((project_root/'catalog.json').read_text());service.dj=dj
+ service.manifests={item['source_sha256']:{'source_path':'/synthetic/'+item['filename']} for item in service.sources}
+ other=uid(-3);service.protocols[other]=copy.deepcopy(service.protocols[protocol])
+ service.protocols[other]['definition']['protocol_uuid']=other;service.protocols[other]['result']['protocol_uuid']=other
+ for value in service.protocols.values():
+ value['definition']['project_uuid']=service.project['project_uuid']
+ value['definition']['query']={'version':1,'all':[{'field':'EpochBlock.protocol_name','operator':'eq','value':'Synthetic'}]}
+ index=DiskMetadataIndex.build(project_root/'cache'/'metadata.sqlite',service.rows,Details(service.rows),service.sources,'tag-workflow-fixture-v1',service.project['project_uuid'])
+ service.disk_index=index;service.details=index.details
+ Project,_,_,_=workspace_tables(dj);project=service.project['project_uuid']
+ Project.insert1({'project_uuid':project,'name':service.project['name'],'directory':str(project_root)})
+ app=create_app(project_root,root/'.rieke-runtime/retinanalysis',service=service)
+ assert service._recovery_tracker.ready,service._recovery_tracker.reason
+ store=app.extensions['curation_store'];shared=app.extensions['shared_annotations'];authors=[uid(-100-i) for i in range(3)]
+ author_names=['Same Scientist','Same Scientist','Scientist C'];stamp=dt.datetime(2026,9,29,12)
+ ids=tuple(service.rows);ordinals={identity:i for i,identity in enumerate(ids)};cell_ordinals={identity:i for i,identity in enumerate(service.cells)}
+ report['mysql_version']=dj.conn().query('SELECT VERSION()').fetchone()[0]
+ seed=time.perf_counter();log(json.dumps({'stage':'seed','epochs':args.epochs,'source_root':str(root)}))
+ with dj.conn().transaction:
+ shared.Profile.insert([{'project_uuid':project,'profile_uuid':author,'display_name':author_names[p],'created_at':stamp,'created_by':'workflow oracle'} for p,author in enumerate(authors)])
+ batch=[]
+ for kind,identities in [('epoch',ids),('cell',tuple(service.cells))]:
+ for ordinal,identity in enumerate(identities):
+ for p,author in enumerate(authors):
+ batch.append({'project_uuid':project,'target_kind':kind,'target_uuid':identity,'profile_uuid':author,
+ 'tags':tags(ordinal,p,cell=kind=='cell'),'author_name':author_names[p],'revision':1,'updated_at':stamp})
+ if len(batch)==500:shared.Annotation.insert(batch);batch=[]
+ if batch:shared.Annotation.insert(batch);batch=[]
+ for p,current in enumerate((protocol,other)):
+ for identity in ids:
+ batch.append({'project_uuid':project,'protocol_uuid':current,'epoch_uuid':identity,'included':True,
+ 'tags':[f'dataset:{p}',*[f'dataset-tag:{i}' for i in range(4)]],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':service._fingerprints[identity]})
+ if len(batch)==500:store.Curation.insert(batch);batch=[]
+ if batch:store.Curation.insert(batch);batch=[]
+ report['seed_seconds']=time.perf_counter()-seed
+ checkpoint=time.perf_counter();snapshot.save(project_root,dj.conn(),service=service);report['seed_checkpoint_seconds']=time.perf_counter()-checkpoint
+ log(json.dumps({'stage':'timed_operations','epochs':args.epochs,'seed_seconds':report['seed_seconds']}))
+ client=app.test_client();base=f'/api/protocols/{protocol}';headers={'X-Workspace-Request':'1','Origin':'http://localhost:8766'}
+ predicates={
+ 'A':{'all':[rule('gate:0','epoch'),rule('gate:1','epoch'),rule('cell-common','cell'),{'not':rule('gate:2')}]},
+ 'B':{'any':[rule('qc','epoch'),rule('cell-bucket:3','cell'),rule('é','epoch')]},
+ 'C':{'all':[{'not':rule('QC')},rule('e\u0301','epoch'),rule('cell-common','cell')]},
+ 'direct_inheritance_excluded':{'all':[rule('cell-common','epoch')]}}
+ expected={
+ 'A':tuple(ids[i] for i in range(args.epochs) if i%2==0 and i%3==0 and i%4!=0),
+ 'B':tuple(ids[i] for i in range(args.epochs) if i%2==1 or (i//100)%5==3 or i%3==0),
+ 'C':tuple(ids[i] for i in range(args.epochs) if i%2==1 and i%3!=0),
+ 'direct_inheritance_excluded':()}
+ edited=expected['A'][:args.edit_count];marker='workflow-selected';dataset_marker='workflow-dataset'
+ predicates['edited']={'all':[rule(marker,'epoch'),*predicates['A']['all']]};expected['edited']=()
+ report['filter_contract']={name:{'predicate':predicate,'expected_count':len(expected[name]),'membership_sha256':hashlib.sha256(''.join(expected[name]).encode()).hexdigest()} for name,predicate in predicates.items()}
+ report['edited_epoch_uuids']=edited;shared_revisions={(key,p):1 for key in edited for p in range(3)};curation_revisions={key:1 for key in edited}
+ shared_active=None;curation_active=False
+ def sql_status():return {key:int(value) for key,value in dj.conn().query("SHOW SESSION STATUS WHERE Variable_name IN ('Rows_sent','Bytes_sent','Questions')").fetchall()}
+ def request(method,path,*,query=None,body=None):
+ response=client.get(path,query_string=query) if method=='GET' else client.post(path,json=body,headers=headers)
+ value=response.get_json()
+ if response.status_code!=200:raise AssertionError((method,path,response.status_code,value))
+ return value
+ def measure(name,callback):
+ before=sql_status();began=time.perf_counter();value=callback();elapsed=time.perf_counter()-began;after=sql_status()
+ samples.setdefault(name,[]).append(elapsed);sql_samples.setdefault(name,[]).append({key:after[key]-before[key] for key in before})
+ return value,elapsed
+ def query(name,**extra):
+ return {'tag_predicate':json.dumps(predicates[name],ensure_ascii=False,separators=(',',':')),
+ **({'include_cells':'true'} if args.page_cells and 'limit' in extra else {}),**extra}
+ def validate_page(value,name,offset=0):
+ wanted=expected[name];shown=wanted[offset:offset+60]
+ assert value['total']==len(wanted) and tuple(row['epoch_uuid'] for row in value['epochs'])==shown,(name,value['total'],len(wanted))
+ if args.page_cells:
+ counts={}
+ for identity in wanted:
+ cell=uid(args.epochs+ordinals[identity]//100);counts[cell]=counts.get(cell,0)+1
+ assert {row['cell_uuid']:row['epochs'] for row in value['cells']}==counts,(name,'filtered cell counts')
+ assert len(value['cells'])==len(counts)
+ for row in value['epochs']:
+ identity=row['epoch_uuid'];i=ordinals[identity];wanted_chips=[]
+ for p,author in enumerate(authors):
+ values=tags(i,p)+([marker] if shared_active==p and identity in edited else [])
+ wanted_chips.extend((tag,author) for tag in values)
+ actual=sorted((item['tag'],item['profile_uuid']) for item in row['annotations']['epoch_tags'])
+ assert actual==sorted(wanted_chips),(name,identity,'author tag chips')
+ cell_i=cell_ordinals[service.rows[identity]['cell_uuid']]
+ wanted_inherited=sorted((tag,author) for p,author in enumerate(authors) for tag in tags(cell_i,p,cell=True))
+ assert sorted((item['tag'],item['profile_uuid']) for item in row['annotations']['cell_tags'])==wanted_inherited
+ assert row['curation']['revision']==curation_revisions.get(identity,1)
+ assert sorted(row['curation']['tags'])==sorted(['dataset:0',*[f'dataset-tag:{j}' for j in range(4)]]+([dataset_marker] if curation_active and identity in edited else []))
+ def page(label,name,offset=0):
+ value,elapsed=measure(label,lambda:request('GET',base+'/epochs',query=query(name,limit=60,offset=offset)))
+ validate_page(value,name,offset);return value,elapsed
+ def summary(label,name):
+ value,elapsed=measure(label,lambda:request('GET',base,query=query(name)))
+ assert value['counts']['epochs']==len(expected[name]),(name,value['counts'],len(expected[name]));return value,elapsed
+ def bundle(label,name):
+ _,a=summary(label+'_summary',name);value,b=page(label+'_page',name);samples.setdefault(label+'_server_bundle',[]).append(a+b);return value
+ def suggest(label,prefix):
+ value,elapsed=measure(label,lambda:request('GET','/api/annotation-tags',query={'q':prefix,'limit':12}))
+ if prefix=='gate':
+ wanted=[{'tag':f'gate:{p}','count':(args.epochs+p+1)//(p+2),'authors':[{'profile_uuid':authors[p],'display_name':author_names[p]}]} for p in range(3)]
+ elif prefix in ('qc','é','e\u0301','cell-common'):
+ all_authors=[{'profile_uuid':author,'display_name':name} for author,name in sorted(zip(authors,author_names))]
+ counts={'QC':(args.epochs+1)//2,'qc':args.epochs//2} if prefix=='qc' else {
+ prefix:(args.epochs+2)//3 if prefix=='é' else args.epochs-(args.epochs+2)//3 if prefix=='e\u0301' else (args.epochs+99)//100}
+ wanted=[{'tag':tag,'count':count,'authors':all_authors} for tag,count in sorted(counts.items(),key=lambda pair:(-pair[1],pair[0].casefold(),pair[0]))]
+ else:wanted=[] if shared_active is None else [{'tag':marker,'count':len(edited),'authors':[{'profile_uuid':authors[shared_active],'display_name':author_names[shared_active]}]}]
+ assert value['tags']==wanted and value['total']==len(wanted),(prefix,value,wanted)
+ return value
+ for name in predicates:
+ if name=='edited':continue
+ page('cold_filter_'+name,name)
+ last=max(0,len(expected[name])-60);page('oracle_boundary_'+name,name,last)
+ exact,_=service._epoch_page_identities(protocol,query(name))
+ check('full_membership_'+name,tuple(exact)==expected[name],count=len(exact))
+ suggest('suggestions_cold','gate')
+ for _ in range(args.samples):suggest('suggestions_warm','gate')
+ if args.page_cells:
+ for label,prefix in [('case','qc'),('unicode_nfc','é'),('unicode_nfd','e\u0301'),('inherited_targets','cell-common')]:
+ suggest('suggestions_first_'+label,prefix)
+ known_fields={'epoch','date','cell','block','block time','cell type','group label','group','protocol',
+ 'parameters/contrast','parameters/seed','parameters/stimTime','parameters/currentMean',
+ 'metadata/cell/properties/type','metadata/cell/label','metadata/block/parameters/amp'}
+ allowed_fields={'id','label','category','path','components','grouping_role','grouping_priority','grouping_hint'}
+ for iteration in range(args.samples+1):
+ value,_=measure('metadata_fields_cold' if iteration==0 else 'metadata_fields_warm',lambda:request('GET','/api/metadata/fields'))
+ assert {field['id'] for field in value['fields']}==known_fields
+ assert len(value['fields'])==len(known_fields) and all(set(field)<=allowed_fields for field in value['fields'])
+ def shared_change(add,p,*,label=None):
+ nonlocal shared_active
+ body={'target_kind':'epoch','target_uuids':list(edited),'profile_uuid':authors[p],
+ 'tags_add' if add else 'tags_remove':[marker],'expected_revisions':{key:shared_revisions[(key,p)] for key in edited}}
+ call=lambda:request('POST','/api/annotations',body=body)
+ value=measure(label,call)[0] if label else call()
+ assert value['changed']==len(edited)
+ for key in edited:shared_revisions[(key,p)]+=1
+ shared_active=p if add else None;expected['edited']=edited if add else ()
+ def curation_change(add,current_page,*,label):
+ nonlocal curation_active
+ body={'epoch_uuids':list(edited),'query_revision':current_page['query_revision'],'expected_binding_version':0,
+ 'expected_revisions':dict(curation_revisions),'changes':{'tags_add' if add else 'tags_remove':[dataset_marker]}}
+ measure(label,lambda:request('POST',base+'/curation',body=body))
+ for key in edited:curation_revisions[key]+=1
+ curation_active=add
+ for iteration in range(args.samples):
+ before=time.perf_counter();bundle('filter_A','A');bundle('filter_B','B');current=bundle('return_A','A')
+ samples.setdefault('A_B_A_sequential_server_bundle',[]).append(sum(samples[key][-1] for key in ('filter_A_server_bundle','filter_B_server_bundle','return_A_server_bundle')))
+ p=iteration%3
+ shared_change(True,p,label='shared_add_durable_save');current=bundle('first_after_shared_add','A')
+ page('added_tag_filter','edited');suggest('suggestions_after_shared_add','workflow')
+ shared_change(False,p,label='shared_remove_durable_save');current=bundle('first_after_shared_remove','A')
+ page('removed_tag_filter','edited');suggest('suggestions_after_shared_remove','workflow')
+ curation_change(True,current,label='dataset_add_durable_save');current=bundle('first_after_dataset_add','A')
+ curation_change(False,current,label='dataset_remove_durable_save');bundle('first_after_dataset_remove','A')
+ samples.setdefault('complete_workflow_wall_with_oracles',[]).append(time.perf_counter()-before)
+ log(json.dumps({'stage':'cycle','epochs':args.epochs,'iteration':iteration+1,'seconds':samples['complete_workflow_wall_with_oracles'][-1]}))
+ # This is measured separately: the first request after a save is
+ # the visible page, before any summary/autocomplete can maintain
+ # or warm the shared membership index for it.
+ if args.page_cells:
+ def focused(label):
+ identity=edited[0];i=ordinals[identity]
+ value,_=measure(label,lambda:request('GET','/api/epochs/'+identity,query={'protocol_uuid':protocol}))
+ assert value['epoch_uuid']==identity and value['parameters']=={'contrast':(i%5)/10,'seed':i,'stimTime':1000,'currentMean':(i%3)*100}
+ assert value['curation']['revision']==curation_revisions[identity]
+ wanted=sorted((tag,author) for p,author in enumerate(authors) for tag in tags(i,p)+([marker] if shared_active==p else []))
+ assert sorted((item['tag'],item['profile_uuid']) for item in value['annotations']['epoch_tags'])==wanted
+ for iteration in range(args.samples):
+ page('page_first_filter_A','A');page('page_first_filter_B','B');current,_=page('page_first_return_A','A')
+ p=iteration%3
+ shared_change(True,p,label='page_first_shared_add_save')
+ current,_=page('page_first_after_shared_add','A')
+ focused('focused_metadata_after_shared_add')
+ suggest('page_first_suggestions_after_shared_add','workflow')
+ shared_change(False,p,label='page_first_shared_remove_save')
+ current,_=page('page_first_after_shared_remove','A')
+ focused('focused_metadata_after_shared_remove')
+ suggest('page_first_suggestions_after_shared_remove','workflow')
+ curation_change(True,current,label='page_first_dataset_add_save')
+ current,_=page('page_first_after_dataset_add','A')
+ focused('focused_metadata_after_dataset_add')
+ curation_change(False,current,label='page_first_dataset_remove_save')
+ page('page_first_after_dataset_remove','A')
+ focused('focused_metadata_after_dataset_remove')
+ log(json.dumps({'stage':'page_first_cycle','epochs':args.epochs,'iteration':iteration+1}))
+ def profile(name,callback):
+ profiler=cProfile.Profile();value=profiler.runcall(callback);stats=pstats.Stats(profiler)
+ report.setdefault('cpu_profiles',{})[name]=[{'file':str(Path(key[0]).relative_to(root)) if Path(key[0]).is_absolute() and Path(key[0]).is_relative_to(root) else key[0],
+ 'line':key[1],'function':key[2],'primitive_calls':value[0],'calls':value[1],'self_seconds':value[2],'cumulative_seconds':value[3]}
+ for key,value in sorted(stats.stats.items(),key=lambda item:item[1][3],reverse=True)[:40]]
+ return value
+ if args.profile:
+ value=profile('warm_multitag_page',lambda:request('GET',base+'/epochs',query=query('A',limit=60)));validate_page(value,'A')
+ def post_save_loop():
+ shared_change(True,0)
+ value=request('GET',base,query=query('A'));assert value['counts']['epochs']==len(expected['A'])
+ result=request('GET',base+'/epochs',query=query('A',limit=60));validate_page(result,'A')
+ request('GET','/api/annotation-tags',query={'q':'workflow','limit':12})
+ profile('post_shared_save_summary_page_suggestions',post_save_loop);shared_change(False,0)
+ check('final_exact_membership_restored',all(tuple(service._epoch_page_identities(protocol,query(name))[0])==expected[name] for name in predicates))
+ other_page=request('GET',f'/api/protocols/{other}/epochs',query={'limit':60})
+ check('other_protocol_curation_untouched',all(row['curation']['revision']==1 and 'dataset:1' in row['curation']['tags'] and dataset_marker not in row['curation']['tags'] for row in other_page['epochs']))
+ report['workflow_peak_rss_before_durable_oracle_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ began=time.perf_counter();state=snapshot.load(project_root/'app-state.json');report['durable_snapshot_verify_seconds']=time.perf_counter()-began
+ saved={(row['target_uuid'],row['profile_uuid']):row for row in state['tables']['shared_annotation'] if row['target_kind']=='epoch' and row['target_uuid'] in edited}
+ check('durable_mirror_exact_tag_removal_and_author_revisions',all(sorted(saved[(key,authors[p])]['tags'])==sorted(tags(ordinals[key],p)) and saved[(key,authors[p])]['revision']==shared_revisions[(key,p)] for key in edited for p in range(3)))
+ check('durable_mirror_keeps_two_protocol_scopes',len(state['tables']['curation'])==args.epochs*2)
+ report['shared_index_stats']=getattr(getattr(shared,'_shared_tag_index',None),'stats',None)
+ loaded={name:str(Path(module.__file__).resolve()) for name,module in sys.modules.items() if (name.startswith('workspace_') or name=='recording_workspace') and getattr(module,'__file__',None)}
+ check('all_product_modules_loaded_from_selected_source',all(Path(path).is_relative_to(root) for path in loaded.values()))
+ report['loaded_product_modules']=loaded
+ finally:
+ if app is not None:
+ shared=app.extensions.get('shared_annotations');derived=getattr(shared,'_shared_tag_index',None)
+ if derived is not None:derived.close()
+ lock=app.extensions.get('app_state_session_lock')
+ if lock is not None:lock.close()
+ if index is not None:index.close()
+ if dj is not None:dj.conn().close()
+ if project_root is not None:closed=native.stop_native_database(project_root)
+ report['owned_native_runtime_stopped']=closed
+ except Exception as error:
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};log(traceback.format_exc())
+ derived={
+ 'warm_page_only_A_B_A_inferred_from_summary_first':('filter_A_page','filter_B_page','return_A_page'),
+ 'shared_add_save_plus_warm_page_inferred':('shared_add_durable_save','first_after_shared_add_page'),
+ 'shared_remove_save_plus_warm_page_inferred':('shared_remove_durable_save','first_after_shared_remove_page'),
+ 'measured_page_first_A_B_A':('page_first_filter_A','page_first_filter_B','page_first_return_A'),
+ 'measured_shared_add_save_plus_first_page':('page_first_shared_add_save','page_first_after_shared_add'),
+ 'measured_shared_remove_save_plus_first_page':('page_first_shared_remove_save','page_first_after_shared_remove'),
+ 'measured_shared_add_save_page_focus':('page_first_shared_add_save','page_first_after_shared_add','focused_metadata_after_shared_add'),
+ 'measured_shared_remove_save_page_focus':('page_first_shared_remove_save','page_first_after_shared_remove','focused_metadata_after_shared_remove'),
+ 'measured_dataset_add_save_plus_first_page':('page_first_dataset_add_save','page_first_after_dataset_add'),
+ 'measured_dataset_remove_save_plus_first_page':('page_first_dataset_remove_save','page_first_after_dataset_remove')}
+ report['derived_bundle_definitions']={name:list(keys) for name,keys in derived.items() if all(key in samples for key in keys)}
+ for name,keys in report['derived_bundle_definitions'].items():
+ lengths={len(samples[key]) for key in keys}
+ if len(lengths)==1:samples[name]=[sum(values) for values in zip(*(samples[key] for key in keys))]
+ report['operations']={name:{**percentiles(values),'sql_session_deltas':sql_samples.get(name,[])} for name,values in samples.items()}
+ report['source_inventory_after']=source_inventory(root);report['source_unchanged']=report['source_inventory_before']==report['source_inventory_after']
+ report['harness_unchanged']=digest(__file__)==report['harness_sha256']
+ report['seconds']=time.perf_counter()-started;report['finished_at']=dt.datetime.now(dt.timezone.utc).isoformat();report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['passed']=not report.get('error') and closed and report['source_unchanged'] and report['harness_unchanged'] and all(item['passed'] for item in report['checks'])
+ args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');log(json.dumps({'stage':'done','epochs':args.epochs,'passed':report['passed'],'seconds':report['seconds'],'output':str(args.output)}))
+ return int(not report['passed'])
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py b/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py
new file mode 100644
index 0000000..bfc07b5
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py
@@ -0,0 +1,135 @@
+"""Disposable current disk-index service probe; no SQL, lab files, or mutations.
+
+Run with PYTHONPATH=python .rieke-runtime/venv/bin/python
+ --epochs 10000 --output
+Timings cover service CPU/I/O only, excluding HTTP hooks and browser rendering.
+"""
+from __future__ import annotations
+import argparse
+from collections.abc import Mapping
+import cProfile
+import datetime as dt
+import gc
+import hashlib
+import json
+from pathlib import Path
+import pstats
+import platform
+import statistics
+import tempfile
+import time
+import uuid
+import psutil
+from workspace_disk_index import DiskMetadataIndex
+from workspace_service import WorkspaceService
+from workspace_tree_pages import TreePages
+
+
+def uid(number):
+ # Deterministic pseudorandom UUIDs avoid unrealistically cheap wire compression.
+ return str(uuid.UUID(bytes=hashlib.sha256(str(number).encode()).digest()[:16]))
+
+
+class Details(Mapping):
+ def __init__(self, rows): self.rows = rows
+ def __len__(self): return len(self.rows)
+ def __iter__(self): return iter(self.rows)
+ def __getitem__(self, key):
+ row = self.rows[key]; i = row['synthetic_index']
+ return {'parameters': {'contrast': (i % 5) / 10, 'seed': i,
+ 'stimTime': 1000, 'currentMean': (i % 3) * 100}, 'properties': {},
+ 'attributes': {}, 'metadata': {'cell': {'uuid': row['cell_uuid'],
+ 'label': row['cell_label'], 'properties': {'type': row['cell_type']}},
+ 'block': {'uuid': row['block_uuid'], 'parameters': {'amp': 'Amp1'}}}}
+
+
+def fixture(count):
+ service = WorkspaceService.__new__(WorkspaceService)
+ service._loaded = True; service.curation_provider = None
+ service.project = {'project_uuid': uid(-1), 'name': 'Disposable scale probe'}
+ service.config = {'database': 'no-sql'}
+ service.sources = [{'source_sha256': f'{i+1:064x}', 'filename': f'synthetic-{i}.h5'} for i in range(10)]
+ service.rows = {}; service.cells = {}
+ base = dt.datetime(2026, 9, 1, 12)
+ for i in range(count):
+ identity = uid(i); cell = uid(count+i//100); block = uid(2*count+i//20)
+ started = (base + dt.timedelta(seconds=i)).strftime('%m/%d/%Y %H:%M:%S:%f')
+ row = {'epoch_uuid': identity, 'cell_uuid': cell, 'cell_label': f'Cell{i//100}',
+ 'cell_type': ['ON', 'OFF'][(i//100)%2], 'date': '2026-09-01', 'start_time': started,
+ 'group_uuid': uid(3*count), 'group_label': 'Control', 'block_uuid': block,
+ 'block_start_time': started, 'protocol_name': 'Synthetic', 'duration_seconds': 1.,
+ 'epoch_number': i%20+1, 'streams': [], 'source_sha256': service.sources[(i//100)%10]['source_sha256'],
+ 'metadata_hash': 'b'*64, 'synthetic_index': i}
+ service.rows[identity] = row
+ service.cells[cell] = {'cell_uuid': cell, 'label': row['cell_label'], 'cell_type': row['cell_type'], 'date': row['date']}
+ protocol = uid(-2)
+ service.protocols = {protocol: {'definition': {'protocol_uuid': protocol, 'name': 'All epochs', 'query': {'version':1, 'all':[]}},
+ 'result': {'protocol_uuid': protocol, 'protocol_name': 'Synthetic',
+ 'epochs': [{'uuid': key, 'metadata_hash': 'b'*64} for key in service.rows],
+ 'cells': [{'uuid': key} for key in service.cells], 'source_revisions': [s['source_sha256'] for s in service.sources]}}}
+ service._fingerprints = {key:'b'*64 for key in service.rows}
+ service.events = lambda limit=25: []
+ return service, protocol
+
+
+def run(count, repeats):
+ process = psutil.Process()
+ rss = lambda: round(process.memory_info().rss/1024**2, 2)
+ report = {'epochs':count, 'sources':10, 'protocol_members':count, 'synthetic':True,
+ 'python':platform.python_version(), 'platform':platform.platform(),
+ 'limits':'No SQL, API snapshot hooks, HTTP, tags, waveform data, or browser; 4 scalar parameters per epoch.',
+ 'rss_initial_mib':rss(), 'operations':[]}
+ service, protocol = fixture(count)
+ with tempfile.TemporaryDirectory(prefix='rieke-service-scale-') as directory:
+ started = time.perf_counter()
+ index = DiskMetadataIndex.build(Path(directory)/'index.sqlite', service.rows, Details(service.rows), service.sources, 'synthetic-generation', service.project['project_uuid'])
+ report.update(build_seconds=time.perf_counter()-started, index_bytes=index.path.stat().st_size,
+ fields=len(index.catalog()['fields']), rss_model_index_mib=rss())
+ service.disk_index = index; service.details = index.details
+ pager = TreePages(service)
+ predicate = {'field':'parameters/contrast','operator':'eq','value':.3}
+ operations = [
+ ('epoch_page_60', lambda:service.epoch_page(protocol, limit=60)),
+ ('epoch_page_60_filter_one_cell', lambda:service.epoch_page(protocol, {'cell_uuid':next(iter(service.cells))}, limit=60)),
+ ('tree_root_page_80', lambda:pager.page({'splits':'cell,block','limit':80})),
+ ('flat_tree_page_80', lambda:pager.page({'splits':'','limit':80})),
+ ('select_one_epoch_predicate', lambda:service.match_predicate({'field':'epoch','operator':'eq','value':next(iter(service.rows))})),
+ ('filter_preview_summary', lambda:service.explore_preview(predicate, 'cell,block', include_tree=False)),
+ ('filter_preview_fast_summary', lambda:service.explore_preview(predicate, 'cell,block', include_tree=False, include_catalog_summary=False)),
+ ('overview', service.overview)]
+ for name, operation in operations:
+ samples = []; cpu_samples = []
+ for _ in range(repeats):
+ gc.collect(); cpu = time.process_time(); started = time.perf_counter()
+ result = operation()
+ samples.append(time.perf_counter()-started); cpu_samples.append(time.process_time()-cpu)
+ started = time.perf_counter(); encoded = json.dumps(result, separators=(',',':')).encode()
+ serialization = time.perf_counter()-started
+ record = {'name':name,'wall_seconds_samples':samples, 'median_seconds':statistics.median(samples),
+ 'median_cpu_seconds':statistics.median(cpu_samples), 'json_bytes':len(encoded),
+ 'serialization_seconds':serialization,'rss_mib':rss()}
+ if isinstance(result,dict):
+ for key in ('total','matched_count','count'):
+ if key in result: record[key]=result[key]
+ if name in ('tree_root_page_80','filter_preview_summary','select_one_epoch_predicate'):
+ profile=cProfile.Profile();profile.enable();operation();profile.disable()
+ stats=pstats.Stats(profile)
+ record['profile_top_cumulative']=[{'file':Path(key[0]).name,'line':key[1],
+ 'function':key[2],'primitive_calls':value[0],'calls':value[1],
+ 'self_seconds':value[2],'cumulative_seconds':value[3]}
+ for key,value in sorted(stats.stats.items(),key=lambda item:item[1][3],reverse=True)[:18]]
+ report['operations'].append(record)
+ print(json.dumps({'epochs':count,**record},default=str),flush=True)
+ del result, encoded
+ report['rss_after_operations_mib']=rss()
+ return report
+
+
+if __name__ == '__main__':
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs',type=int,required=True)
+ parser.add_argument('--repeats',type=int,default=3)
+ parser.add_argument('--output',type=Path,required=True)
+ args=parser.parse_args()
+ result=run(args.epochs,args.repeats)
+ args.output.write_text(json.dumps(result,indent=2))
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_single_tag_write.py b/docs/dev/scale-audit-2026-09-29/benchmark_single_tag_write.py
new file mode 100644
index 0000000..34f7662
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_single_tag_write.py
@@ -0,0 +1,247 @@
+"""Native single-epoch and single-cell writes without post-seed index preparation.
+
+Every project/database is disposable. Acquisition metadata is synthetic; actual
+MySQL, triggers, Flask routes, derived indexes and each version's save hooks
+are used. Native SQL commits are checked immediately; any background recovery
+mirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude
+oracle work, and do not model browser concurrency/rendering. Sequential bundle
+times are sums of their separately measured requests. Profiles are separate.
+"""
+import argparse
+import copy
+import cProfile
+import datetime as dt
+import hashlib
+import json
+import math
+import os
+from pathlib import Path
+import pstats
+import resource
+import statistics
+import sys
+import tempfile
+import time
+import traceback
+
+
+def digest(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
+def percentiles(values):
+ ordered=sorted(values)
+ return {'samples_seconds':values,'p50_seconds':statistics.median(values),'p95_seconds':ordered[math.ceil(.95*len(ordered))-1],
+ 'p99_seconds':ordered[math.ceil(.99*len(ordered))-1],'maximum_seconds':max(values),'quantile_method':'nearest_rank'}
+def rule(tag,scope='effective'):return {'field':'annotations/'+scope+'/tags','operator':'contains','value':tag}
+def tags(ordinal,profile,*,cell=False):
+ if cell:return ['cell-common',f'cell-bucket:{ordinal%5}',f'cell-author:{profile}','Inherited' if profile==0 else 'inherited',f'cell-type:{ordinal%2}']
+ return ['all',f'gate:{profile}' if ordinal%(profile+2)==0 else f'rest:{profile}','QC' if ordinal%2==0 else 'qc',
+ 'é' if ordinal%3==0 else 'e\u0301',f'bucket:{ordinal%17}']
+def source_inventory(root):
+ return {str(path.relative_to(root)):digest(path) for path in sorted((root/'python').rglob('*.py'))}
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--source-root',type=Path,required=True)
+ ap.add_argument('--mysql-runtime-root',type=Path,required=True);ap.add_argument('--epochs',type=int,required=True)
+ ap.add_argument('--samples',type=int,default=20);ap.add_argument('--targets',default='epoch:1,cell:1')
+ ap.add_argument('--output',type=Path,required=True);ap.add_argument('--profile',action='store_true')
+ ap.add_argument('--page-cells',action='store_true',default=True)
+ args=ap.parse_args()
+ args.output=args.output.resolve();root=args.source_root.resolve(strict=True)
+ if args.output.exists():ap.error('Choose an unused output receipt')
+ if args.epochs<200 or args.samples<1:ap.error('Use at least200 epochs and one sample')
+ # Product and fixture imports must come from the selected immutable tree.
+ sys.path[:0]=[str(root/'python'),str(root/'docs/dev/scale-audit-2026-09-29')]
+ os.chdir(root)
+ from benchmark_service_scale import fixture,Details,uid
+ import benchmark_service_scale
+ from recording_workspace import connect,workspace_tables
+ from workspace_projects import create_project
+ import workspace_native_mysql as native
+ from workspace_mysql_runtime import _probe,runtime_spec
+ from workspace_disk_index import DiskMetadataIndex
+ from workspace_api import create_app
+ import workspace_state_snapshot as snapshot
+ runtime=_probe(args.mysql_runtime_root.resolve(strict=True),runtime_spec(root)[0]['mysql_version'])
+ def runtime_binary(name='mysqld'):
+ if name not in {'mysqld','mysql','mysqldump'}:raise ValueError('Unsupported native executable')
+ return Path(runtime[name])
+ native.native_binary=runtime_binary
+ report={'scope':__doc__,'epochs':args.epochs,'profiles':3,'tags_per_annotation':5,'protocols':2,'samples':args.samples,
+ 'source_root':str(root),'source_inventory_before':source_inventory(root),'fixture_module':str(Path(benchmark_service_scale.__file__).resolve()),
+ 'harness_sha256':digest(__file__),'started_at':dt.datetime.now(dt.timezone.utc).isoformat(),'release_ready':False,'operations':{},'checks':[],
+ 'page_cells':True,'target_specification':args.targets,'baseline_harness_sha256':digest(Path(__file__).with_name('benchmark_scientist_tag_workflow.py'))}
+ samples={};sql_samples={};app=None;dj=None;project_root=None;index=None;closed=False
+ def check(name,condition,**detail):
+ report['checks'].append({'name':name,'passed':bool(condition),**detail})
+ if not condition:raise AssertionError(name)
+ def log(message):print(message,flush=True)
+ started=time.perf_counter()
+ try:
+ with tempfile.TemporaryDirectory(prefix='rieke-single-tag-write-') as directory:
+ project_root=Path(create_project(Path(directory)/'projects','Disposable scientist tag workflow')['path'])
+ try:
+ native.ensure_native_database(project_root);dj=connect({'kind':'native-project'},project_dir=project_root)
+ service,protocol=fixture(args.epochs);service.project_dir=project_root
+ service.project=json.loads((project_root/'project.json').read_text());service.config=json.loads((project_root/'catalog.json').read_text());service.dj=dj
+ service.manifests={item['source_sha256']:{'source_path':'/synthetic/'+item['filename']} for item in service.sources}
+ other=uid(-3);service.protocols[other]=copy.deepcopy(service.protocols[protocol])
+ service.protocols[other]['definition']['protocol_uuid']=other;service.protocols[other]['result']['protocol_uuid']=other
+ for value in service.protocols.values():
+ value['definition']['project_uuid']=service.project['project_uuid']
+ value['definition']['query']={'version':1,'all':[{'field':'EpochBlock.protocol_name','operator':'eq','value':'Synthetic'}]}
+ index=DiskMetadataIndex.build(project_root/'cache'/'metadata.sqlite',service.rows,Details(service.rows),service.sources,'tag-workflow-fixture-v1',service.project['project_uuid'])
+ service.disk_index=index;service.details=index.details
+ Project,_,_,_=workspace_tables(dj);project=service.project['project_uuid']
+ Project.insert1({'project_uuid':project,'name':service.project['name'],'directory':str(project_root)})
+ app=create_app(project_root,root/'.rieke-runtime/retinanalysis',service=service)
+ assert service._recovery_tracker.ready,service._recovery_tracker.reason
+ store=app.extensions['curation_store'];shared=app.extensions['shared_annotations'];authors=[uid(-100-i) for i in range(3)]
+ author_names=['Same Scientist','Same Scientist','Scientist C'];stamp=dt.datetime(2026,9,29,12)
+ ids=tuple(service.rows);ordinals={identity:i for i,identity in enumerate(ids)};cell_ordinals={identity:i for i,identity in enumerate(service.cells)}
+ report['mysql_version']=dj.conn().query('SELECT VERSION()').fetchone()[0]
+ seed=time.perf_counter();log(json.dumps({'stage':'seed','epochs':args.epochs,'source_root':str(root)}))
+ with dj.conn().transaction:
+ shared.Profile.insert([{'project_uuid':project,'profile_uuid':author,'display_name':author_names[p],'created_at':stamp,'created_by':'workflow oracle'} for p,author in enumerate(authors)])
+ batch=[]
+ for kind,identities in [('epoch',ids),('cell',tuple(service.cells))]:
+ for ordinal,identity in enumerate(identities):
+ for p,author in enumerate(authors):
+ batch.append({'project_uuid':project,'target_kind':kind,'target_uuid':identity,'profile_uuid':author,
+ 'tags':tags(ordinal,p,cell=kind=='cell'),'author_name':author_names[p],'revision':1,'updated_at':stamp})
+ if len(batch)==500:shared.Annotation.insert(batch);batch=[]
+ if batch:shared.Annotation.insert(batch);batch=[]
+ for p,current in enumerate((protocol,other)):
+ for identity in ids:
+ batch.append({'project_uuid':project,'protocol_uuid':current,'epoch_uuid':identity,'included':True,
+ 'tags':[f'dataset:{p}',*[f'dataset-tag:{i}' for i in range(4)]],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':service._fingerprints[identity]})
+ if len(batch)==500:store.Curation.insert(batch);batch=[]
+ if batch:store.Curation.insert(batch);batch=[]
+ report['seed_seconds']=time.perf_counter()-seed
+ checkpoint=time.perf_counter();snapshot.save(project_root,dj.conn(),service=service);report['seed_checkpoint_seconds']=time.perf_counter()-checkpoint
+ log(json.dumps({'stage':'timed_operations','epochs':args.epochs,'seed_seconds':report['seed_seconds']}))
+ client=app.test_client();base=f'/api/protocols/{protocol}';headers={'X-Workspace-Request':'1','Origin':'http://localhost:8766'}
+
+
+
+ import inspect,threading
+ from workspace_shared_tag_index import SharedTagIndex
+ lock=inspect.getclosurevars(app.view_functions['annotation_batch_read']).nonlocals['db_lock']
+ connection=dj.conn();owner=threading.get_ident();recording=False;trace={}
+ original_start=connection.start_transaction;original_commit=connection.commit_transaction
+ original_event=shared._event;original_membership=shared._membership_index;original_refresh=SharedTagIndex.refresh
+ def current():return recording and threading.get_ident()==owner
+ def start_txn():
+ began=time.perf_counter();value=original_start()
+ if current():trace['transaction_started']=began;trace['begins']+=1
+ return value
+ def commit_txn():
+ began=time.perf_counter();value=original_commit()
+ if current():
+ trace['commit_seconds'].append(time.perf_counter()-began)
+ trace['transaction_seconds'].append(time.perf_counter()-trace['transaction_started']);trace['commits']+=1
+ return value
+ def event(*args,**kwargs):
+ if current():
+ assert connection.in_transaction and trace['begins']==1 and trace['commits']==0
+ trace['events_inside_transaction']+=1
+ return original_event(*args,**kwargs)
+ def membership(*args,**kwargs):
+ if current():trace['membership_calls']+=1
+ return original_membership(*args,**kwargs)
+ def refresh(*args,**kwargs):
+ if current():trace['index_refresh_calls']+=1
+ return original_refresh(*args,**kwargs)
+ connection.start_transaction=start_txn;connection.commit_transaction=commit_txn
+ shared._event=event;shared._membership_index=membership;SharedTagIndex.refresh=refresh
+ with lock:
+ values=connection.query('SELECT @@innodb_flush_log_at_trx_commit,@@sync_binlog,@@log_bin').fetchone()
+ report['mysql_durability']={'innodb_flush_log_at_trx_commit':int(values[0]),'sync_binlog':int(values[1]),'log_bin':bool(values[2])}
+ epoch=ids[0];cell=service.rows[epoch]['cell_uuid'];children=tuple(key for key in ids if service.rows[key]['cell_uuid']==cell)
+ targets={'epoch':epoch,'cell':cell};revisions={(kind,p):1 for kind in targets for p in range(3)};active={};marker='single-write-marker'
+ report['post_traces']=[];report['backup_receipts']=[]
+ index_before=getattr(shared,'_shared_tag_index',None)
+ report['index_before_posts']={'present':index_before is not None,'stats':dict(index_before.stats) if index_before else None,'post_seed_preparation':False}
+ def wanted(kind,p):return sorted(tags(ordinals[epoch] if kind=='epoch' else cell_ordinals[cell],p,cell=kind=='cell')+([marker] if active.get(kind)==p else []))
+ def child_rows():
+ with lock:rows=(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'}&[{'target_uuid':key} for key in children]).to_dicts()
+ return sorted((row['target_uuid'],row['profile_uuid'],row['revision'],tuple(sorted(row['tags'])),str(row['updated_at'])) for row in rows)
+ def post(label,kind,p,add):
+ nonlocal recording,trace
+ key=targets[kind];before_children=child_rows() if kind=='cell' else None
+ body={'target_kind':kind,'target_uuids':[key],'profile_uuid':authors[p],
+ 'tags_add':[marker] if add else [],'tags_remove':[] if add else [marker],
+ 'expected_revisions':{key:revisions[(kind,p)]}}
+ trace={'operation':label,'begins':0,'commits':0,'events_inside_transaction':0,'membership_calls':0,'index_refresh_calls':0,'commit_seconds':[],'transaction_seconds':[]}
+ recording=True;began=time.perf_counter()
+ try:response=client.post('/api/annotations',json=body,headers=headers);value=response.get_json()
+ finally:elapsed=time.perf_counter()-began;recording=False
+ assert response.status_code==200 and value['changed']==1,(response.status_code,value)
+ assert value['persistence']['database']=='committed'
+ assert trace['begins']==trace['commits']==trace['events_inside_transaction']==1,trace
+ assert trace['membership_calls']==trace['index_refresh_calls']==0,trace
+ trace.pop('transaction_started',None);report['post_traces'].append(trace);report['backup_receipts'].append(value['persistence'])
+ samples.setdefault(label,[]).append(elapsed)
+ samples.setdefault(label+'_sql_transaction',[]).append(trace['transaction_seconds'][0]);samples.setdefault(label+'_commit_roundtrip',[]).append(trace['commit_seconds'][0])
+ revisions[(kind,p)]+=1
+ if add:active[kind]=p
+ else:active.pop(kind,None)
+ receipt=value['targets'][key]
+ assert receipt['revisions']=={author:revisions[(kind,q)] for q,author in enumerate(authors)}
+ assert sorted((v['tag'],v['profile_uuid']) for v in receipt['tags'])==sorted((tag,author) for q,author in enumerate(authors) for tag in wanted(kind,q))
+ with lock:
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':kind,'target_uuid':key,'profile_uuid':authors[p]}).to_dicts()
+ events=(shared.Event&{'event_uuid':value['event_uuid']}).to_dicts()
+ assert len(rows)==len(events)==1 and rows[0]['revision']==revisions[(kind,p)] and sorted(rows[0]['tags'])==wanted(kind,p)
+ assert events[0]['action']=='shared_annotations_updated' and events[0]['payload']['target_count']==1
+ assert events[0]['payload']['after'][0]['target_uuid']==key and events[0]['payload']['after'][0]['revision']==revisions[(kind,p)]
+ if kind=='cell':assert child_rows()==before_children,'Cell save rewrote child annotation rows'
+ # Both first writes happen before repeated samples, with no
+ # post-seed index rebuild or filter request anywhere in this run.
+ for kind in targets:post(kind+'_first_add',kind,0,True)
+ for kind in targets:post(kind+'_first_remove',kind,0,False)
+ log(json.dumps({'stage':'first_writes','epochs':args.epochs,'epoch_ms':samples['epoch_first_add'][0]*1000,'cell_ms':samples['cell_first_add'][0]*1000}))
+ for kind in targets:
+ for iteration in range(args.samples):
+ post(kind+'_add',kind,iteration%3,True);post(kind+'_remove',kind,iteration%3,False)
+ log(json.dumps({'stage':'single_target_complete','epochs':args.epochs,'target':kind,'samples':args.samples}))
+ connection.start_transaction=original_start;connection.commit_transaction=original_commit
+ shared._event=original_event;shared._membership_index=original_membership;SharedTagIndex.refresh=original_refresh
+ check('no_membership_index_or_refresh_during_any_post',all(row['membership_calls']==row['index_refresh_calls']==0 for row in report['post_traces']),posts=len(report['post_traces']))
+ check('annotation_event_committed_in_same_transaction',all(row['begins']==row['commits']==row['events_inside_transaction']==1 for row in report['post_traces']))
+ after_index=getattr(shared,'_shared_tag_index',None);report['index_after_posts']={'stats':dict(after_index.stats) if after_index else None}
+ scheduler=app.extensions['backup_scheduler'];scheduler.flush();report['final_backup_status']=scheduler.status()
+ state=snapshot.load(project_root/'app-state.json');saved={(row['target_kind'],row['target_uuid'],row['profile_uuid']):row for row in state['tables']['shared_annotation']}
+ for (kind,p),revision in revisions.items():
+ row=saved[(kind,targets[kind],authors[p])];assert row['revision']==revision and sorted(row['tags'])==wanted(kind,p)
+ check('final_recovery_mirror_exact_tags_and_revisions',True)
+ check('cell_has_one_canonical_record_per_author_no_child_copies',sum(row['target_kind']=='epoch' for row in saved.values())==args.epochs*3,child_epochs=len(children))
+ check('other_protocol_curation_untouched',len(state['tables']['curation'])==args.epochs*2 and all(row['revision']==1 for row in state['tables']['curation']))
+ loaded={name:str(Path(module.__file__).resolve()) for name,module in sys.modules.items() if (name.startswith('workspace_') or name=='recording_workspace') and getattr(module,'__file__',None)}
+ check('all_product_modules_from_selected_source',all(Path(path).is_relative_to(root) for path in loaded.values()));report['loaded_product_modules']=loaded
+ finally:
+ if app is not None:
+ scheduler=app.extensions.get('backup_scheduler')
+ if scheduler is not None:
+ scheduler.flush()
+ close=getattr(scheduler,'close',None)
+ if close is not None:close(flush=False)
+ shared=app.extensions.get('shared_annotations');derived=getattr(shared,'_shared_tag_index',None)
+ if derived is not None:derived.close()
+ lock=app.extensions.get('app_state_session_lock')
+ if lock is not None:lock.close()
+ if index is not None:index.close()
+ if dj is not None:dj.conn().close()
+ if project_root is not None:closed=native.stop_native_database(project_root)
+ report['owned_native_runtime_stopped']=closed
+ except Exception as error:
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};log(traceback.format_exc())
+ report['operations']={name:{**percentiles(values),'sql_session_deltas':sql_samples.get(name,[])} for name,values in samples.items()}
+ report['source_inventory_after']=source_inventory(root);report['source_unchanged']=report['source_inventory_before']==report['source_inventory_after']
+ report['harness_unchanged']=digest(__file__)==report['harness_sha256']
+ report['seconds']=time.perf_counter()-started;report['finished_at']=dt.datetime.now(dt.timezone.utc).isoformat();report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['passed']=not report.get('error') and closed and report['source_unchanged'] and report['harness_unchanged'] and all(item['passed'] for item in report['checks'])
+ args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');log(json.dumps({'stage':'done','epochs':args.epochs,'passed':report['passed'],'seconds':report['seconds'],'output':str(args.output)}))
+ return int(not report['passed'])
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_tag_actions.py b/docs/dev/scale-audit-2026-09-29/benchmark_tag_actions.py
new file mode 100644
index 0000000..e3a30dc
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_tag_actions.py
@@ -0,0 +1,284 @@
+"""Native single, batch and inherited-cell tag actions against a selected source tree.
+
+Every project/database is disposable. Acquisition metadata is synthetic; actual
+MySQL, triggers, Flask routes, derived indexes and synchronous durable save hooks
+are used. HTTP times include Flask serialization/client JSON decoding, exclude
+oracle work, and do not model browser concurrency/rendering. Sequential bundle
+times are sums of their separately measured requests. Profiles are separate.
+"""
+import argparse
+import copy
+import cProfile
+import datetime as dt
+import hashlib
+import json
+import math
+import os
+from pathlib import Path
+import pstats
+import resource
+import statistics
+import sys
+import tempfile
+import time
+import traceback
+
+
+def digest(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
+def percentiles(values):
+ ordered=sorted(values)
+ return {'samples_seconds':values,'p50_seconds':statistics.median(values),'p95_seconds':ordered[math.ceil(.95*len(ordered))-1],
+ 'p99_seconds':ordered[math.ceil(.99*len(ordered))-1],'maximum_seconds':max(values),'quantile_method':'nearest_rank'}
+def rule(tag,scope='effective'):return {'field':'annotations/'+scope+'/tags','operator':'contains','value':tag}
+def tags(ordinal,profile,*,cell=False):
+ if cell:return ['cell-common',f'cell-bucket:{ordinal%5}',f'cell-author:{profile}','Inherited' if profile==0 else 'inherited',f'cell-type:{ordinal%2}']
+ return ['all',f'gate:{profile}' if ordinal%(profile+2)==0 else f'rest:{profile}','QC' if ordinal%2==0 else 'qc',
+ 'é' if ordinal%3==0 else 'e\u0301',f'bucket:{ordinal%17}']
+def source_inventory(root):
+ return {str(path.relative_to(root)):digest(path) for path in sorted((root/'python').rglob('*.py'))}
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--source-root',type=Path,required=True)
+ ap.add_argument('--mysql-runtime-root',type=Path,required=True);ap.add_argument('--epochs',type=int,required=True)
+ ap.add_argument('--samples',type=int,default=20);ap.add_argument('--targets',default='epoch:1,epoch:5,epoch:20,cell:1')
+ ap.add_argument('--output',type=Path,required=True);ap.add_argument('--profile',action='store_true')
+ ap.add_argument('--page-cells',action='store_true',default=True)
+ args=ap.parse_args()
+ args.output=args.output.resolve();root=args.source_root.resolve(strict=True)
+ if args.output.exists():ap.error('Choose an unused output receipt')
+ if args.epochs<12 or args.samples<1:ap.error('Use at least12 epochs and one sample')
+ # Product and fixture imports must come from the selected immutable tree.
+ sys.path[:0]=[str(root/'python'),str(root/'docs/dev/scale-audit-2026-09-29')]
+ os.chdir(root)
+ from benchmark_service_scale import fixture,Details,uid
+ import benchmark_service_scale
+ from recording_workspace import connect,workspace_tables
+ from workspace_projects import create_project
+ import workspace_native_mysql as native
+ from workspace_mysql_runtime import _probe,runtime_spec
+ from workspace_disk_index import DiskMetadataIndex
+ from workspace_api import create_app
+ import workspace_state_snapshot as snapshot
+ runtime=_probe(args.mysql_runtime_root.resolve(strict=True),runtime_spec(root)[0]['mysql_version'])
+ def runtime_binary(name='mysqld'):
+ if name not in {'mysqld','mysql','mysqldump'}:raise ValueError('Unsupported native executable')
+ return Path(runtime[name])
+ native.native_binary=runtime_binary
+ report={'scope':__doc__,'epochs':args.epochs,'profiles':3,'tags_per_annotation':5,'protocols':2,'samples':args.samples,
+ 'source_root':str(root),'source_inventory_before':source_inventory(root),'fixture_module':str(Path(benchmark_service_scale.__file__).resolve()),
+ 'harness_sha256':digest(__file__),'started_at':dt.datetime.now(dt.timezone.utc).isoformat(),'release_ready':False,'operations':{},'checks':[],
+ 'page_cells':True,'target_specification':args.targets,'baseline_harness_sha256':digest(Path(__file__).with_name('benchmark_scientist_tag_workflow.py'))}
+ samples={};sql_samples={};app=None;dj=None;project_root=None;index=None;closed=False
+ def check(name,condition,**detail):
+ report['checks'].append({'name':name,'passed':bool(condition),**detail})
+ if not condition:raise AssertionError(name)
+ def log(message):print(message,flush=True)
+ started=time.perf_counter()
+ try:
+ with tempfile.TemporaryDirectory(prefix='rieke-tag-actions-') as directory:
+ project_root=Path(create_project(Path(directory)/'projects','Disposable scientist tag workflow')['path'])
+ try:
+ native.ensure_native_database(project_root);dj=connect({'kind':'native-project'},project_dir=project_root)
+ service,protocol=fixture(args.epochs);service.project_dir=project_root
+ service.project=json.loads((project_root/'project.json').read_text());service.config=json.loads((project_root/'catalog.json').read_text());service.dj=dj
+ service.manifests={item['source_sha256']:{'source_path':'/synthetic/'+item['filename']} for item in service.sources}
+ other=uid(-3);service.protocols[other]=copy.deepcopy(service.protocols[protocol])
+ service.protocols[other]['definition']['protocol_uuid']=other;service.protocols[other]['result']['protocol_uuid']=other
+ for value in service.protocols.values():
+ value['definition']['project_uuid']=service.project['project_uuid']
+ value['definition']['query']={'version':1,'all':[{'field':'EpochBlock.protocol_name','operator':'eq','value':'Synthetic'}]}
+ index=DiskMetadataIndex.build(project_root/'cache'/'metadata.sqlite',service.rows,Details(service.rows),service.sources,'tag-workflow-fixture-v1',service.project['project_uuid'])
+ service.disk_index=index;service.details=index.details
+ Project,_,_,_=workspace_tables(dj);project=service.project['project_uuid']
+ Project.insert1({'project_uuid':project,'name':service.project['name'],'directory':str(project_root)})
+ app=create_app(project_root,root/'.rieke-runtime/retinanalysis',service=service)
+ assert service._recovery_tracker.ready,service._recovery_tracker.reason
+ store=app.extensions['curation_store'];shared=app.extensions['shared_annotations'];authors=[uid(-100-i) for i in range(3)]
+ author_names=['Same Scientist','Same Scientist','Scientist C'];stamp=dt.datetime(2026,9,29,12)
+ ids=tuple(service.rows);ordinals={identity:i for i,identity in enumerate(ids)};cell_ordinals={identity:i for i,identity in enumerate(service.cells)}
+ report['mysql_version']=dj.conn().query('SELECT VERSION()').fetchone()[0]
+ seed=time.perf_counter();log(json.dumps({'stage':'seed','epochs':args.epochs,'source_root':str(root)}))
+ with dj.conn().transaction:
+ shared.Profile.insert([{'project_uuid':project,'profile_uuid':author,'display_name':author_names[p],'created_at':stamp,'created_by':'workflow oracle'} for p,author in enumerate(authors)])
+ batch=[]
+ for kind,identities in [('epoch',ids),('cell',tuple(service.cells))]:
+ for ordinal,identity in enumerate(identities):
+ for p,author in enumerate(authors):
+ batch.append({'project_uuid':project,'target_kind':kind,'target_uuid':identity,'profile_uuid':author,
+ 'tags':tags(ordinal,p,cell=kind=='cell'),'author_name':author_names[p],'revision':1,'updated_at':stamp})
+ if len(batch)==500:shared.Annotation.insert(batch);batch=[]
+ if batch:shared.Annotation.insert(batch);batch=[]
+ for p,current in enumerate((protocol,other)):
+ for identity in ids:
+ batch.append({'project_uuid':project,'protocol_uuid':current,'epoch_uuid':identity,'included':True,
+ 'tags':[f'dataset:{p}',*[f'dataset-tag:{i}' for i in range(4)]],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':service._fingerprints[identity]})
+ if len(batch)==500:store.Curation.insert(batch);batch=[]
+ if batch:store.Curation.insert(batch);batch=[]
+ report['seed_seconds']=time.perf_counter()-seed
+ checkpoint=time.perf_counter();snapshot.save(project_root,dj.conn(),service=service);report['seed_checkpoint_seconds']=time.perf_counter()-checkpoint
+ log(json.dumps({'stage':'timed_operations','epochs':args.epochs,'seed_seconds':report['seed_seconds']}))
+ client=app.test_client();base=f'/api/protocols/{protocol}';headers={'X-Workspace-Request':'1','Origin':'http://localhost:8766'}
+
+ targets=[]
+ for entry in args.targets.split(','):
+ kind,count=entry.split(':');count=int(count)
+ if kind not in ('epoch','cell') or count<1 or (kind=='cell' and count!=1):raise ValueError('Use epoch:N or cell:1')
+ selected=ids[:count] if kind=='epoch' else (next(iter(service.cells)),)
+ if len(selected)!=count:raise ValueError('Not enough targets')
+ targets.append({'name':f'{kind}_{count}','kind':kind,'ids':tuple(selected),'marker':f'action-{kind}-{count}'})
+ report['targets']=[{**target,'child_epoch_count':sum(service.rows[key]['cell_uuid'] in target['ids'] for key in ids) if target['kind']=='cell' else len(target['ids'])} for target in targets]
+ revisions={(target['kind'],identity,p):1 for target in targets for identity in target['ids'] for p in range(3)}
+ active=None
+ stable_predicate={'all':[rule('gate:0','epoch'),rule('gate:1','epoch'),rule('cell-common','cell'),{'not':rule('gate:2')}]}
+ stable_expected=tuple(ids[i] for i in range(args.epochs) if i%2==0 and i%3==0 and i%4!=0)
+ def sql_status():return {key:int(value) for key,value in dj.conn().query("SHOW SESSION STATUS WHERE Variable_name IN ('Rows_sent','Bytes_sent','Questions')").fetchall()}
+ def request(method,path,*,query=None,body=None):
+ response=client.get(path,query_string=query) if method=='GET' else client.post(path,json=body,headers=headers)
+ value=response.get_json()
+ if response.status_code!=200:raise AssertionError((method,path,response.status_code,value))
+ return value
+ def measure(name,callback):
+ before=sql_status();began=time.perf_counter();value=callback();elapsed=time.perf_counter()-began;after=sql_status()
+ samples.setdefault(name,[]).append(elapsed);sql_samples.setdefault(name,[]).append({key:after[key]-before[key] for key in before})
+ return value,elapsed
+ def query(predicate,**extra):return {'tag_predicate':json.dumps(predicate,ensure_ascii=False,separators=(',',':')),**extra}
+ def wanted_tags(kind,identity,p):
+ ordinal=ordinals[identity] if kind=='epoch' else cell_ordinals[identity]
+ result=tags(ordinal,p,cell=kind=='cell')
+ if active is not None and active[0]['kind']==kind and identity in active[0]['ids'] and active[1]==p:result=result+[active[0]['marker']]
+ return sorted(result)
+ def validate_row(row):
+ identity=row['epoch_uuid'];cell=service.rows[identity]['cell_uuid']
+ for kind,field,key in [('epoch','epoch_tags',identity),('cell','cell_tags',cell)]:
+ wanted=sorted((tag,author) for p,author in enumerate(authors) for tag in wanted_tags(kind,key,p))
+ assert sorted((chip['tag'],chip['profile_uuid']) for chip in row['annotations'][field])==wanted,(identity,kind)
+ assert row['curation']['revision']==1
+ assert sorted(row['curation']['tags'])==sorted(['dataset:0',*[f'dataset-tag:{j}' for j in range(4)]])
+ def page(label,predicate,wanted,offset=0,*,timed=True):
+ call=lambda:request('GET',base+'/epochs',query=query(predicate,limit=60,offset=offset,include_cells='true'))
+ value,elapsed=measure(label,call) if timed else (call(),0)
+ assert value['total']==len(wanted)
+ assert tuple(row['epoch_uuid'] for row in value['epochs'])==wanted[offset:offset+60]
+ counts={}
+ for key in wanted:
+ cell=service.rows[key]['cell_uuid'];counts[cell]=counts.get(cell,0)+1
+ assert {row['cell_uuid']:row['epochs'] for row in value['cells']}==counts
+ for row in value['epochs']:validate_row(row)
+ return value,elapsed
+ def focused(label,identity):
+ value,elapsed=measure(label,lambda:request('GET','/api/epochs/'+identity,query={'protocol_uuid':protocol}))
+ assert value['epoch_uuid']==identity;validate_row(value);return value,elapsed
+ def affected_ids(target):
+ return target['ids'] if target['kind']=='epoch' else tuple(key for key in ids if service.rows[key]['cell_uuid'] in target['ids'])
+ def change(target,add,p,*,label=None):
+ nonlocal active
+ body={'target_kind':target['kind'],'target_uuids':list(target['ids']),'profile_uuid':authors[p],
+ 'tags_add' if add else 'tags_remove':[target['marker']],
+ 'expected_revisions':{key:revisions[(target['kind'],key,p)] for key in target['ids']}}
+ call=lambda:request('POST','/api/annotations',body=body)
+ value=measure(label,call)[0] if label else call()
+ assert value['changed']==len(target['ids'])
+ for key in target['ids']:revisions[(target['kind'],key,p)]+=1
+ active=(target,p) if add else None
+ return value
+ def sql_rows(kind,keys):
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':kind}&[{'target_uuid':key} for key in keys]).to_dicts()
+ return sorted((row['target_uuid'],row['profile_uuid'],row['revision'],tuple(sorted(row['tags'])),row['author_name'],str(row['updated_at'])) for row in rows)
+ initial_epoch_row_count=len(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'})
+ check('three_direct_annotation_rows_per_epoch',initial_epoch_row_count==args.epochs*3)
+ page('cold_initial_shared_index',stable_predicate,stable_expected)
+ exact,_=service._epoch_page_identities(protocol,query(stable_predicate));check('stable_filter_exact_membership',tuple(exact)==stable_expected,count=len(exact))
+ # Warm the empty marker predicates once. Every later add/remove
+ # must invalidate and recompute the affected predicate exactly.
+ for target in targets:page('warm_empty_'+target['name'],{'all':[rule(target['marker'])]},(),timed=False)
+ for target in targets:
+ wanted=affected_ids(target);focus=wanted[0];predicate={'all':[rule(target['marker'])]}
+ child_rows=sql_rows('epoch',wanted) if target['kind']=='cell' else None
+ for iteration in range(args.samples):
+ p=iteration%3
+ for add in (True,False):
+ label=target['name']+('_add' if add else '_remove')
+ change(target,add,p,label=label+'_durable_save')
+ page(label+'_first_stable_page',stable_predicate,stable_expected)
+ focused(label+'_focused_metadata',focus)
+ expected=wanted if add else ()
+ page(label+'_affected_filter',predicate,expected)
+ exact,_=service._epoch_page_identities(protocol,query(predicate));assert tuple(exact)==expected
+ if len(expected)>60:page(label+'_affected_last_page',predicate,expected,len(expected)-60,timed=False)
+ if target['kind']=='cell':
+ direct={'all':[rule(target['marker'],'epoch')]}
+ page(label+'_direct_epoch_excludes_inheritance',direct,(),timed=False)
+ assert sql_rows('epoch',wanted)==child_rows,'Cell edit changed child annotation rows'
+ assert len(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'})==initial_epoch_row_count,'Cell edit created epoch annotation copies'
+ for key in target['ids']:
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':target['kind'],'target_uuid':key,'profile_uuid':authors[p]}).to_dicts()
+ assert len(rows)==1 and rows[0]['revision']==revisions[(target['kind'],key,p)]
+ assert sorted(rows[0]['tags'])==wanted_tags(target['kind'],key,p)
+ samples.setdefault(label+'_save_plus_first_page',[]).append(samples[label+'_durable_save'][-1]+samples[label+'_first_stable_page'][-1])
+ samples.setdefault(label+'_save_page_focus',[]).append(samples[label+'_save_plus_first_page'][-1]+samples[label+'_focused_metadata'][-1])
+ log(json.dumps({'stage':'action_cycle','epochs':args.epochs,'target':target['name'],'iteration':iteration+1}))
+ check('exact_add_remove_'+target['name'],True,samples=args.samples,targets=len(target['ids']),effective_epochs=len(wanted))
+ if target['kind']=='cell':check('cell_inheritance_without_child_annotation_copy',True,child_epochs=len(wanted),direct_rows_unchanged=len(child_rows))
+ def profile_save(target,add):
+ import re
+ connection=dj.conn();original_query=connection.query;original_fsync=os.fsync;sql_calls=[];sync_calls=[]
+ def traced_query(statement,*positional,**keywords):
+ began=time.perf_counter()
+ try:return original_query(statement,*positional,**keywords)
+ finally:sql_calls.append({'sql_shape':re.sub(r"'[^']*'","'?'",str(statement))[:240],'seconds':time.perf_counter()-began})
+ def traced_fsync(descriptor):
+ began=time.perf_counter()
+ try:return original_fsync(descriptor)
+ finally:sync_calls.append(time.perf_counter()-began)
+ profiler=cProfile.Profile();connection.query=traced_query;os.fsync=traced_fsync
+ try:profiler.runcall(change,target,add,0)
+ finally:connection.query=original_query;os.fsync=original_fsync
+ stats=pstats.Stats(profiler);rows=[]
+ for key,value in sorted(stats.stats.items(),key=lambda item:item[1][3],reverse=True):
+ path=Path(key[0]);rows.append({'file':str(path.relative_to(root)) if path.is_absolute() and path.is_relative_to(root) else key[0],
+ 'line':key[1],'function':key[2],'primitive_calls':value[0],'calls':value[1],'self_seconds':value[2],'cumulative_seconds':value[3]})
+ label=target['name']+('_add' if add else '_remove')
+ report.setdefault('cpu_profiles',{})[label]={'top_functions':rows[:100],
+ 'sql_and_durability_functions':[row for row in rows if any(word in row['function'].lower() for word in ('fsync','commit','query','execute','capture','recovery','atomic_write'))],
+ 'native_sql_calls':sql_calls,'python_fsync_calls_seconds':sync_calls,
+ 'limits':'Separate instrumented sample. Native SQL timing wraps query dispatch; fetch costs appear in cProfile. Python fsync does not intercept SQLite internal fsync; SQLite commit time appears in cProfile.'}
+ page(label+'_profile_postcheck',stable_predicate,stable_expected,timed=False)
+ if args.profile:
+ for target in targets:
+ for add in (True,False):profile_save(target,add)
+ check('final_markers_removed',active is None)
+ report['workflow_peak_rss_before_durable_oracle_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ began=time.perf_counter();state=snapshot.load(project_root/'app-state.json');report['durable_snapshot_verify_seconds']=time.perf_counter()-began
+ saved={(row['target_kind'],row['target_uuid'],row['profile_uuid']):row for row in state['tables']['shared_annotation']}
+ for (kind,key,p),revision in revisions.items():
+ row=saved[(kind,key,authors[p])]
+ assert row['revision']==revision and sorted(row['tags'])==wanted_tags(kind,key,p)
+ check('durable_mirror_exact_targets_revisions_and_removal',True,records=len(revisions))
+ check('durable_mirror_direct_row_count_unchanged',sum(row['target_kind']=='epoch' for row in saved.values())==args.epochs*3)
+ check('independent_protocol_curation_untouched',len(state['tables']['curation'])==args.epochs*2 and all(row['revision']==1 for row in state['tables']['curation']))
+ loaded={name:str(Path(module.__file__).resolve()) for name,module in sys.modules.items() if (name.startswith('workspace_') or name=='recording_workspace') and getattr(module,'__file__',None)}
+ check('all_product_modules_loaded_from_selected_source',all(Path(path).is_relative_to(root) for path in loaded.values()))
+ report['loaded_product_modules']=loaded
+ finally:
+ if app is not None:
+ shared=app.extensions.get('shared_annotations');derived=getattr(shared,'_shared_tag_index',None)
+ if derived is not None:derived.close()
+ lock=app.extensions.get('app_state_session_lock')
+ if lock is not None:lock.close()
+ if index is not None:index.close()
+ if dj is not None:dj.conn().close()
+ if project_root is not None:closed=native.stop_native_database(project_root)
+ report['owned_native_runtime_stopped']=closed
+ except Exception as error:
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};log(traceback.format_exc())
+ report['operations']={name:{**percentiles(values),'sql_session_deltas':sql_samples.get(name,[])} for name,values in samples.items()}
+ report['source_inventory_after']=source_inventory(root);report['source_unchanged']=report['source_inventory_before']==report['source_inventory_after']
+ report['harness_unchanged']=digest(__file__)==report['harness_sha256']
+ report['seconds']=time.perf_counter()-started;report['finished_at']=dt.datetime.now(dt.timezone.utc).isoformat();report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['passed']=not report.get('error') and closed and report['source_unchanged'] and report['harness_unchanged'] and all(item['passed'] for item in report['checks'])
+ args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');log(json.dumps({'stage':'done','epochs':args.epochs,'passed':report['passed'],'seconds':report['seconds'],'output':str(args.output)}))
+ return int(not report['passed'])
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_tag_collation.py b/docs/dev/scale-audit-2026-09-29/benchmark_tag_collation.py
new file mode 100644
index 0000000..5b82f2b
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_tag_collation.py
@@ -0,0 +1,122 @@
+"""Disposable same-server old/new trigger validation timing; no UI timing claim."""
+import hashlib,json,statistics,tempfile,time,uuid
+from pathlib import Path
+import pymysql
+from test_workspace_state_generation import NativeConnection
+import workspace_native_tag_lookup as lookup_module
+from workspace_projects import create_project
+import workspace_native_mysql as native
+
+
+def baseline_manifest():
+ def valid(alias):
+ tag=lookup_module._tag(alias)
+ whitespace=[chr(n).encode('utf-8').hex() for n in range(0x3001) if chr(n).isspace()]
+ ends=','.join('0x'+value for value in whitespace)
+ controls=' OR '.join(f'LOCATE(CHAR({n}),{tag})>0' for n in range(32))
+ invalid=(f"JSON_TYPE(JSON_EXTRACT({alias}.tags,CONCAT('$[',j.ordinal-1,']')))!='STRING' "
+ f'OR CHAR_LENGTH({tag}) NOT BETWEEN 1 AND 255 OR BINARY LEFT({tag},1) IN ({ends}) '
+ f'OR BINARY RIGHT({tag},1) IN ({ends}) OR {controls}')
+ return (f"IF JSON_TYPE({alias}.tags)!='ARRAY' OR JSON_LENGTH({alias}.tags)>100 OR EXISTS ("
+ f"SELECT 1 FROM JSON_TABLE({alias}.tags,'$[*]' COLUMNS(ordinal FOR ORDINALITY)) j WHERE {invalid}) "
+ "THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Invalid canonical shared annotation tags'; END IF;")
+ def insert(alias):
+ return (valid(alias)+f' INSERT INTO `{lookup_module.SCHEMA}`.`{lookup_module.TABLE}` ({",".join(lookup_module.COLUMNS)}) '
+ f'SELECT {alias}.project_uuid,{alias}.target_kind,{alias}.target_uuid,{alias}.profile_uuid,'
+ f"CAST({lookup_module._tag(alias)} AS BINARY) FROM JSON_TABLE({alias}.tags,'$[*]' "
+ "COLUMNS(ordinal FOR ORDINALITY)) j;")
+ delete,dirty=lookup_module._delete,lookup_module._dirty
+ return {lookup_module.PREFIX+suffix:{'table':'shared_annotation','event':event,'timing':'AFTER','body':'BEGIN '+body+' END'}
+ for suffix,event,body in [('ai','INSERT',insert('NEW')+' '+dirty('NEW')),
+ ('au','UPDATE',delete('OLD')+' '+insert('NEW')+' '+dirty('OLD')+' '+dirty('NEW')),
+ ('ad','DELETE',delete('OLD')+' '+dirty('OLD'))]}
+
+
+def optimized_manifest():
+ def valid(alias):
+ tag='j.tag'
+ whitespace=[chr(n).encode('utf-8').hex() for n in range(0x3001) if chr(n).isspace()]
+ ends=','.join('0x'+value for value in whitespace)
+ invalid=(f"JSON_TYPE(JSON_EXTRACT({alias}.tags,CONCAT('$[',j.ordinal-1,']')))!='STRING' "
+ f'OR CHAR_LENGTH({tag}) NOT BETWEEN 1 AND 255 OR BINARY LEFT({tag},1) IN ({ends}) '
+ f'OR BINARY RIGHT({tag},1) IN ({ends}) '
+ f"OR REGEXP_LIKE({tag},CONVERT(0x5b002d1f5d USING utf8mb4),'c')")
+ return (f"IF JSON_TYPE({alias}.tags)!='ARRAY' OR JSON_LENGTH({alias}.tags)>100 OR EXISTS ("
+ f"SELECT 1 FROM JSON_TABLE({alias}.tags,'$[*]' COLUMNS(ordinal FOR ORDINALITY,"
+ f"tag VARCHAR(1020) CHARACTER SET utf8mb4 PATH '$' ERROR ON ERROR)) j WHERE {invalid}) "
+ "THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Invalid canonical shared annotation tags'; END IF;")
+ def insert(alias):
+ return (valid(alias)+f' INSERT INTO `{lookup_module.SCHEMA}`.`{lookup_module.TABLE}` ({",".join(lookup_module.COLUMNS)}) '
+ f'SELECT {alias}.project_uuid,{alias}.target_kind,{alias}.target_uuid,{alias}.profile_uuid,'
+ f"CAST(j.tag AS BINARY) FROM JSON_TABLE({alias}.tags,'$[*]' "
+ "COLUMNS(tag VARCHAR(1020) CHARACTER SET utf8mb4 PATH '$' ERROR ON ERROR)) j;")
+ delete,dirty=lookup_module._delete,lookup_module._dirty
+ return {lookup_module.PREFIX+suffix:{'table':'shared_annotation','event':event,'timing':'AFTER','body':'BEGIN '+body+' END'}
+ for suffix,event,body in [('ai','INSERT',insert('NEW')+' '+dirty('NEW')),
+ ('au','UPDATE',delete('OLD')+' '+insert('NEW')+' '+dirty('OLD')+' '+dirty('NEW')),
+ ('ad','DELETE',delete('OLD')+' '+dirty('OLD'))]}
+
+
+def run(output):
+ report={'kind':'same-server-native-sql-trigger-microbenchmark','samples':10,'targets_per_transaction':10,
+ 'canonical_tags_per_row':6,'includes':'10 canonical UPDATE statements and native transaction commit; no HTTP/event/provenance reads'}
+ with tempfile.TemporaryDirectory(prefix='rieke-tag-trigger-timing-') as directory:
+ root=Path(create_project(Path(directory)/'projects','Private tag trigger timing')['path']);raw=None
+ try:
+ native.ensure_native_database(root);raw=pymysql.connect(**native.connection_parameters(root),autocommit=True);conn=NativeConnection(raw)
+ conn.query('CREATE DATABASE recording_workspace')
+ conn.query('CREATE TABLE recording_workspace.shared_annotation (project_uuid varchar(36),target_kind varchar(8),target_uuid varchar(36),profile_uuid varchar(36),tags JSON NOT NULL,revision INT NOT NULL,PRIMARY KEY(project_uuid,target_kind,target_uuid,profile_uuid)) ENGINE=InnoDB')
+ conn.query('CREATE TABLE recording_workspace.app_state_generation (project_uuid varchar(36),scope_kind varchar(32),scope_uuid varchar(36),generation BIGINT,PRIMARY KEY(project_uuid,scope_kind,scope_uuid)) ENGINE=InnoDB')
+ project,profile=str(uuid.uuid4()),str(uuid.uuid4());all_ids=[str(uuid.uuid4()) for _ in range(1500)];ids=all_ids[:10]
+ conn.query('INSERT INTO recording_workspace.app_state_generation VALUES (%s,%s,%s,0)',(project,'shared_annotations',project))
+ for key in all_ids:conn.query('INSERT INTO recording_workspace.shared_annotation VALUES (%s,%s,%s,%s,%s,1)',(project,'epoch',key,profile,json.dumps(['all','bucket:7','ON','é','profile:0','review:0'])))
+ proof={'project_uuid':project,'tables':{'shared_annotation':{'count':1500,'xor':'0'*64}}}
+ lookup=lookup_module.bootstrap(conn,project,proof);assert lookup.ready,lookup.reason
+ import runpy
+ validation=runpy.run_path(str(Path(__file__).with_name('benchmark_tag_trigger_validation.py')))
+ before=validation['optimized_manifest']()
+ fixed={name:dict(spec) for name,spec in before.items()}
+ for spec in fixed.values():
+ for alias in ('OLD','NEW'):
+ for field in ('project_uuid','target_kind','target_uuid','profile_uuid'):
+ spec['body']=spec['body'].replace(field+'='+alias+'.'+field,field+'=CONVERT('+alias+'.'+field+' USING ascii) COLLATE ascii_bin')
+ manifests={'before':before,'fixed_comparison':fixed}
+ report['mysql_version']=conn.query('SELECT VERSION()').fetchone()[0]
+ report['column_collations']=conn.query("SELECT TABLE_NAME,COLUMN_NAME,COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='recording_workspace' AND TABLE_NAME IN ('shared_annotation','app_shared_tag_lookup')",as_dict=True).fetchall()
+ report['delete_plans']={}
+ for variant in ('before','fixed_comparison'):
+ delete=validation['historical_delete']('OLD')
+ if variant=='fixed_comparison':
+ for field in ('project_uuid','target_kind','target_uuid','profile_uuid'):
+ delete=delete.replace(field+'=OLD.'+field,field+'=CONVERT(OLD.'+field+' USING ascii) COLLATE ascii_bin')
+ for field in ('project_uuid','target_kind','target_uuid','profile_uuid'):delete=delete.replace('OLD.'+field,'p_'+field)
+ declarations=','.join('IN p_'+field+' VARCHAR(36) CHARACTER SET utf8mb4' for field in ('project_uuid','target_kind','target_uuid','profile_uuid'))
+ conn.query('CREATE PROCEDURE recording_workspace.plan_'+variant+'('+declarations+') BEGIN EXPLAIN FORMAT=JSON '+delete+' END')
+ report['delete_plans'][variant]=json.loads(conn.query('CALL recording_workspace.plan_'+variant+'(%s,%s,%s,%s)',(project,'epoch',ids[0],profile)).fetchone()[0])
+
+ for label,manifest in manifests.items():
+ for name,spec in manifest.items():
+ conn.query('DROP TRIGGER recording_workspace.'+name)
+ conn.query('CREATE TRIGGER recording_workspace.'+name+' AFTER '+spec['event']+' ON recording_workspace.shared_annotation FOR EACH ROW '+spec['body'])
+ report[label]={'manifest_sha256':hashlib.sha256(json.dumps(manifest,sort_keys=True).encode()).hexdigest()}
+ samples=[]
+ before_reads=conn.query("SHOW SESSION STATUS WHERE Variable_name='Handler_read_next'").fetchone()[1]
+ for sample in range(15):
+ values=json.dumps(['all','bucket:7','ON','é','profile:0','review:'+str(sample%2)])
+ started=time.perf_counter()
+ with conn.transaction:
+ for key in ids:conn.query('UPDATE recording_workspace.shared_annotation SET tags=%s,revision=revision+1 WHERE project_uuid=%s AND target_kind=%s AND target_uuid=%s AND profile_uuid=%s',(values,project,'epoch',key,profile))
+ elapsed=time.perf_counter()-started
+ if sample>=5:samples.append(elapsed)
+ report[label]['handler_read_next']=int(conn.query("SHOW SESSION STATUS WHERE Variable_name='Handler_read_next'").fetchone()[1])-int(before_reads)
+ report[label].update(seconds=samples,median_seconds=statistics.median(samples),p95_seconds=sorted(samples)[9])
+ assert lookup.targets('review:1')==set()
+ Path(output).write_text(json.dumps(report,indent=2)+'\n');print(json.dumps({label:{k:v for k,v in value.items() if k!='seconds'} for label,value in report.items() if isinstance(value,dict)}),flush=True)
+ finally:
+ if raw is not None:raw.close()
+ native.stop_native_database(root)
+
+
+if __name__=='__main__':
+ import sys
+ run(sys.argv[1])
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py b/docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py
new file mode 100644
index 0000000..11ebfe7
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py
@@ -0,0 +1,340 @@
+"""Native tag-ten, tag-another-ten, filter-twenty and hierarchical cell/epoch workflow.
+
+Every project/database is disposable. Acquisition metadata is synthetic; actual
+MySQL, triggers, Flask routes, derived indexes and each version's save hooks
+are used. Native SQL commits are checked immediately; any background recovery
+mirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude
+oracle work, and do not model browser concurrency/rendering. Sequential bundle
+times are sums of their separately measured requests. Profiles are separate.
+"""
+import argparse
+import copy
+import cProfile
+import datetime as dt
+import hashlib
+import json
+import math
+import os
+from pathlib import Path
+import pstats
+import resource
+import statistics
+import sys
+import tempfile
+import time
+import traceback
+
+
+def digest(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
+def percentiles(values):
+ ordered=sorted(values)
+ return {'samples_seconds':values,'p50_seconds':statistics.median(values),'p95_seconds':ordered[math.ceil(.95*len(ordered))-1],
+ 'p99_seconds':ordered[math.ceil(.99*len(ordered))-1],'maximum_seconds':max(values),'quantile_method':'nearest_rank'}
+def rule(tag,scope='effective'):return {'field':'annotations/'+scope+'/tags','operator':'contains','value':tag}
+def tags(ordinal,profile,*,cell=False):
+ if cell:return ['cell-common',f'cell-bucket:{ordinal%5}',f'cell-author:{profile}','Inherited' if profile==0 else 'inherited',f'cell-type:{ordinal%2}']
+ return ['all',f'gate:{profile}' if ordinal%(profile+2)==0 else f'rest:{profile}','QC' if ordinal%2==0 else 'qc',
+ 'é' if ordinal%3==0 else 'e\u0301',f'bucket:{ordinal%17}']
+def source_inventory(root):
+ return {str(path.relative_to(root)):digest(path) for path in sorted((root/'python').rglob('*.py'))}
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--source-root',type=Path,required=True)
+ ap.add_argument('--mysql-runtime-root',type=Path,required=True);ap.add_argument('--epochs',type=int,required=True)
+ ap.add_argument('--samples',type=int,default=5);ap.add_argument('--targets',default='tag10_then10_filter20_and_cell_epoch_hierarchy')
+ ap.add_argument('--output',type=Path,required=True);ap.add_argument('--profile',action='store_true')
+ ap.add_argument('--page-cells',action='store_true',default=True)
+ args=ap.parse_args()
+ args.output=args.output.resolve();root=args.source_root.resolve(strict=True)
+ if args.output.exists():ap.error('Choose an unused output receipt')
+ if args.epochs<200 or args.samples<1:ap.error('Use at least200 epochs and one sample')
+ # Product and fixture imports must come from the selected immutable tree.
+ sys.path[:0]=[str(root/'python'),str(root/'docs/dev/scale-audit-2026-09-29')]
+ os.chdir(root)
+ from benchmark_service_scale import fixture,Details,uid
+ import benchmark_service_scale
+ from recording_workspace import connect,workspace_tables
+ from workspace_projects import create_project
+ import workspace_native_mysql as native
+ from workspace_mysql_runtime import _probe,runtime_spec
+ from workspace_disk_index import DiskMetadataIndex
+ from workspace_api import create_app
+ import workspace_state_snapshot as snapshot
+ runtime=_probe(args.mysql_runtime_root.resolve(strict=True),runtime_spec(root)[0]['mysql_version'])
+ def runtime_binary(name='mysqld'):
+ if name not in {'mysqld','mysql','mysqldump'}:raise ValueError('Unsupported native executable')
+ return Path(runtime[name])
+ native.native_binary=runtime_binary
+ report={'scope':__doc__,'epochs':args.epochs,'profiles':3,'tags_per_annotation':5,'protocols':2,'samples':args.samples,
+ 'source_root':str(root),'source_inventory_before':source_inventory(root),'fixture_module':str(Path(benchmark_service_scale.__file__).resolve()),
+ 'harness_sha256':digest(__file__),'started_at':dt.datetime.now(dt.timezone.utc).isoformat(),'release_ready':False,'operations':{},'checks':[],
+ 'page_cells':True,'target_specification':args.targets,'baseline_harness_sha256':digest(Path(__file__).with_name('benchmark_scientist_tag_workflow.py'))}
+ samples={};sql_samples={};app=None;dj=None;project_root=None;index=None;closed=False
+ def check(name,condition,**detail):
+ report['checks'].append({'name':name,'passed':bool(condition),**detail})
+ if not condition:raise AssertionError(name)
+ def log(message):print(message,flush=True)
+ started=time.perf_counter()
+ try:
+ with tempfile.TemporaryDirectory(prefix='rieke-tag-sequence-') as directory:
+ project_root=Path(create_project(Path(directory)/'projects','Disposable scientist tag workflow')['path'])
+ try:
+ native.ensure_native_database(project_root);dj=connect({'kind':'native-project'},project_dir=project_root)
+ service,protocol=fixture(args.epochs);service.project_dir=project_root
+ service.project=json.loads((project_root/'project.json').read_text());service.config=json.loads((project_root/'catalog.json').read_text());service.dj=dj
+ service.manifests={item['source_sha256']:{'source_path':'/synthetic/'+item['filename']} for item in service.sources}
+ other=uid(-3);service.protocols[other]=copy.deepcopy(service.protocols[protocol])
+ service.protocols[other]['definition']['protocol_uuid']=other;service.protocols[other]['result']['protocol_uuid']=other
+ for value in service.protocols.values():
+ value['definition']['project_uuid']=service.project['project_uuid']
+ value['definition']['query']={'version':1,'all':[{'field':'EpochBlock.protocol_name','operator':'eq','value':'Synthetic'}]}
+ index=DiskMetadataIndex.build(project_root/'cache'/'metadata.sqlite',service.rows,Details(service.rows),service.sources,'tag-workflow-fixture-v1',service.project['project_uuid'])
+ service.disk_index=index;service.details=index.details
+ Project,_,_,_=workspace_tables(dj);project=service.project['project_uuid']
+ Project.insert1({'project_uuid':project,'name':service.project['name'],'directory':str(project_root)})
+ app=create_app(project_root,root/'.rieke-runtime/retinanalysis',service=service)
+ assert service._recovery_tracker.ready,service._recovery_tracker.reason
+ store=app.extensions['curation_store'];shared=app.extensions['shared_annotations'];authors=[uid(-100-i) for i in range(3)]
+ author_names=['Same Scientist','Same Scientist','Scientist C'];stamp=dt.datetime(2026,9,29,12)
+ ids=tuple(service.rows);ordinals={identity:i for i,identity in enumerate(ids)};cell_ordinals={identity:i for i,identity in enumerate(service.cells)}
+ report['mysql_version']=dj.conn().query('SELECT VERSION()').fetchone()[0]
+ seed=time.perf_counter();log(json.dumps({'stage':'seed','epochs':args.epochs,'source_root':str(root)}))
+ with dj.conn().transaction:
+ shared.Profile.insert([{'project_uuid':project,'profile_uuid':author,'display_name':author_names[p],'created_at':stamp,'created_by':'workflow oracle'} for p,author in enumerate(authors)])
+ batch=[]
+ for kind,identities in [('epoch',ids),('cell',tuple(service.cells))]:
+ for ordinal,identity in enumerate(identities):
+ for p,author in enumerate(authors):
+ batch.append({'project_uuid':project,'target_kind':kind,'target_uuid':identity,'profile_uuid':author,
+ 'tags':tags(ordinal,p,cell=kind=='cell'),'author_name':author_names[p],'revision':1,'updated_at':stamp})
+ if len(batch)==500:shared.Annotation.insert(batch);batch=[]
+ if batch:shared.Annotation.insert(batch);batch=[]
+ for p,current in enumerate((protocol,other)):
+ for identity in ids:
+ batch.append({'project_uuid':project,'protocol_uuid':current,'epoch_uuid':identity,'included':True,
+ 'tags':[f'dataset:{p}',*[f'dataset-tag:{i}' for i in range(4)]],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':service._fingerprints[identity]})
+ if len(batch)==500:store.Curation.insert(batch);batch=[]
+ if batch:store.Curation.insert(batch);batch=[]
+ report['seed_seconds']=time.perf_counter()-seed
+ checkpoint=time.perf_counter();snapshot.save(project_root,dj.conn(),service=service);report['seed_checkpoint_seconds']=time.perf_counter()-checkpoint
+ log(json.dumps({'stage':'timed_operations','epochs':args.epochs,'seed_seconds':report['seed_seconds']}))
+ client=app.test_client();base=f'/api/protocols/{protocol}';headers={'X-Workspace-Request':'1','Origin':'http://localhost:8766'}
+
+
+ import inspect
+ import threading
+ app_db_lock=inspect.getclosurevars(app.view_functions['annotation_batch_read']).nonlocals['db_lock']
+ first=ids[:10];second=ids[100:110];selected=first+second;cell=service.rows[first[0]]['cell_uuid']
+ children=tuple(key for key in ids if service.rows[key]['cell_uuid']==cell)
+ assert len(children)==100 and service.rows[second[0]]['cell_uuid']!=cell
+ epoch_marker='sequence-selected';cell_marker='sequence-cell'
+ epoch_predicate={'all':[rule(epoch_marker,'epoch')]}
+ cell_predicate={'all':[rule(cell_marker,'cell')]}
+ hierarchy={'all':[rule(cell_marker,'cell'),rule(epoch_marker,'epoch')]}
+ model={(kind,key,p):set() for kind,keys in [('epoch',selected),('cell',(cell,))] for key in keys for p in range(3)}
+ revisions={key:1 for key in model}
+ report['selected_epoch_uuids']={'first10':first,'second10':second,'cell':cell,'cell_children':children}
+ report['backup_receipts']=[]
+ def sql_status():
+ with app_db_lock:return {key:int(value) for key,value in dj.conn().query("SHOW SESSION STATUS WHERE Variable_name IN ('Bytes_sent','Questions')").fetchall()}
+ def request(method,path,*,query=None,body=None):
+ response=client.get(path,query_string=query) if method=='GET' else client.post(path,json=body,headers=headers)
+ value=response.get_json()
+ if response.status_code!=200:raise AssertionError((method,path,response.status_code,value))
+ return value
+ def measure(name,callback):
+ before=sql_status();began=time.perf_counter();value=callback();elapsed=time.perf_counter()-began;after=sql_status()
+ samples.setdefault(name,[]).append(elapsed);sql_samples.setdefault(name,[]).append({key:after[key]-before[key] for key in before})
+ return value,elapsed
+ def wanted_tags(kind,key,p):
+ ordinal=ordinals[key] if kind=='epoch' else cell_ordinals[key]
+ return sorted([*tags(ordinal,p,cell=kind=='cell'),*model.get((kind,key,p),())])
+ def validate_target(row,kind,key):
+ assert row['target_uuid']==key and row['target_kind']==kind
+ assert row['revisions']=={author:revisions.get((kind,key,p),1) for p,author in enumerate(authors)}
+ assert sorted((chip['tag'],chip['profile_uuid']) for chip in row['tags'])==sorted((tag,author) for p,author in enumerate(authors) for tag in wanted_tags(kind,key,p))
+ def preflight(label,keys,p,*,timed=True):
+ call=lambda:request('POST','/api/annotations/read',body={'target_kind':'epoch','target_uuids':list(keys)})
+ value,_=measure(label,call) if timed else (call(),0)
+ assert set(value['targets'])==set(keys)
+ for key,row in value['targets'].items():validate_target(row,'epoch',key)
+ return {key:value['targets'][key]['revisions'][authors[p]] for key in keys}
+ def change(label,kind,keys,p,marker,add,expected=None,*,timed=True):
+ body={'target_kind':kind,'target_uuids':list(keys),'profile_uuid':authors[p],
+ 'tags_add':[marker] if add else [],'tags_remove':[] if add else [marker],
+ 'expected_revisions':expected or {key:revisions[(kind,key,p)] for key in keys}}
+ call=lambda:request('POST','/api/annotations',body=body)
+ value,_=measure(label,call) if timed else (call(),0)
+ assert value['changed']==len(keys)
+ if 'persistence' in value:
+ assert value['persistence']['database']=='committed',value
+ report['backup_receipts'].append({'operation':label,'persistence':value['persistence']})
+ for key in keys:
+ revisions[(kind,key,p)]+=1
+ if add:model[(kind,key,p)].add(marker)
+ else:model[(kind,key,p)].remove(marker)
+ assert set(value['targets'])==set(keys)
+ for key,row in value['targets'].items():validate_target(row,kind,key)
+ # The oracle uses the application's actual SQL lock so the
+ # candidate's background mirror cannot race this connection.
+ with app_db_lock:
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':kind,'profile_uuid':authors[p]}&[{'target_uuid':key} for key in keys]).to_dicts()
+ assert {row['target_uuid'] for row in rows}==set(keys)
+ for row in rows:
+ key=row['target_uuid'];assert row['revision']==revisions[(kind,key,p)] and sorted(row['tags'])==wanted_tags(kind,key,p)
+ return value
+ def query(predicate,**extra):return {'tag_predicate':json.dumps(predicate,separators=(',',':')),**extra}
+ def page(label,predicate,wanted,*,offset=0,timed=True):
+ call=lambda:request('GET',base+'/epochs',query=query(predicate,limit=60,offset=offset,include_cells='true'))
+ value,elapsed=measure(label,call) if timed else (call(),0)
+ assert value['total']==len(wanted)
+ assert tuple(row['epoch_uuid'] for row in value['epochs'])==wanted[offset:offset+60]
+ counts={}
+ for key in wanted:
+ target_cell=service.rows[key]['cell_uuid'];counts[target_cell]=counts.get(target_cell,0)+1
+ assert {row['cell_uuid']:row['epochs'] for row in value['cells']}==counts
+ for row in value['epochs']:
+ key=row['epoch_uuid'];target_cell=service.rows[key]['cell_uuid']
+ for kind,field,target in [('epoch','epoch_tags',key),('cell','cell_tags',target_cell)]:
+ assert sorted((chip['tag'],chip['profile_uuid']) for chip in row['annotations'][field])==sorted((tag,author) for p,author in enumerate(authors) for tag in wanted_tags(kind,target,p))
+ assert row['curation']['revision']==1
+ assert sorted(row['curation']['tags'])==sorted(['dataset:0',*[f'dataset-tag:{j}' for j in range(4)]])
+ with app_db_lock:
+ exact,_=service._epoch_page_identities(protocol,query(predicate));assert tuple(exact)==wanted
+ return value,elapsed
+ def child_sql():
+ with app_db_lock:
+ rows=(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'}&[{'target_uuid':key} for key in children]).to_dicts()
+ return sorted((row['target_uuid'],row['profile_uuid'],row['revision'],tuple(sorted(row['tags'])),row['author_name'],str(row['updated_at'])) for row in rows)
+ # Index construction belongs to preparation, outside all action
+ # distributions. Record it separately instead of hiding its cost.
+ prepared=time.perf_counter()
+ import importlib.util
+ prepare_project_annotations=None
+ if importlib.util.find_spec('workspace_annotation_preparation') is not None:
+ from workspace_annotation_preparation import prepare_project_annotations
+ with app_db_lock:
+ report['project_annotation_preparation']=prepare_project_annotations(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'))
+ assert report['project_annotation_preparation']['status']=='ready',report['project_annotation_preparation']
+ page('preparation_epoch_filter',epoch_predicate,())
+ page('preparation_cell_filter',cell_predicate,())
+ page('preparation_hierarchical_filter',hierarchy,())
+ report['preparation_index_seconds']=time.perf_counter()-prepared
+ check('preparation_exact_empty_memberships',True)
+ report['bundle_definitions']={
+ 'tag10_then10_filter20_http_total':['first10_revision_preflight','first10_save','second10_revision_preflight','second10_save','filter20'],
+ 'cell_tag_then_cell_filter_http_total':['cell_save','filter_cell100'],
+ 'first10_selected_action_http_total':['first10_revision_preflight','first10_save'],
+ 'second10_selected_action_http_total':['second10_revision_preflight','second10_save']}
+ for iteration in range(args.samples):
+ p=iteration%3
+ expected=preflight('first10_revision_preflight',first,p)
+ change('first10_save','epoch',first,p,epoch_marker,True,expected)
+ expected=preflight('second10_revision_preflight',second,p)
+ change('second10_save','epoch',second,p,epoch_marker,True,expected)
+ value,_=page('filter20',epoch_predicate,selected)
+ before_children=child_sql()
+ # The rendered page already carries cell revisions, as the
+ # direct-cell editor does; no selected-epoch preflight.
+ cell_expected={cell:value['epochs'][0]['annotations']['revisions']['cell'][authors[p]]}
+ change('cell_save','cell',(cell,),p,cell_marker,True,cell_expected)
+ page('filter_cell100',cell_predicate,children)
+ page('filter_cell100_last_page',cell_predicate,children,offset=40,timed=False)
+ page('filter_cell_and_epoch10',hierarchy,first)
+ page('oracle_cell_marker_not_direct',{'all':[rule(cell_marker,'epoch')]},(),timed=False)
+ assert child_sql()==before_children,'Cell add changed child annotation records'
+ expected=preflight('remove_first10_revision_preflight',first,p)
+ change('remove_first10_save','epoch',first,p,epoch_marker,False,expected)
+ page('filter_hierarchy_after_epoch_remove',hierarchy,())
+ page('filter_remaining10',epoch_predicate,second)
+ expected=preflight('remove_second10_revision_preflight',second,p)
+ change('remove_second10_save','epoch',second,p,epoch_marker,False,expected)
+ page('filter_after_all_epoch_removals',epoch_predicate,())
+ before_children=child_sql()
+ change('remove_cell_save','cell',(cell,),p,cell_marker,False)
+ page('filter_after_cell_removal',cell_predicate,())
+ assert child_sql()==before_children,'Cell removal changed child annotation records'
+ with app_db_lock:assert len(shared.Annotation&{'project_uuid':project,'target_kind':'epoch'})==args.epochs*3
+ for label,parts in report['bundle_definitions'].items():samples.setdefault(label,[]).append(sum(samples[part][-1] for part in parts))
+ log(json.dumps({'stage':'sequence_cycle','epochs':args.epochs,'iteration':iteration+1}))
+ def flush_backup():
+ scheduler=app.extensions.get('backup_scheduler')
+ if scheduler is not None:
+ scheduler.flush();report['final_backup_status']=scheduler.status()
+ if args.profile:
+ for name,keys in [('first10',first),('second10',second)]:
+ expected=preflight('profile_'+name+'_preflight',keys,0,timed=False)
+ change('profile_'+name+'_save','epoch',keys,0,epoch_marker,True,expected,timed=False)
+ flush_backup()
+ import re
+ connection=dj.conn();original_query=connection.query;owner=threading.get_ident();sql_calls=[]
+ def traced_query(statement,*positional,**keywords):
+ began=time.perf_counter()
+ try:return original_query(statement,*positional,**keywords)
+ finally:sql_calls.append({'thread':'filter' if threading.get_ident()==owner else 'background','sql_shape':re.sub(r"'[^']*'","'?'",str(statement))[:240],'seconds':time.perf_counter()-began})
+ profiler=cProfile.Profile();connection.query=traced_query
+ try:profiled=profiler.runcall(lambda:request('GET',base+'/epochs',query=query(epoch_predicate,limit=60,offset=0,include_cells='true')))
+ finally:connection.query=original_query
+ assert profiled['total']==20 and tuple(row['epoch_uuid'] for row in profiled['epochs'])==selected
+ stats=pstats.Stats(profiler);rows=[]
+ for key,value in sorted(stats.stats.items(),key=lambda item:item[1][3],reverse=True):
+ path=Path(key[0]);rows.append({'file':str(path.relative_to(root)) if path.is_absolute() and path.is_relative_to(root) else key[0],
+ 'line':key[1],'function':key[2],'primitive_calls':value[0],'calls':value[1],'self_seconds':value[2],'cumulative_seconds':value[3]})
+ report['filter20_cpu_profile']={'top_functions':rows[:100],'native_sql_calls':sql_calls,'limits':'Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap.'}
+ page('profile_filter20_verify',epoch_predicate,selected,timed=False)
+ if prepare_project_annotations is not None:
+ flush_backup();began=time.perf_counter()
+ with app_db_lock:
+ checkpoint=prepare_project_annotations(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'))
+ assert checkpoint['status']=='ready' and checkpoint['checkpoint']['saved'],checkpoint
+ previous_builds=shared._shared_tag_index.stats['full_builds']
+ shared._shared_tag_index.close()
+ reopened=prepare_project_annotations(service,store,shared,protocol_state=app.extensions.get('protocol_state_reader'),reuse=True)
+ assert reopened['status']=='ready' and reopened['checkpoint_restore']['restored'],reopened
+ after_builds=shared._shared_tag_index.stats['full_builds']
+ assert after_builds in (0,previous_builds),(previous_builds,after_builds)
+ report['same_server_checkpoint_reopen']={'seconds':time.perf_counter()-began,'checkpoint':checkpoint,'reopened':reopened,'full_builds_before':previous_builds,'full_builds_after':after_builds,'scope':'Shared derived index closed and reopened within the same native server; not a native process restart.'}
+ page('same_server_reopened_filter20',epoch_predicate,selected)
+ check('same_server_checkpoint_restores_exact_twenty_without_rebuild',True)
+ for name,keys in [('first10',first),('second10',second)]:
+ change('profile_remove_'+name,'epoch',keys,0,epoch_marker,False,timed=False)
+ flush_backup()
+ check('canonical_final_markers_removed',not any(model.values()))
+ report['workflow_peak_rss_before_durable_oracle_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ began=time.perf_counter();state=snapshot.load(project_root/'app-state.json');report['durable_snapshot_verify_seconds']=time.perf_counter()-began
+ saved={(row['target_kind'],row['target_uuid'],row['profile_uuid']):row for row in state['tables']['shared_annotation']}
+ for (kind,key,p),revision in revisions.items():
+ row=saved[(kind,key,authors[p])];assert row['revision']==revision and sorted(row['tags'])==wanted_tags(kind,key,p)
+ check('flushed_recovery_mirror_exact_revisions_and_removal',True,records=len(revisions))
+ check('one_canonical_cell_record_no_child_copies',sum(row['target_kind']=='epoch' for row in saved.values())==args.epochs*3,child_epochs=len(children))
+ check('independent_protocol_curation_untouched',len(state['tables']['curation'])==args.epochs*2 and all(row['revision']==1 for row in state['tables']['curation']))
+ loaded={name:str(Path(module.__file__).resolve()) for name,module in sys.modules.items() if (name.startswith('workspace_') or name=='recording_workspace') and getattr(module,'__file__',None)}
+ check('all_product_modules_loaded_from_selected_source',all(Path(path).is_relative_to(root) for path in loaded.values()));report['loaded_product_modules']=loaded
+ finally:
+ if app is not None:
+ scheduler=app.extensions.get('backup_scheduler')
+ if scheduler is not None:
+ scheduler.flush()
+ close=getattr(scheduler,'close',None)
+ if close is not None:close(flush=False)
+ shared=app.extensions.get('shared_annotations');derived=getattr(shared,'_shared_tag_index',None)
+ if derived is not None:derived.close()
+ lock=app.extensions.get('app_state_session_lock')
+ if lock is not None:lock.close()
+ if index is not None:index.close()
+ if dj is not None:dj.conn().close()
+ if project_root is not None:closed=native.stop_native_database(project_root)
+ report['owned_native_runtime_stopped']=closed
+ except Exception as error:
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};log(traceback.format_exc())
+ report['operations']={name:{**percentiles(values),'sql_session_deltas':sql_samples.get(name,[])} for name,values in samples.items()}
+ report['source_inventory_after']=source_inventory(root);report['source_unchanged']=report['source_inventory_before']==report['source_inventory_after']
+ report['harness_unchanged']=digest(__file__)==report['harness_sha256']
+ report['seconds']=time.perf_counter()-started;report['finished_at']=dt.datetime.now(dt.timezone.utc).isoformat();report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['passed']=not report.get('error') and closed and report['source_unchanged'] and report['harness_unchanged'] and all(item['passed'] for item in report['checks'])
+ args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');log(json.dumps({'stage':'done','epochs':args.epochs,'passed':report['passed'],'seconds':report['seconds'],'output':str(args.output)}))
+ return int(not report['passed'])
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_tag_trigger_validation.py b/docs/dev/scale-audit-2026-09-29/benchmark_tag_trigger_validation.py
new file mode 100644
index 0000000..928d291
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_tag_trigger_validation.py
@@ -0,0 +1,110 @@
+"""Disposable same-server old/new trigger validation timing; no UI timing claim."""
+import hashlib,json,statistics,tempfile,time,uuid
+from pathlib import Path
+import pymysql
+from test_workspace_state_generation import NativeConnection
+import workspace_native_tag_lookup as lookup_module
+from workspace_projects import create_project
+import workspace_native_mysql as native
+
+
+def historical_delete(alias):
+ return (f'DELETE FROM `{lookup_module.SCHEMA}`.`{lookup_module.TABLE}` WHERE project_uuid={alias}.project_uuid '
+ f'AND target_kind={alias}.target_kind AND target_uuid={alias}.target_uuid '
+ f'AND profile_uuid={alias}.profile_uuid;')
+
+
+def historical_dirty(alias):
+ return f'UPDATE `{lookup_module.SCHEMA}`.`{lookup_module.MARKER}` SET dirty=1 WHERE project_uuid={alias}.project_uuid;'
+
+
+def baseline_manifest():
+ def valid(alias):
+ tag=lookup_module._tag(alias)
+ whitespace=[chr(n).encode('utf-8').hex() for n in range(0x3001) if chr(n).isspace()]
+ ends=','.join('0x'+value for value in whitespace)
+ controls=' OR '.join(f'LOCATE(CHAR({n}),{tag})>0' for n in range(32))
+ invalid=(f"JSON_TYPE(JSON_EXTRACT({alias}.tags,CONCAT('$[',j.ordinal-1,']')))!='STRING' "
+ f'OR CHAR_LENGTH({tag}) NOT BETWEEN 1 AND 255 OR BINARY LEFT({tag},1) IN ({ends}) '
+ f'OR BINARY RIGHT({tag},1) IN ({ends}) OR {controls}')
+ return (f"IF JSON_TYPE({alias}.tags)!='ARRAY' OR JSON_LENGTH({alias}.tags)>100 OR EXISTS ("
+ f"SELECT 1 FROM JSON_TABLE({alias}.tags,'$[*]' COLUMNS(ordinal FOR ORDINALITY)) j WHERE {invalid}) "
+ "THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Invalid canonical shared annotation tags'; END IF;")
+ def insert(alias):
+ return (valid(alias)+f' INSERT INTO `{lookup_module.SCHEMA}`.`{lookup_module.TABLE}` ({",".join(lookup_module.COLUMNS)}) '
+ f'SELECT {alias}.project_uuid,{alias}.target_kind,{alias}.target_uuid,{alias}.profile_uuid,'
+ f"CAST({lookup_module._tag(alias)} AS BINARY) FROM JSON_TABLE({alias}.tags,'$[*]' "
+ "COLUMNS(ordinal FOR ORDINALITY)) j;")
+ delete,dirty=historical_delete,historical_dirty
+ return {lookup_module.PREFIX+suffix:{'table':'shared_annotation','event':event,'timing':'AFTER','body':'BEGIN '+body+' END'}
+ for suffix,event,body in [('ai','INSERT',insert('NEW')+' '+dirty('NEW')),
+ ('au','UPDATE',delete('OLD')+' '+insert('NEW')+' '+dirty('OLD')+' '+dirty('NEW')),
+ ('ad','DELETE',delete('OLD')+' '+dirty('OLD'))]}
+
+
+def optimized_manifest():
+ def valid(alias):
+ tag='j.tag'
+ whitespace=[chr(n).encode('utf-8').hex() for n in range(0x3001) if chr(n).isspace()]
+ ends=','.join('0x'+value for value in whitespace)
+ invalid=(f"JSON_TYPE(JSON_EXTRACT({alias}.tags,CONCAT('$[',j.ordinal-1,']')))!='STRING' "
+ f'OR CHAR_LENGTH({tag}) NOT BETWEEN 1 AND 255 OR BINARY LEFT({tag},1) IN ({ends}) '
+ f'OR BINARY RIGHT({tag},1) IN ({ends}) '
+ f"OR REGEXP_LIKE({tag},CONVERT(0x5b002d1f5d USING utf8mb4),'c')")
+ return (f"IF JSON_TYPE({alias}.tags)!='ARRAY' OR JSON_LENGTH({alias}.tags)>100 OR EXISTS ("
+ f"SELECT 1 FROM JSON_TABLE({alias}.tags,'$[*]' COLUMNS(ordinal FOR ORDINALITY,"
+ f"tag VARCHAR(1020) CHARACTER SET utf8mb4 PATH '$' ERROR ON ERROR)) j WHERE {invalid}) "
+ "THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Invalid canonical shared annotation tags'; END IF;")
+ def insert(alias):
+ return (valid(alias)+f' INSERT INTO `{lookup_module.SCHEMA}`.`{lookup_module.TABLE}` ({",".join(lookup_module.COLUMNS)}) '
+ f'SELECT {alias}.project_uuid,{alias}.target_kind,{alias}.target_uuid,{alias}.profile_uuid,'
+ f"CAST(j.tag AS BINARY) FROM JSON_TABLE({alias}.tags,'$[*]' "
+ "COLUMNS(tag VARCHAR(1020) CHARACTER SET utf8mb4 PATH '$' ERROR ON ERROR)) j;")
+ delete,dirty=historical_delete,historical_dirty
+ return {lookup_module.PREFIX+suffix:{'table':'shared_annotation','event':event,'timing':'AFTER','body':'BEGIN '+body+' END'}
+ for suffix,event,body in [('ai','INSERT',insert('NEW')+' '+dirty('NEW')),
+ ('au','UPDATE',delete('OLD')+' '+insert('NEW')+' '+dirty('OLD')+' '+dirty('NEW')),
+ ('ad','DELETE',delete('OLD')+' '+dirty('OLD'))]}
+
+
+def run(output):
+ report={'kind':'same-server-native-sql-trigger-microbenchmark','samples':50,'targets_per_transaction':10,
+ 'canonical_tags_per_row':6,'includes':'10 canonical UPDATE statements and native transaction commit; no HTTP/event/provenance reads'}
+ with tempfile.TemporaryDirectory(prefix='rieke-tag-trigger-timing-') as directory:
+ root=Path(create_project(Path(directory)/'projects','Private tag trigger timing')['path']);raw=None
+ try:
+ native.ensure_native_database(root);raw=pymysql.connect(**native.connection_parameters(root),autocommit=True);conn=NativeConnection(raw)
+ conn.query('CREATE DATABASE recording_workspace')
+ conn.query('CREATE TABLE recording_workspace.shared_annotation (project_uuid varchar(36),target_kind varchar(8),target_uuid varchar(36),profile_uuid varchar(36),tags JSON NOT NULL,revision INT NOT NULL,PRIMARY KEY(project_uuid,target_kind,target_uuid,profile_uuid)) ENGINE=InnoDB')
+ conn.query('CREATE TABLE recording_workspace.app_state_generation (project_uuid varchar(36),scope_kind varchar(32),scope_uuid varchar(36),generation BIGINT,PRIMARY KEY(project_uuid,scope_kind,scope_uuid)) ENGINE=InnoDB')
+ project,profile=str(uuid.uuid4()),str(uuid.uuid4());ids=[str(uuid.uuid4()) for _ in range(10)]
+ conn.query('INSERT INTO recording_workspace.app_state_generation VALUES (%s,%s,%s,0)',(project,'shared_annotations',project))
+ for key in ids:conn.query('INSERT INTO recording_workspace.shared_annotation VALUES (%s,%s,%s,%s,%s,1)',(project,'epoch',key,profile,json.dumps(['all','bucket:7','ON','é','profile:0','review:0'])))
+ proof={'project_uuid':project,'tables':{'shared_annotation':{'count':10,'xor':'0'*64}}}
+ lookup=lookup_module.bootstrap(conn,project,proof);assert lookup.ready,lookup.reason
+ manifests={'before':baseline_manifest(),'optimized':optimized_manifest()}
+ report['mysql_version']=conn.query('SELECT VERSION()').fetchone()[0]
+ for label,manifest in manifests.items():
+ for name,spec in manifest.items():
+ conn.query('DROP TRIGGER recording_workspace.'+name)
+ conn.query('CREATE TRIGGER recording_workspace.'+name+' AFTER '+spec['event']+' ON recording_workspace.shared_annotation FOR EACH ROW '+spec['body'])
+ report[label]={'manifest_sha256':hashlib.sha256(json.dumps(manifest,sort_keys=True).encode()).hexdigest()}
+ samples=[]
+ for sample in range(55):
+ values=json.dumps(['all','bucket:7','ON','é','profile:0','review:'+str(sample%2)])
+ started=time.perf_counter()
+ with conn.transaction:
+ for key in ids:conn.query('UPDATE recording_workspace.shared_annotation SET tags=%s,revision=revision+1 WHERE project_uuid=%s AND target_kind=%s AND target_uuid=%s AND profile_uuid=%s',(values,project,'epoch',key,profile))
+ elapsed=time.perf_counter()-started
+ if sample>=5:samples.append(elapsed)
+ report[label].update(seconds=samples,median_seconds=statistics.median(samples),p95_seconds=sorted(samples)[47])
+ assert lookup.targets('review:0')=={('epoch',key) for key in ids}
+ Path(output).write_text(json.dumps(report,indent=2)+'\n');print(json.dumps({label:{k:v for k,v in value.items() if k!='seconds'} for label,value in report.items() if isinstance(value,dict)}),flush=True)
+ finally:
+ if raw is not None:raw.close()
+ native.stop_native_database(root)
+
+
+if __name__=='__main__':
+ import sys
+ run(sys.argv[1])
diff --git a/docs/dev/scale-audit-2026-09-29/benchmark_trace_integrity.py b/docs/dev/scale-audit-2026-09-29/benchmark_trace_integrity.py
new file mode 100644
index 0000000..fdbc4b8
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/benchmark_trace_integrity.py
@@ -0,0 +1,70 @@
+"""Measure fresh-process integrity verification versus bounded trace reading.
+
+Temporary real H5 payloads, 32 and 256 MiB; file caches are warm after writing.
+This is not a cold-disk or tens-of-GB production benchmark.
+"""
+import hashlib
+import json
+from pathlib import Path
+import statistics
+import tempfile
+import time
+import uuid
+from unittest.mock import patch
+import h5py
+import numpy as np
+import workspace_service as module
+from recording_workspace import digest
+from workspace_service import WorkspaceService
+
+
+def run(size_mib):
+ with tempfile.TemporaryDirectory(prefix='rieke-trace-integrity-') as directory:
+ path=Path(directory)/'synthetic.h5'
+ epoch,stream_id=str(uuid.uuid4()),str(uuid.uuid4())
+ with h5py.File(path,'w') as file:
+ group=file.create_group('/epochs/'+epoch+'/responses/'+stream_id)
+ group.attrs['uuid']=stream_id;group.attrs['sampleRate']=10000.
+ group.parent.parent.attrs['uuid']=epoch
+ values=np.zeros(200000,dtype=[('quantity','f8'),('units','S8')])
+ values['quantity']=np.arange(len(values));values['units']=b'pA'
+ group.create_dataset('data',data=values)
+ # Actually allocate the payload; no sparse-file timing shortcut.
+ padding=file.create_dataset('fixture_payload',(size_mib*1024*1024,),dtype='u1')
+ chunk=np.random.default_rng(7).integers(0,256,1024*1024,dtype='u1')
+ for offset in range(0,len(padding),len(chunk)):padding[offset:offset+len(chunk)]=chunk
+ sha=digest(path)
+ service=WorkspaceService.__new__(WorkspaceService)
+ service._loaded=True;service._source_signatures={}
+ service.rows={epoch:{'epoch_uuid':epoch,'source_sha256':sha,'streams':[
+ {'uuid':stream_id,'kind':'responses','h5_path':group.name or '/epochs/'+epoch+'/responses/'+stream_id,
+ 'sample_rate':10000.,'sample_count':len(values),'units':'pA'}]}}
+ service.manifests={sha:{'source_path':str(path),'source_sha256':sha,'source_size':path.stat().st_size}}
+ def measured():
+ started=time.perf_counter();result=service.trace(epoch,stream_id,start=400,count=20000)
+ elapsed=time.perf_counter()-started
+ assert result['values']==list(range(400,20400))
+ return elapsed
+ with patch.object(module,'digest',wraps=digest) as checked:
+ first=measured();cold_checks=checked.call_count
+ warm=[measured() for _ in range(5)];warm_checks=checked.call_count-cold_checks
+ # A same-size content mutation must invalidate the signature and fail.
+ with h5py.File(path,'a') as file:file['fixture_payload'][0]=int(file['fixture_payload'][0])^1
+ try:service.trace(epoch,stream_id,count=1)
+ except ValueError as error:mutation_result=str(error)
+ else:raise AssertionError('Changed source must be rejected')
+ return {'fixture_payload_mib':size_mib,'file_bytes':path.stat().st_size,
+ 'first_uncached_integrity_trace_seconds':first,'warm_trace_seconds':warm,
+ 'warm_median_seconds':statistics.median(warm),'first_full_hash_calls':cold_checks,
+ 'warm_full_hash_calls':warm_checks,'samples_per_window':20000,
+ 'same_size_mutation_rejected':mutation_result,
+ 'limits':'Real temp H5; OS page cache warm after construction; no API/HTTP/browser. Not a cold-disk benchmark.'}
+
+
+if __name__=='__main__':
+ import argparse
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--output',type=Path,required=True)
+ args=parser.parse_args()
+ results=[run(size) for size in (32,256)]
+ args.output.write_text(json.dumps(results,indent=2));print(json.dumps(results,indent=2))
diff --git a/docs/dev/scale-audit-2026-09-29/catalog_collision_scale_probe.py b/docs/dev/scale-audit-2026-09-29/catalog_collision_scale_probe.py
new file mode 100644
index 0000000..9ddc9dd
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/catalog_collision_scale_probe.py
@@ -0,0 +1,178 @@
+"""Disposable native MySQL collision-lookup plan, timing and transaction proof.
+
+Never accepts an existing project or connection. No acquisition schema is altered;
+all benchmark data lives in throwaway probe tables in a fresh temporary project.
+"""
+import argparse
+import hashlib
+import json
+from pathlib import Path
+import re
+import statistics
+import tempfile
+import time
+import traceback
+from types import SimpleNamespace
+
+from workspace_projects import create_project
+from workspace_native_mysql import ensure_native_database, stop_native_database, connection_parameters
+from recording_workspace import connect,assert_new_catalog_identities
+
+
+def main():
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--output',type=Path,required=True)
+ parser.add_argument('--epochs',type=int,default=100000)
+ parser.add_argument('--baseline-batches',type=int,default=5)
+ args=parser.parse_args()
+ if args.output.exists():parser.error('Use a fresh output file')
+ if not 1000<=args.epochs<=1000000:parser.error('epochs must be 1000–1000000')
+ if not 1<=args.baseline_batches<=100:parser.error('baseline-batches must be 1–100')
+ temporary=Path(tempfile.mkdtemp(prefix='rieke-collision-scale-'))
+ report={'epochs':args.epochs,'database_scope':'fresh disposable native MySQL only',
+ 'probe_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
+ 'limitations':['Lookup benchmark only; not full parser/population/import latency.',
+ 'Baseline full100k time is extrapolated from measured disjoint200-identity batches; not an observed full baseline run.',
+ 'This proves the proposed scratch-table algorithm, not product integration.']}
+ project=dj=None;clean=False
+ try:
+ project=Path(create_project(temporary/'projects','Disposable collision probe')['path'])
+ ensure_native_database(project);dj=connect({'kind':'native-project'},project_dir=project)
+ from retinanalysis.config import schema as catalog
+ conn=catalog.schema.connection
+ report['mysql_version']=conn.query('SELECT VERSION()').fetchone()[0]
+ report['catalog_columns']={};report['catalog_indexes']={}
+ for kind in ('Animal','Preparation','Cell','EpochGroup','EpochBlock','Epoch','Response','Stimulus'):
+ table=getattr(catalog,kind)
+ report['catalog_columns'][kind]=conn.query(f'SHOW FULL COLUMNS FROM {table.full_table_name} WHERE Field=%s',('h5_uuid',),as_dict=True).fetchone()
+ report['catalog_indexes'][kind]=conn.query(f'SHOW INDEX FROM {table.full_table_name}',as_dict=True).fetchall()
+ collation=report['catalog_columns']['Epoch']['Collation'];charset=collation.split('_',1)[0]
+ assert re.fullmatch(r'[A-Za-z0-9_]+',collation)
+ conn.query(f'CREATE TABLE `schema`.`identity_scan_probe` (id BIGINT PRIMARY KEY AUTO_INCREMENT,h5_uuid VARCHAR(255) CHARACTER SET {charset} COLLATE {collation} NOT NULL) ENGINE=InnoDB')
+ # Candidate index is declared before starting the import transaction.
+ conn.query(f'CREATE TEMPORARY TABLE `schema`.`identity_candidates_probe` (h5_uuid VARCHAR(255) CHARACTER SET {charset} COLLATE {collation} NOT NULL, KEY candidate_uuid(h5_uuid)) ENGINE=InnoDB')
+ conn.query('CREATE TABLE `schema`.`identity_rollback_probe` (id INT PRIMARY KEY) ENGINE=InnoDB')
+ def insert(table,values,batch=1000):
+ for offset in range(0,len(values),batch):
+ block=values[offset:offset+batch]
+ conn.query(f'INSERT INTO {table} (h5_uuid) VALUES '+','.join(['(%s)']*len(block)),tuple(block))
+ incoming=[f'incoming-{n:09d}' for n in range(args.epochs)]
+ with conn.transaction:insert('`schema`.`identity_scan_probe`',[f'catalog-{n:09d}' for n in range(args.epochs)])
+ baseline='SELECT h5_uuid FROM `schema`.`identity_scan_probe` WHERE '+' OR '.join(['h5_uuid=%s']*200)+' LIMIT 1'
+ candidate='SELECT existing.h5_uuid FROM `schema`.`identity_scan_probe` AS existing STRAIGHT_JOIN `schema`.`identity_candidates_probe` AS incoming FORCE INDEX(candidate_uuid) ON incoming.h5_uuid=existing.h5_uuid LIMIT 1'
+ report['baseline_explain']=conn.query('EXPLAIN FORMAT=JSON '+baseline,tuple(incoming[:200])).fetchone()[0]
+ samples=[]
+ for n in range(args.baseline_batches):
+ block=incoming[n*200:n*200+200]
+ if len(block)!=200:break
+ start=time.perf_counter();assert conn.query(baseline,tuple(block)).fetchone() is None;samples.append(time.perf_counter()-start)
+ report['baseline']={'batch_size':200,'measured_batches':len(samples),'samples_seconds':samples,'median_batch_seconds':statistics.median(samples),
+ 'full_batches_required':(len(incoming)+199)//200,'extrapolated_total_seconds':statistics.median(samples)*((len(incoming)+199)//200)}
+ assert conn.query("SELECT GET_LOCK('recording_workspace_import',30)").fetchone()[0]==1
+ start=time.perf_counter()
+ with conn.transaction:
+ insert('`schema`.`identity_candidates_probe`',incoming)
+ report['candidate_staging_seconds']=time.perf_counter()-start
+ report['candidate_explain']=conn.query('EXPLAIN FORMAT=JSON '+candidate).fetchone()[0]
+ report['candidate_analyze']=conn.query('EXPLAIN ANALYZE '+candidate).fetchone()[0]
+ samples=[]
+ for _ in range(3):
+ at=time.perf_counter();assert conn.query(candidate).fetchone() is None;samples.append(time.perf_counter()-at)
+ report['candidate_lookup']={'samples_seconds':samples,'median_seconds':statistics.median(samples)}
+ report['candidate_stage_plus_median_seconds']=report['candidate_staging_seconds']+report['candidate_lookup']['median_seconds']
+ # A second connection cannot access this connection's scratch table.
+ import pymysql
+ other=pymysql.connect(**connection_parameters(project))
+ try:
+ try:
+ with other.cursor() as cursor:cursor.execute('SELECT COUNT(*) FROM `schema`.`identity_candidates_probe`')
+ except pymysql.err.ProgrammingError as error:report['connection_local']=error.args[0]==1146
+ else:report['connection_local']=False
+ finally:other.close()
+ report['collation_cases']=[]
+ for index,name in enumerate(dict.fromkeys([collation,'utf8mb4_bin','utf8mb4_unicode_ci','utf8mb4_0900_ai_ci'])):
+ case_charset=name.split('_',1)[0]
+ source=f'`schema`.`identity_collation_{index}`';temp=f'`schema`.`identity_temp_{index}`'
+ conn.query(f'CREATE TABLE {source} (h5_uuid VARCHAR(255) CHARACTER SET {case_charset} COLLATE {name} NOT NULL) ENGINE=InnoDB')
+ conn.query(f'CREATE TEMPORARY TABLE {temp} (h5_uuid VARCHAR(255) CHARACTER SET {case_charset} COLLATE {name} NOT NULL,KEY candidate_uuid(h5_uuid)) ENGINE=InnoDB')
+ for stored,value in [('ABC','abc'),('résumé','resume'),('trail','trail '),("quote'\\value","quote'\\value"),('plain','other')]:
+ with conn.transaction:
+ conn.query(f'DELETE FROM {source}');conn.query(f'DELETE FROM {temp}')
+ insert(source,[stored]);insert(temp,[value])
+ original=conn.query(f'SELECT h5_uuid FROM {source} WHERE h5_uuid=%s LIMIT1'.replace('LIMIT1','LIMIT 1'),(value,)).fetchone()
+ joined=conn.query(f'SELECT existing.h5_uuid FROM {source} existing STRAIGHT_JOIN {temp} incoming FORCE INDEX(candidate_uuid) ON incoming.h5_uuid=existing.h5_uuid LIMIT 1').fetchone()
+ report['collation_cases'].append({'collation':name,'stored':stored,'incoming':value,'original':original,'candidate':joined,'equal':original==joined})
+ conn.query(f'DROP TEMPORARY TABLE {temp}')
+ # Lookup/staging cannot commit a preceding write when later work fails.
+ class RollbackProbe(Exception):pass
+ try:
+ with conn.transaction:
+ conn.query('INSERT INTO `schema`.`identity_rollback_probe` VALUES (1)')
+ conn.query('DELETE FROM `schema`.`identity_candidates_probe`')
+ insert('`schema`.`identity_candidates_probe`',[f'catalog-{args.epochs-1:09d}'])
+ assert conn.query(candidate).fetchone() is not None
+ raise RollbackProbe()
+ except RollbackProbe:pass
+ report['transaction_rollback_preserved']=conn.query('SELECT COUNT(*) FROM `schema`.`identity_rollback_probe`').fetchone()[0]==0
+ report['candidate_insert_rollback_preserved']=conn.query('SELECT COUNT(*) FROM `schema`.`identity_candidates_probe`').fetchone()[0]==args.epochs
+ report['source_row_count_preserved']=conn.query('SELECT COUNT(*) FROM `schema`.`identity_scan_probe`').fetchone()[0]==args.epochs
+ report['advisory_lock_still_owned']=conn.query("SELECT IS_USED_LOCK('recording_workspace_import')=CONNECTION_ID()").fetchone()[0]==1
+ # Exercise the production helper, including its real table metadata,
+ # ownership checks and bounded parameter batches, in this same fixture.
+ from workspace_catalog_collision import CatalogCollisionLookup,KINDS
+ import workspace_catalog_collision as helper_module
+ report['helper_sha256']=hashlib.sha256(Path(helper_module.__file__).read_bytes()).hexdigest()
+ relation=dj.FreeTable(conn,'`schema`.`identity_scan_probe`')
+ probe_catalog=SimpleNamespace(schema=SimpleNamespace(connection=conn),**{kind:relation for kind in KINDS})
+ with CatalogCollisionLookup(probe_catalog) as lookup:
+ report['helper_temp_tables']=len(lookup.temporary)
+ try:
+ with conn.transaction:
+ conn.query('INSERT INTO `schema`.`identity_rollback_probe` VALUES (2)')
+ at=time.perf_counter()
+ assert lookup.first_collision('Epoch',incoming) is None
+ report['helper_100000_lookup_seconds']=time.perf_counter()-at
+ expected=f'catalog-{args.epochs-1:09d}'
+ assert lookup.first_collision('Epoch',[*incoming,expected])==expected
+ raise RollbackProbe()
+ except RollbackProbe:pass
+ report['helper_rollback_preserved']=conn.query('SELECT COUNT(*) FROM `schema`.`identity_rollback_probe`').fetchone()[0]==0
+ source={'animals':[{'uuid':'incoming-animal','preparations':[{'uuid':'incoming-preparation','cells':[
+ {'uuid':'incoming-cell','epoch_groups':[{'uuid':'incoming-group','epoch_blocks':[
+ {'uuid':'incoming-block','epochs':[{'uuid':key} for key in incoming]}]}]}]}]}]}
+ with conn.transaction:
+ at=time.perf_counter();assert_new_catalog_identities(source,probe_catalog,lookup=lookup)
+ report['integrated_guard_100000_seconds']=time.perf_counter()-at
+ source['animals'][0]['preparations'][0]['cells'][0]['uuid']=expected
+ from workspace_catalog_identity import CatalogIdentityConflict
+ try:
+ with conn.transaction:
+ conn.query('INSERT INTO `schema`.`identity_rollback_probe` VALUES (3)')
+ assert_new_catalog_identities(source,probe_catalog,lookup=lookup)
+ except CatalogIdentityConflict as error:
+ report['integrated_guard_collision_rejected']=(error.conflict['kind']=='source_uuid_collision' and error.conflict['acquisition_uuid']==expected)
+ else:report['integrated_guard_collision_rejected']=False
+ report['integrated_guard_rollback_preserved']=conn.query('SELECT COUNT(*) FROM `schema`.`identity_rollback_probe`').fetchone()[0]==0
+ report['helper_cleanup_complete']=not lookup.temporary
+ conn.query('DROP TEMPORARY TABLE `schema`.`identity_candidates_probe`')
+ conn.query("SELECT RELEASE_LOCK('recording_workspace_import')")
+ except Exception as error:
+ report['runtime_error']={'type':type(error).__name__,'error':str(error),'traceback':traceback.format_exc()};print(traceback.format_exc(),flush=True)
+ finally:
+ if dj is not None:
+ try:dj.conn().close()
+ except Exception:pass
+ if project is not None:
+ try:clean=stop_native_database(project);report['clean_shutdown']=clean
+ except Exception as error:report['shutdown_error']=str(error)
+ if clean:
+ import shutil
+ shutil.rmtree(temporary);report['temporary_project_removed']=True
+ else:report['preserved_project']=str(temporary)
+ report['passed']=not report.get('runtime_error') and clean and all(report.get(key) is True for key in ('connection_local','transaction_rollback_preserved','candidate_insert_rollback_preserved','source_row_count_preserved','advisory_lock_still_owned','helper_rollback_preserved','helper_cleanup_complete','integrated_guard_collision_rejected','integrated_guard_rollback_preserved')) and all(row['equal'] for row in report.get('collation_cases',[]))
+ args.output.write_text(json.dumps(report,indent=2,default=str)+'\n')
+ print(json.dumps({key:report[key] for key in ('passed','baseline','candidate_stage_plus_median_seconds','runtime_error') if key in report}),flush=True)
+ return 0 if report['passed'] else 1
+
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/catalog_identity_native_probe.py b/docs/dev/scale-audit-2026-09-29/catalog_identity_native_probe.py
new file mode 100644
index 0000000..b23b769
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/catalog_identity_native_probe.py
@@ -0,0 +1,196 @@
+"""Actual pinned population and catalog validator against a disposable native MySQL.
+
+Creates only a fresh temporary project. Never accepts a project/database address,
+never uses Docker, and shuts down its verified owned runtime before removing it.
+"""
+import argparse
+import copy
+import datetime as dt
+import hashlib
+import json
+from pathlib import Path
+import tempfile
+import traceback
+import uuid
+
+from workspace_projects import create_project
+from workspace_native_mysql import ensure_native_database, stop_native_database, native_binary
+from recording_workspace import connect, assert_new_catalog_identities
+import workspace_catalog_identity as validator
+
+
+def fixture(number):
+ def item(name, **extra):
+ identity = str(uuid.uuid5(uuid.NAMESPACE_URL, f'rieke-disposable-identity/{number}/{name}'))
+ return {'uuid':identity, 'label':name, 'properties':{'integer':1,'float':1.0,'boolean':True,'null':None},
+ 'attributes':{'uuid':identity}, 'start_time':'06/11/2026 12:34:56:123456', **extra}
+ source = item('Experiment', rig_type='PATCH', rig='Rig '+str(number), animals=[])
+ animal = item('Animal', preparations=[], sex='F', age='42')
+ preparation = item('Preparation', cells=[], region='central')
+ source['animals']=[animal]; animal['preparations']=[preparation]
+ for n in range(2):
+ cell = item('cell-'+str(n), type='ON', epoch_groups=[]); cell['label']='Cell3'
+ group = item('group-'+str(n), epoch_blocks=[])
+ block = item('block-'+str(n), protocolID='example.Protocol', parameters={}, epochs=[])
+ epoch = item('epoch-'+str(n), parameters={'contrast':.5,'control':True,'count':1,'float_count':1.0}, responses={}, stimuli={})
+ response = item('response-'+str(n), h5path=f'/experiment/{number}/cell/{n}/epoch/responses/Amp1',
+ sampleRate=10000.0,sampleRateUnits='Hz',
+ inputTimeDotNetDateTimeOffsetOffsetHours=-7,
+ inputTimeDotNetDateTimeOffsetTicks=638853284961234567)
+ stimulus = item('stimulus-'+str(n), h5path=f'/experiment/{number}/cell/{n}/epoch/stimuli/LED')
+ epoch['responses']['Amp1']=response;epoch['stimuli']['LED']=stimulus
+ block['epochs']=[epoch];group['epoch_blocks']=[block];cell['epoch_groups']=[group]
+ preparation['cells'].append(cell)
+ return source
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__)
+ ap.add_argument('--output',type=Path,required=True)
+ ap.add_argument('--strict',action='store_true',help='Return failure for any incorrect scientific outcome, runtime error, or changed helper')
+ args=ap.parse_args()
+ if args.output.exists():ap.error('Choose a new output file; historical evidence is preserved')
+ report={'native':True,'database_scope':'fresh temporary native project only','cases':[],
+ 'strict':args.strict,'probe_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
+ 'helper_sha256_before':hashlib.sha256(Path(validator.__file__).read_bytes()).hexdigest()}
+ project=None;dj=None;clean=False
+ temporary=Path(tempfile.mkdtemp(prefix='rieke-catalog-identity-native-'))
+ try:
+ project=Path(create_project(temporary/'projects','Disposable identity probe')['path'])
+ ensure_native_database(project)
+ report['mysql_binary']=str(native_binary())
+ dj=connect({'kind':'native-project'},project_dir=project)
+ from retinanalysis.config import schema as catalog
+ from retinanalysis.utils import database_pop as population
+ report['pinned_source_sha256']={module.__name__:hashlib.sha256(Path(module.__file__).read_bytes()).hexdigest()
+ for module in (catalog,population)}
+ population.configure_tables(catalog)
+ connection=catalog.schema.connection
+ report['mysql_version']=connection.query('SELECT VERSION()').fetchone()[0]
+ report['timestamp_storage_probes']=[]
+ original_mode=connection.query('SELECT @@SESSION.sql_mode').fetchone()[0]
+ report['default_sql_mode']=original_mode
+ connection.query('CREATE TEMPORARY TABLE `schema`.`catalog_timestamp_probe` (stamp DATETIME(0))')
+ try:
+ modes=[('server_default',original_mode),('explicit_rounding',','.join(mode for mode in original_mode.split(',') if mode!='TIME_TRUNCATE_FRACTIONAL')),
+ ('truncate_fractional',','.join(sorted(set(original_mode.split(','))|{'TIME_TRUNCATE_FRACTIONAL'})))]
+ for mode_name,mode in modes:
+ connection.query('SET SESSION sql_mode=%s',(mode,))
+ for stamp in ('2026-06-11 12:34:56.123456','2026-06-11 12:34:56.499999',
+ '2026-06-11 12:34:56.500000','2026-06-11 12:34:56.654321',
+ '2026-06-11 23:59:59.999999'):
+ connection.query('DELETE FROM `schema`.`catalog_timestamp_probe`')
+ connection.query('INSERT INTO `schema`.`catalog_timestamp_probe` VALUES (%s)',(dt.datetime.fromisoformat(stamp),))
+ stored=connection.query('SELECT stamp FROM `schema`.`catalog_timestamp_probe`').fetchone()[0]
+ report['timestamp_storage_probes'].append({'mode':mode_name,'input':stamp,'stored':str(stored)})
+ finally:
+ connection.query('SET SESSION sql_mode=%s',(original_mode,))
+ connection.query('DROP TEMPORARY TABLE `schema`.`catalog_timestamp_probe`')
+ sources=[fixture(n) for n in (1,2)]
+ # The pinned ExperimentObj omits optional scalar keys when unrecorded;
+ # population should leave these nullable columns NULL, not invent data.
+ sources[1].pop('rig')
+ sources[1]['animals'][0]['preparations'][0]['cells'][1]['type']=None
+ first,second=[cell['epoch_groups'][0] for cell in sources[1]['animals'][0]['preparations'][0]['cells']]
+ moved=second['epoch_blocks'].pop();moved['protocolID']='other.Protocol'
+ first['epoch_blocks'].append(moved) # Actual population: one mixed group, one empty group.
+ with connection.transaction:
+ for n,source in enumerate(sources):
+ population.append_experiment(str(temporary/'metadata.json'),str(temporary/f'rig-{n}.h5'),
+ str(temporary/'tags.json'),source,'disposable-audit',{})
+ ids=[(catalog.Experiment & {'h5_uuid':source['uuid']}).fetch1('id') for source in sources]
+ report['experiment_ids']=[int(value) for value in ids]
+ def validate(name,source,identity):
+ try:
+ counts=validator.validate_catalog_identity(source,catalog,identity)
+ except Exception as error:
+ result={'name':name,'accepted':False,'error_type':type(error).__name__,'error':str(error)}
+ else:result={'name':name,'accepted':True,'counts':counts}
+ report['cases'].append(result);print(json.dumps(result),flush=True)
+ return result
+ for index in range(2):validate('valid_source_'+str(index+1),sources[index],ids[index])
+ protocol_names={row['protocol_id']:row['name'] for row in catalog.Protocol.to_dicts()}
+ report['group_protocol_controls']=[{'group_uuid':row['h5_uuid'],'protocol':protocol_names[row['protocol_id']]}
+ for row in catalog.EpochGroup.to_dicts()]
+ stream_relation=catalog.Response & (catalog.Epoch & {'experiment_id':ids[0]}).proj(parent_id='id')
+ report['response_semijoin_sql']=str(stream_relation.make_sql())
+ report['response_semijoin_count']=len(stream_relation)
+ report['all_response_count']=len(catalog.Response())
+ def objects(source):
+ animal=source['animals'][0];preparation=animal['preparations'][0];cell=preparation['cells'][0]
+ group=cell['epoch_groups'][0];block=group['epoch_blocks'][0];epoch=block['epochs'][0]
+ return {'Animal':animal,'Preparation':preparation,'Cell':cell,'EpochGroup':group,'EpochBlock':block,
+ 'Epoch':epoch,'Response':epoch['responses']['Amp1'],'Stimulus':epoch['stimuli']['LED']}
+ report['guard_cases']=[]
+ tables=['Experiment',*objects(sources[0]),'Protocol']
+ def inventory():
+ return hashlib.sha256(json.dumps({name:getattr(catalog,name).to_dicts() for name in tables},
+ sort_keys=True,default=str).encode()).hexdigest()
+ before_guard=inventory()
+ for kind in ('distinct_control',*objects(sources[0])):
+ incoming=fixture(3)
+ if kind!='distinct_control':objects(incoming)[kind]['uuid']=objects(sources[0])[kind]['uuid']
+ try:assert_new_catalog_identities(incoming,catalog)
+ except Exception as error:
+ passed=kind!='distinct_control' and isinstance(error,ValueError) and str(error).startswith(f'Existing {kind} source UUID ')
+ result={'kind':kind,'accepted':False,'passed':passed,'error_type':type(error).__name__,'error':str(error)}
+ else:result={'kind':kind,'accepted':True,'passed':kind=='distinct_control'}
+ report['guard_cases'].append(result)
+ report['guard_preserved_all_catalog_rows']=before_guard==inventory()
+ group_ids=(catalog.EpochGroup & {'experiment_id':ids[0]}).fetch('id',order_by='id')
+ group_rows=(catalog.EpochGroup & {'experiment_id':ids[0]}).to_dicts()
+ mutations=[('swapped_cell_parent',catalog.EpochGroup,'parent_id',group_ids[0],group_rows[1]['parent_id']),
+ ('wrong_is_mea',catalog.Experiment,'is_mea',ids[0],1),
+ ('wrong_group_protocol',catalog.EpochGroup,'protocol_id',group_ids[0],
+ (catalog.Protocol & {'name':'no_group_protocol'}).fetch1('protocol_id')),
+ ('wrong_rig_scalar',catalog.Experiment,'rig',ids[0],'WRONG RIG'),
+ ('wrong_epoch_time',catalog.Epoch,'start_time',(catalog.Epoch & {'experiment_id':ids[0]}).fetch('id')[0],'1999-01-01 00:00:00'),
+ ('wrong_response_offset',catalog.Response,'offset_ticks',stream_relation.fetch('id')[0],'123'),
+ ('wrong_response_rate',catalog.Response,'sample_rate',stream_relation.fetch('id')[0],'20000')]
+ for name,table,column,number,value in mutations:
+ with connection.transaction:
+ original=(table & {'id':number}).fetch1(column)
+ connection.query(f'UPDATE {table.full_table_name} SET `{column}`=%s WHERE id=%s',(value,int(number)))
+ validate(name,sources[0],ids[0])
+ connection.query(f'UPDATE {table.full_table_name} SET `{column}`=%s WHERE id=%s',(original,int(number)))
+ with connection.transaction:
+ connection.query(f'UPDATE {catalog.Experiment.full_table_name} SET rig=%s WHERE id=%s',('UNRECORDED RIG',int(ids[1])))
+ validate('invented_missing_rig',sources[1],ids[1])
+ connection.query(f'UPDATE {catalog.Experiment.full_table_name} SET rig=NULL WHERE id=%s',(int(ids[1]),))
+ report['stored_time']=str((catalog.Epoch & {'experiment_id':ids[0]}).fetch('start_time')[0])
+ report['stored_response_offsets']=stream_relation.fetch1('offset_hours','offset_ticks') if len(stream_relation)==1 else [str(v) for v in stream_relation.fetch('offset_ticks')]
+ report['valid_after_mutations']=validate('restored_source',sources[0],ids[0])['accepted']
+ except Exception as error:
+ report['runtime_error']={'type':type(error).__name__,'error':str(error),'traceback':traceback.format_exc()}
+ print(traceback.format_exc(),flush=True)
+ finally:
+ if dj is not None:
+ try:dj.conn().close()
+ except Exception:pass
+ if project is not None:
+ try:
+ clean=stop_native_database(project)
+ report['clean_native_shutdown']=clean
+ except Exception as error:report['shutdown_error']=str(error)
+ if clean:
+ import shutil
+ shutil.rmtree(temporary)
+ report['temporary_project_removed']=True
+ else:
+ report['temporary_project_preserved']=str(temporary)
+ report['helper_sha256_after']=hashlib.sha256(Path(validator.__file__).read_bytes()).hexdigest()
+ report['helper_changed_during_run']=report['helper_sha256_before']!=report['helper_sha256_after']
+ controls={'valid_source_1','valid_source_2','restored_source'}
+ report['incorrect_outcomes']=[case['name'] for case in report['cases']
+ if (not case['accepted'] if case['name'] in controls else
+ case['accepted'] or case.get('error_type')!='CatalogIdentityConflict')]
+ report['scientific_contract_passed']=(len(report['cases'])==11 and not report['incorrect_outcomes']
+ and len(report.get('guard_cases',[]))==9 and all(case['passed'] for case in report['guard_cases'])
+ and report.get('guard_preserved_all_catalog_rows') is True
+ and not report.get('runtime_error') and clean and not report['helper_changed_during_run'])
+ report['release_ready']=False
+ args.output.write_text(json.dumps(report,indent=2,default=str)+'\n')
+ return 1 if report.get('runtime_error') or not clean or (args.strict and not report['scientific_contract_passed']) else 0
+
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/dense_shared_index_oracle.py b/docs/dev/scale-audit-2026-09-29/dense_shared_index_oracle.py
new file mode 100644
index 0000000..ea9111d
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/dense_shared_index_oracle.py
@@ -0,0 +1,204 @@
+"""Independent ordinal oracle for the production shared-tag SQLite index.
+
+This directly exercises the index; generation/record callbacks are deterministic
+fixtures, not native SQL or service/browser performance claims.
+"""
+import argparse
+from collections import Counter
+import hashlib
+import json
+import math
+from pathlib import Path
+import resource
+import time
+import uuid
+
+import workspace_shared_tag_index as product
+
+
+def uid(n):return str(uuid.UUID(int=n))
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--epochs',type=int,default=100000);ap.add_argument('--tags',type=int,default=5);ap.add_argument('--samples',type=int,default=20);ap.add_argument('--aggregates',action='store_true');ap.add_argument('--output',type=Path,required=True);args=ap.parse_args()
+ if args.output.exists():ap.error('Choose an unused receipt')
+ if args.tags<5:ap.error('This exact-identity fixture requires at least five tags')
+ n=args.epochs;profiles=[uid(9000000+p) for p in range(3)];cells=[uid(1000000+c) for c in range((n+99)//100)]
+ rows=[{'epoch_uuid':uid(100000+i),'cell_uuid':cells[i//100]} for i in range(n)]
+ epoch_ord={row['epoch_uuid']:i for i,row in enumerate(rows)};cell_ord={value:i for i,value in enumerate(cells)}
+ epoch_field='annotations/epoch/tags';cell_field='annotations/cell/tags';effective='annotations/effective/tags'
+ def original(kind,i,p):
+ if kind=='epoch':return ['common','QC' if i%2==0 else 'qc','é' if i%3==0 else 'e\u0301',f'bucket-{i%17}',f'unique-{i}']+[f'extra-{j}-{p}' for j in range(args.tags-5)]
+ return ['common','inherited',f'cell-bucket-{i%7}',f'cell-profile-{p}',f'cell-unique-{i}']
+ overrides={};state={'generation':0,'epoch':'fixture-v1'};feed={};read_stats={'calls':0,'loaded':0};inject=[False]
+ def tags_for(kind,i,p):return overrides.get((kind,i,p),original(kind,i,p))
+ def make_record(kind,i,p):
+ tags=tags_for(kind,i,p)
+ if tags is None:return None
+ return {'target_kind':kind,'target_uuid':rows[i]['epoch_uuid'] if kind=='epoch' else cells[i],
+ 'profile_uuid':profiles[p],'author_name':'Same display name' if p<2 else 'Third author',
+ 'revision':1,'tags':tags}
+ def records(keys):
+ read_stats['calls']+=1
+ identities=((kind,i,p) for kind,amount in [('cell',len(cells)),('epoch',n)] for i in range(amount) for p in range(3)) if keys is None else (
+ (key['target_kind'],(epoch_ord if key['target_kind']=='epoch' else cell_ord)[key['target_uuid']],profiles.index(key['profile_uuid'])) for key in keys)
+ for kind,i,p in identities:
+ row=make_record(kind,i,p)
+ if row is not None:
+ read_stats['loaded']+=1;yield row
+ if inject[0]:inject[0]=False;mutate('epoch',n-1,2,['racing','QC'])
+ def token():return (state['epoch'],state['generation'])
+ def changes(old):
+ if old[0]!=state['epoch']:return None
+ return ([{'target_kind':kind,'target_uuid':rows[i]['epoch_uuid'] if kind=='epoch' else cells[i],'profile_uuid':profiles[p]}
+ for (kind,i,p),version in feed.items() if version>old[1]],token())
+ def mutate(kind,i,p,values):
+ overrides[(kind,i,p)]=values;state['generation']+=1;feed[(kind,i,p)]=state['generation']
+ def validate(row):
+ assert len(set(row['tags']))==len(row['tags']) and all(isinstance(tag,str) for tag in row['tags'])
+ assert row['profile_uuid'] in profiles and row['revision']==1
+ return row['tags']
+ def expected_tags(kind,i):return set().union(*(set(tags_for(kind,i if kind=='epoch' else i//100,p) or []) for p in range(3)))
+ def summary_oracle():
+ tag_counts=Counter();cell_counts=Counter()
+ for first in range(0,n,100):
+ present=set()
+ for i in range(first,min(first+100,n)):
+ values=expected_tags('epoch',i)|expected_tags('cell',i);tag_counts.update(values);present.update(values)
+ cell_counts.update(present)
+ ranked=sorted(tag_counts,key=lambda tag:(-tag_counts[tag],tag.casefold(),tag))[:12]
+ return {'shared_tagged_cells':len(cells),'shared_tagged_epochs':n,
+ 'tags':[{'tag':tag,'epoch_count':tag_counts[tag],'cell_count':cell_counts[tag]} for tag in ranked],
+ 'total_tags':len(tag_counts),'truncated':len(tag_counts)>12}
+ def catalog_oracle(field):
+ amount=len(cells) if field==cell_field else n if field in (epoch_field,effective) else 1
+ choices=[]
+ for ordinal in range(min(50,amount)):
+ i=ordinal*100 if field==cell_field else ordinal
+ values=sorted(expected_tags('cell',i)) if field==cell_field else sorted(expected_tags('epoch',i)) if field==epoch_field else sorted(expected_tags('epoch',i)|expected_tags('cell',i)) if field==effective else ['Same display name','Third author'] if field=='annotations/authors' else profiles
+ count=min(100,n-ordinal*100) if field==cell_field else 1 if field in (epoch_field,effective) else n
+ choices.append({'value':values,'type':'array','count':count})
+ return amount,choices
+ # Independent explicit predicate evaluator. No production matcher is called.
+ def expected(predicate,i):
+ if 'all' in predicate:return all(expected(item,i) for item in predicate['all'])
+ if 'any' in predicate:return any(expected(item,i) for item in predicate['any'])
+ if 'not' in predicate:return not expected(predicate['not'],i)
+ field=predicate['field'];values=sorted(expected_tags('epoch',i) if field==epoch_field else expected_tags('cell',i) if field==cell_field else expected_tags('epoch',i)|expected_tags('cell',i))
+ op=predicate['operator'];value=predicate.get('value')
+ if op=='contains':return value in values
+ if op=='eq':return values==value
+ if op=='ne':return values!=value
+ if op=='in':return values in value
+ if op=='not_in':return values not in value
+ if op=='exists':return True
+ if op in ('missing','is_null','gt','gte','lt','lte'):return False
+ raise AssertionError(op)
+ def leaf(field,op,value=None):return {'field':field,'operator':op,'value':value}
+ cases=[('exact_case_upper',leaf(epoch_field,'contains','QC')),('exact_case_lower',leaf(epoch_field,'contains','qc')),
+ ('unicode_composed',leaf(epoch_field,'contains','é')),('unicode_decomposed',leaf(epoch_field,'contains','e\u0301')),
+ ('inherited',leaf(cell_field,'contains','cell-bucket-2')),('direct_does_not_inherit',leaf(epoch_field,'contains','inherited')),
+ ('and',{'all':[leaf(epoch_field,'contains','bucket-3'),leaf(cell_field,'contains','cell-bucket-2')]}),
+ ('or',{'any':[leaf(epoch_field,'contains','bucket-3'),leaf(cell_field,'contains','cell-bucket-2')]}),
+ ('scoped_not',{'not':leaf(epoch_field,'contains','bucket-3')}),
+ ('nested',{'all':[{'any':[leaf(epoch_field,'contains','bucket-3'),leaf(cell_field,'contains','cell-bucket-2')]},{'not':leaf(epoch_field,'contains','unique-503')}]}),
+ ('empty_array',leaf(effective,'eq',[])),('missing',leaf(effective,'missing')),('numeric_scalar',leaf(epoch_field,'contains',1)),
+ ('exact_union',leaf(epoch_field,'eq',sorted(expected_tags('epoch',503)))),
+ ('noncanonical_array_order',leaf(epoch_field,'eq',list(reversed(sorted(expected_tags('epoch',503)))))),
+ ('repeated_array_tag',leaf(epoch_field,'eq',['common','common'])),
+ ('array_in',leaf(epoch_field,'in',[[],sorted(expected_tags('epoch',503))])),
+ ('array_not_in',leaf(epoch_field,'not_in',[[],sorted(expected_tags('epoch',503))]))]
+ source_hash=hashlib.sha256(Path(product.__file__).read_bytes()).hexdigest()
+ def timings_summary(values):
+ ordered=sorted(values)
+ return {'samples':len(values),'quantile_method':'nearest_rank',
+ **{'p'+str(int(q*100)):ordered[max(0,math.ceil(q*len(ordered))-1)] for q in (.5,.95,.99)},
+ 'maximum':max(values),'sample_seconds':values}
+ manifest={'epochs':n,'profiles':profiles,'tags_per_epoch_profile':args.tags,'cell_tags_per_profile':5,'formula_version':2}
+ report={'scope':__doc__,'fixture':manifest,'fixture_sha256':hashlib.sha256(json.dumps(manifest,sort_keys=True).encode()).hexdigest(),
+ 'source_sha256_before':source_hash,'probe_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),'cases':[],'release_ready':False}
+ index=product.SharedTagIndex(generation=token,changes=changes,records=records,validate=validate)
+ def check(name,condition,**detail):
+ item={'name':name,'passed':bool(condition),**detail};report['cases'].append(item);print(json.dumps(item),flush=True)
+ if not condition:raise AssertionError(name)
+ try:
+ start=time.perf_counter();check('cold_build',index.refresh());report['cold_build_seconds']=time.perf_counter()-start
+ full_loaded=read_stats['loaded'];check('all_profiles_indexed',full_loaded==3*(n+len(cells)),loaded=full_loaded)
+ if hasattr(index,'storage_stats'):report['storage_after_build']=index.storage_stats()
+ if args.aggregates:
+ # Product overview ordering follows the established casefold then
+ # exact-text tie rule, independently reproduced from ordinal sets.
+ wanted=summary_oracle()
+ start=time.perf_counter();actual=index.summary(rows);report['cold_summary_seconds']=time.perf_counter()-start
+ check('summary_independent_exact_counts',actual==wanted,seconds=report['cold_summary_seconds'])
+ fields=[cell_field,epoch_field,effective,'annotations/authors','annotations/author_uuids']
+ start=time.perf_counter();catalog=index.catalog(rows,fields);report['cold_catalog_seconds']=time.perf_counter()-start
+ for field in fields:
+ amount,choices=catalog_oracle(field)
+ actual=catalog[field]
+ check('catalog_'+field,actual['count']==n and actual['distinct_count']==amount and actual['choices']==choices and actual['choices_truncated']==(amount>50),distinct=actual['distinct_count'])
+ summary_times=[];catalog_times=[]
+ for _ in range(args.samples):
+ start=time.perf_counter();assert index.summary(rows)==wanted;summary_times.append(time.perf_counter()-start)
+ start=time.perf_counter();assert index.catalog(rows,fields)==catalog;catalog_times.append(time.perf_counter()-start)
+ for name,values in [('summary',summary_times),('catalog',catalog_times)]:
+ report['warm_'+name+'_seconds']=timings_summary(values)
+ if hasattr(index,'storage_stats'):report['storage_after_aggregates']=index.storage_stats()
+ for name,predicate in cases:
+ eligible=rows[37::3] if name=='scoped_not' else rows
+ wanted=tuple(row['epoch_uuid'] for row in eligible if expected(predicate,epoch_ord[row['epoch_uuid']]))
+ start=time.perf_counter();actual,_=index.matching(eligible,{},predicate);seconds=time.perf_counter()-start
+ check(name,actual==wanted,matched=len(actual),query_seconds=seconds,uuid_sha256=hashlib.sha256(''.join(actual).encode()).hexdigest())
+ # Membership queries load no canonical annotation rows after refresh.
+ timings=[]
+ for _ in range(args.samples):
+ start=time.perf_counter();assert index.refresh();result,_=index.matching(rows,{'tag':'bucket-3'});timings.append(time.perf_counter()-start)
+ check('warm_queries_read_zero_annotation_rows',read_stats['loaded']==full_loaded)
+ report['warm_match_seconds']=timings_summary(timings)
+ for count in (1,1000):
+ before=read_stats['loaded'];start=time.perf_counter()
+ for i in range(min(count,n)):mutate('epoch',i,0,['QC','modified'])
+ assert index.refresh();elapsed=time.perf_counter()-start
+ actual,_=index.matching(rows,{'tag':'modified'});wanted=tuple(row['epoch_uuid'] for row in rows[:min(count,n)])
+ check(f'delta_{count}_same_revision',actual==wanted and read_stats['loaded']-before==min(count,n),rows_loaded=read_stats['loaded']-before,refresh_seconds=elapsed)
+ if args.aggregates:
+ wanted_summary=summary_oracle();start=time.perf_counter();actual_summary=index.summary(rows);summary_seconds=time.perf_counter()-start
+ start=time.perf_counter();actual_catalog=index.catalog(rows,fields);catalog_seconds=time.perf_counter()-start
+ good=actual_summary==wanted_summary
+ for field in fields:
+ amount,choices=catalog_oracle(field);good=good and actual_catalog[field]['distinct_count']==amount and actual_catalog[field]['choices']==choices
+ check(f'delta_{count}_aggregates_remain_exact',good,summary_seconds=summary_seconds,catalog_seconds=catalog_seconds)
+ if hasattr(index,'storage_stats'):report[f'storage_after_delta_{count}']=index.storage_stats()
+ for p in range(3):mutate('epoch',0,p,[]);mutate('cell',0,p,[])
+ assert index.refresh();actual,_=index.matching(rows,{},leaf(effective,'eq',[]))
+ check('empty_tag_rows_and_inheritance_removed',actual==(rows[0]['epoch_uuid'],))
+ for p in range(3):mutate('epoch',1,p,None)
+ assert index.refresh();actual,_=index.matching(rows,{},leaf(epoch_field,'eq',[]))
+ check('deleted_records_leave_exact_empty_membership',actual==(rows[0]['epoch_uuid'],rows[1]['epoch_uuid']))
+ old_token=index.token;mutate('epoch',2,0,['race fixture']);inject[0]=True
+ try:index.refresh()
+ except product.SharedTagsChanged:conflict=True
+ else:conflict=False
+ check('mixed_generation_build_rejected',conflict and index.token==old_token)
+ assert index.refresh();actual,_=index.matching(rows,{'tag':'racing'});check('subsequent_delta_recovers_exact_membership',actual==(rows[-1]['epoch_uuid'],))
+ report['stats']=index.stats;report['sqlite_bytes']=sum(path.stat().st_size for path in Path(index.folder.name).iterdir())
+ report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ report['source_sha256_after']=hashlib.sha256(Path(product.__file__).read_bytes()).hexdigest();report['source_unchanged']=source_hash==report['source_sha256_after']
+ report['passed']=report['source_unchanged'] and all(item['passed'] for item in report['cases'])
+ except Exception as error:
+ import traceback
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};report['passed']=False;print(traceback.format_exc(),flush=True)
+ finally:
+ folder=Path(index.folder.name) if index.folder is not None else None;connection=index.connection
+ index.close();report['derived_folder_removed']=folder is None or not folder.exists()
+ closed=connection is None
+ if connection is not None:
+ try:connection.execute('SELECT 1')
+ except Exception:closed=True
+ report['sqlite_connection_closed']=closed
+ report['passed']=report.get('passed',False) and report['derived_folder_removed'] and closed
+ args.output.write_text(json.dumps(report,indent=2)+'\n')
+ return int(not report['passed'])
+
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/freeze_validation.py b/docs/dev/scale-audit-2026-09-29/freeze_validation.py
new file mode 100644
index 0000000..7e94464
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/freeze_validation.py
@@ -0,0 +1,288 @@
+"""Capture checked source copies and validate that fixed snapshot, preserving evidence.
+
+The snapshot is assembled in a temporary directory, checked against source hashes
+before and after copying, then retained with results. No checkout/branch changes.
+Only Python runtime binaries and node_modules are symlinked; application/parser
+source is copied. External installed dependencies are hashed before/after use.
+"""
+from __future__ import annotations
+
+import argparse
+import datetime as dt
+import hashlib
+import importlib.metadata
+import json
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import tempfile
+import time
+
+HERE = Path(__file__).resolve().parent
+REPO = HERE.parents[2]
+AUDIT = HERE.relative_to(REPO)
+IGNORED = {'__pycache__', '.git', '.pytest_cache', '.DS_Store'}
+
+
+def digest(path):
+ with path.open('rb') as stream:
+ return hashlib.file_digest(stream, 'sha256').hexdigest()
+
+
+def checksum(value):
+ return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
+
+
+def write_json(path, value):
+ path.write_text(json.dumps(value, indent=2, sort_keys=True) + '\n')
+
+
+def walk(folder, *, dependency=False):
+ if not folder.exists():
+ raise FileNotFoundError(folder)
+ ignored = IGNORED | ({'.cache', '.vite', '.vite-temp'} if dependency else set())
+ for current, directories, files in os.walk(folder, followlinks=False):
+ directories[:] = sorted(name for name in directories if name not in ignored)
+ for name in sorted(files):
+ path = Path(current) / name
+ if name in ignored or path.suffix in {'.pyc', '.pyo'}:
+ continue
+ yield path
+ for name in directories:
+ path = Path(current) / name
+ if path.is_symlink():
+ # Do not silently omit a tree that may contain executed code.
+ raise ValueError('Directory symlink requires explicit dependency handling: ' + str(path))
+
+
+def source_inventory():
+ paths = set()
+ for folder in ('python', 'tools', 'tests', 'src', 'workspace-app/src', 'workspace-app/public'):
+ paths.update(walk(REPO / folder))
+ for path in (REPO / 'workspace-app').iterdir():
+ if path.is_file():
+ paths.add(path)
+ for name in ('rieke-release.json', 'rieke.py', 'README.md', 'LICENSE', 'epicTreeGUI.m', 'install.m'):
+ paths.add(REPO / name)
+ paths.update(HERE.glob('*.py'))
+ paths.update(HERE.glob('*.mjs'))
+ parser_root = REPO / '.rieke-runtime/retinanalysis'
+ paths.update(path for path in walk(parser_root) if path.suffix == '.py')
+ paths.update(parser_root / name for name in ('pyproject.toml', 'LICENSE.txt'))
+ result = {}
+ for path in sorted(paths):
+ if path.is_symlink():
+ raise ValueError('Application/parser source must be copied from a regular file: ' + str(path))
+ result[str(path.relative_to(REPO))] = digest(path)
+ return result
+
+
+def dependency_inventory(python_packages, node_modules, python_binary, node_binary):
+ result = {'python_binary': digest(python_binary), 'node_binary': digest(node_binary), 'files': {}}
+ for label, root in (('python_packages', python_packages), ('node_modules', node_modules)):
+ for path in walk(root, dependency=True):
+ entry = {'sha256': digest(path)}
+ if path.is_symlink():
+ target = path.resolve(strict=True)
+ if not target.is_relative_to(root.resolve()):
+ raise ValueError('Dependency link escapes its recorded root: ' + str(path))
+ entry['symlink_target'] = os.readlink(path)
+ result['files'][label + '/' + str(path.relative_to(root))] = entry
+ return result
+
+
+def mysql_inventory(prefix):
+ """Hash the explicitly reused native runtime, never a database directory."""
+ values = {}
+ for directory, folders, files in os.walk(prefix, followlinks=False):
+ for name in sorted(files + [n for n in folders if (Path(directory) / n).is_symlink()]):
+ path = Path(directory) / name
+ relative = str(path.relative_to(prefix))
+ if path.is_symlink():
+ values[relative] = {'symlink': os.readlink(path)}
+ if path.is_file():
+ values[relative]['sha256'] = digest(path)
+ elif path.is_file():
+ values[relative] = {'sha256': digest(path)}
+ return values
+
+
+def capture(output, retries):
+ attempts = []
+ for number in range(1, retries + 1):
+ before = source_inventory()
+ with tempfile.TemporaryDirectory(prefix='rieke-frozen-source-') as temporary:
+ snapshot = Path(temporary) / 'snapshot'
+ snapshot.mkdir()
+ for name in before:
+ destination = snapshot / name
+ destination.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copyfile(REPO / name, destination)
+ copied = {name: digest(snapshot / name) for name in before}
+ after = source_inventory()
+ changed = sorted(name for name in before.keys() | after.keys()
+ if before.get(name) != after.get(name) or copied.get(name) != before.get(name))
+ attempts.append({'attempt': number, 'changed_paths': changed})
+ if changed:
+ print(f'Snapshot attempt {number} changed during copying; retrying', flush=True)
+ continue
+ destination = output / 'snapshot'
+ shutil.move(str(snapshot), destination)
+ # Check the retained files too, including any cross-filesystem move.
+ if {name: digest(destination / name) for name in before} != before:
+ raise ValueError('Retained snapshot differs from its checked source copy')
+ return destination, before, attempts
+ write_json(output / 'snapshot-rejected.json', {'attempts': attempts, 'release_ready': False})
+ raise RuntimeError('Could not capture a stable source snapshot within the retry limit')
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs', type=int, default=100000)
+ parser.add_argument('--output-dir', type=Path, required=True)
+ parser.add_argument('--copy-attempts', type=int, default=3)
+ parser.add_argument('--native-dense', action='store_true',
+ help='Also run the disposable native 5-tag API/backup benchmark against copied application source')
+ parser.add_argument('--physical-manifest', type=Path,
+ help='Read-only imported recording manifest for the ancestor-metadata round-trip oracle')
+ parser.add_argument('--physical-block', help='Exact block UUID to verify with --physical-manifest')
+ args = parser.parse_args()
+ if not 100 <= args.epochs <= 1000000 or not 1 <= args.copy_attempts <= 5:
+ parser.error('epochs must be 100–1000000; copy-attempts must be 1–5')
+ if bool(args.physical_manifest) != bool(args.physical_block):
+ parser.error('physical-manifest and physical-block must be provided together')
+ output = args.output_dir.resolve()
+ output.mkdir(parents=True, exist_ok=False) # Preserve every earlier run.
+ started = dt.datetime.now(dt.timezone.utc).isoformat()
+ print('Capturing checked source snapshot', flush=True)
+ snapshot, hashes, attempts = capture(output, args.copy_attempts)
+ python_binary = Path(sys.executable).resolve(strict=True)
+ node_binary = Path(shutil.which('node') or '').resolve(strict=True)
+ python_packages = Path(sys.prefix) / f'lib/python{sys.version_info.major}.{sys.version_info.minor}/site-packages'
+ node_modules = REPO / 'workspace-app/node_modules'
+ if not python_packages.is_dir() or not node_binary.is_file():
+ raise ValueError('Run with the installed project venv and Node runtime')
+
+ # A minimal isolated venv avoids executing installed editable .pth files that
+ # point at live application/parser source. Packages are on PYTHONPATH directly.
+ venv = snapshot / '.rieke-runtime/venv'
+ (venv / 'bin').mkdir(parents=True)
+ (venv / f'lib/python{sys.version_info.major}.{sys.version_info.minor}/site-packages').mkdir(parents=True)
+ (venv / 'pyvenv.cfg').write_text(f'home = {python_binary.parent}\ninclude-system-site-packages = false\nversion = {sys.version.split()[0]}\n')
+ for name in ('python', 'python3', f'python{sys.version_info.major}.{sys.version_info.minor}'):
+ (venv / 'bin' / name).symlink_to(python_binary)
+ (snapshot / 'workspace-app/node_modules').symlink_to(node_modules, target_is_directory=True)
+ runtime_bin = output / 'runtime-bin'
+ runtime_bin.mkdir()
+ (runtime_bin / 'node').symlink_to(node_binary)
+ env = dict(os.environ)
+ env.pop('PYTHONHOME', None)
+ env.pop('NODE_PATH', None)
+ env.pop('NODE_OPTIONS', None)
+ env['PYTHONPATH'] = os.pathsep.join(map(str, [snapshot / 'python', snapshot / 'python/tests',
+ snapshot / '.rieke-runtime/retinanalysis/src', python_packages]))
+ env['PYTHONDONTWRITEBYTECODE'] = '1'
+ env['PYTHONNOUSERSITE'] = '1'
+ env['PATH'] = str(runtime_bin) + os.pathsep + env.get('PATH', '')
+ # This runner certifies the explicitly implemented disposable gates only.
+ # Never inherit opt-in flags that could start a native integration workload.
+ disabled_opt_ins = {key: env.pop(key) for key in list(env) if key.startswith('RIEKE_TEST_')}
+ interpreter = venv / 'bin/python'
+ probe = subprocess.run([str(interpreter), '-c',
+ 'import sys,json,importlib.util,importlib.metadata; '
+ 'print(json.dumps({"python":sys.version,"prefix":sys.prefix,"sys_path":sys.path,'
+ '"modules":{n:importlib.util.find_spec(n).origin for n in '
+ '["workspace_service","retinanalysis","h5py","bin2py"]},'
+ '"packages":sorted((d.metadata["Name"],d.version) for d in importlib.metadata.distributions())}))'],
+ cwd=snapshot, env=env, text=True, capture_output=True, check=True)
+ runtime = json.loads(probe.stdout)
+ for name in ('workspace_service', 'retinanalysis'):
+ if not Path(runtime['modules'][name]).is_relative_to(snapshot):
+ raise ValueError('Snapshot resolved live application code: ' + name)
+ runtime['node_version'] = subprocess.check_output([str(node_binary), '--version'], text=True).strip()
+ runtime['external_roots'] = {'python_packages': str(python_packages), 'node_modules': str(node_modules),
+ 'python_binary': str(python_binary), 'node_binary': str(node_binary)}
+ runtime['disabled_native_test_flags'] = sorted(disabled_opt_ins)
+ print('Hashing external dependency files before validation', flush=True)
+ dependencies = dependency_inventory(python_packages, node_modules, python_binary, node_binary)
+ write_json(output / 'dependencies.json', dependencies)
+ write_json(output / 'runtime.json', runtime)
+ manifest = {'original_root': str(REPO), 'snapshot_root': str(snapshot), 'started_at': started,
+ 'source_sha256': hashes, 'source_manifest_sha256': checksum(hashes), 'copy_attempts': attempts,
+ 'dependency_manifest_sha256': checksum(dependencies),
+ 'mapping': 'Every source_sha256 key maps original_root/key to snapshot_root/key; all are copied regular files.',
+ 'external_dependencies': 'Installed Python packages and Node modules are reused and checked before/after; editable .pth files are not executed.',
+ 'coherence_limit': 'Equal inventories before/after copying and copied-file hashes establish an observed stable capture; this is not an operating-system transactional filesystem snapshot.'}
+ write_json(output / 'source-manifest.json', manifest)
+ mysql_root = None
+ mysql_before = None
+ if args.native_dense:
+ sys.path.insert(0, str(REPO / 'python'))
+ from workspace_mysql_runtime import mysql_runtime
+ mysql_root = Path(mysql_runtime(root=REPO)['root']).resolve()
+ mysql_before = mysql_inventory(mysql_root)
+ write_json(output / 'native-runtime.json', {'root': str(mysql_root), 'files': mysql_before,
+ 'scope': 'Runtime executables/assets reused; all app/parser source comes from the copied snapshot. Database is newly created and destroyed by the harness.'})
+ gates = output / 'gates'
+ command = [str(interpreter), str(snapshot / AUDIT / 'run_stress_gates.py'),
+ '--epochs', str(args.epochs), '--output-dir', str(gates)]
+ print('Running fixed-snapshot strict gates', flush=True)
+ timer = time.perf_counter()
+ with (output / 'runner.log').open('w') as log:
+ completed = subprocess.run(command, cwd=snapshot, env=env, stdout=log, stderr=subprocess.STDOUT)
+ native_code = None
+ if args.native_dense:
+ print('Running fixed-snapshot native dense-tag API benchmark', flush=True)
+ native_env = {**env, 'PYTHONPATH': str(snapshot / AUDIT) + os.pathsep + env['PYTHONPATH']}
+ with (output / 'native-dense.log').open('w') as log:
+ native = subprocess.run([str(interpreter), str(snapshot / AUDIT / 'benchmark_native_dense_tags.py'),
+ '--epochs', str(args.epochs), '--samples', '20', '--edits', '20', '--tags', '5',
+ '--mysql-runtime-root', str(mysql_root), '--output', str(output / 'native-dense.json')],
+ cwd=snapshot, env=native_env, stdout=log, stderr=subprocess.STDOUT)
+ native_code = native.returncode
+ physical_code = None
+ if args.physical_manifest:
+ print('Running fixed-snapshot read-only physical metadata oracle', flush=True)
+ with (output / 'physical-metadata.log').open('w') as log:
+ physical = subprocess.run([str(interpreter), str(snapshot / AUDIT / 'metadata_ancestor_physical_oracle.py'),
+ '--manifest', str(args.physical_manifest.resolve()), '--block-uuid', args.physical_block,
+ '--output', str(output / 'physical-metadata.json')],
+ cwd=snapshot, env=env, stdout=log, stderr=subprocess.STDOUT)
+ physical_code = physical.returncode
+ print('Checking snapshot and external dependencies after validation', flush=True)
+ source_changes = [name for name, sha in hashes.items()
+ if not (snapshot / name).is_file() or digest(snapshot / name) != sha]
+ after_dependencies = dependency_inventory(python_packages, node_modules, python_binary, node_binary)
+ dependency_changes = sorted(name for name in dependencies['files'].keys() | after_dependencies['files'].keys()
+ if dependencies['files'].get(name) != after_dependencies['files'].get(name))
+ for name in ('python_binary', 'node_binary'):
+ if dependencies[name] != after_dependencies[name]:
+ dependency_changes.append(name)
+ summary = json.loads((gates / 'summary.json').read_text()) if (gates / 'summary.json').exists() else {}
+ passed = completed.returncode == 0 and summary.get('implemented_gates_passed') is True and not source_changes and not dependency_changes
+ mysql_changes = []
+ if args.native_dense:
+ mysql_after = mysql_inventory(mysql_root)
+ mysql_changes = sorted(name for name in mysql_before.keys() | mysql_after.keys()
+ if mysql_before.get(name) != mysql_after.get(name))
+ passed = passed and native_code == 0 and not mysql_changes
+ if args.physical_manifest:
+ passed = passed and physical_code == 0
+ result = {'epochs': args.epochs, 'captured_source_manifest_sha256': checksum(hashes),
+ 'snapshot_gate_exit_code': completed.returncode, 'implemented_gates_passed': passed,
+ 'snapshot_source_changed_during_run': source_changes, 'external_dependencies_changed_during_run': dependency_changes,
+ 'native_dense_exit_code': native_code, 'native_runtime_changed_during_run': mysql_changes,
+ 'physical_metadata_exit_code': physical_code,
+ 'seconds': time.perf_counter() - timer, 'gate_summary': 'gates/summary.json',
+ 'retained_snapshot': 'snapshot', 'release_ready': False,
+ 'required_unexecuted_trials': summary.get('required_unexecuted_trials', ['Gate runner did not produce a summary']),
+ 'scope': 'Only the captured code was validated. Later edits to the original workspace are not covered.'}
+ write_json(output / 'summary.json', result)
+ print(json.dumps(result, indent=2), flush=True)
+ return 0 if passed else 1
+
+
+if __name__ == '__main__':
+ raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/frontend-benchmark.mjs b/docs/dev/scale-audit-2026-09-29/frontend-benchmark.mjs
new file mode 100644
index 0000000..3479d5e
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/frontend-benchmark.mjs
@@ -0,0 +1,63 @@
+// Disposable synthetic audit. No production requests, source data, or app changes.
+import {createRequire} from 'node:module';
+import {fileURLToPath} from 'node:url';
+import {writeFile} from 'node:fs/promises';
+import os from 'node:os';
+import {performance} from 'node:perf_hooks';
+const root=fileURLToPath(new URL('../../../workspace-app/',import.meta.url));
+const require=createRequire(root+'package.json');
+const {createElement}=require('react');
+const {renderToString}=require('react-dom/server');
+const {createServer}=await import(require.resolve('vite'));
+const {default:react}=await import(require.resolve('@vitejs/plugin-react'));
+const {createResourceCache,cachedResourceRequest,initialEpochTracePath}=await import('../../../workspace-app/src/resourceCache.js');
+const {finiteExtent}=await import('../../../workspace-app/src/components/traceGeometry.js');
+const {registeredMetadataField}=await import('../../../workspace-app/src/components/metadataValues.js');
+const {groupedCellPage}=await import('../../../workspace-app/src/boundedTree.js');
+const output={measured_at:new Date().toISOString(),machine:{node:process.version,platform:process.platform,arch:process.arch,cpu:os.cpus()[0]?.model,total_memory_gib:os.totalmem()/2**30},limitations:['Node SSR and synthetic helper timings, not browser paint / INP / H5 throughput.','No production API requests or original data read; generated identities and metadata only.','Synthetic large catalogs put inspected fields near the end to expose repeated linear search cost.'],measurements:[]};
+const previous=console.error;console.error=(message,...args)=>{if(!String(message).startsWith('Warning: useLayoutEffect does nothing on the server'))previous(message,...args);};
+const server=await createServer({root,configFile:false,plugins:[react()],server:{middlewareMode:true,hmr:false},appType:'custom',logLevel:'error'});
+try{
+ const {default:InspectionCellTree}=await server.ssrLoadModule('/src/components/InspectionCellTree.jsx');
+ const {CellList}=await server.ssrLoadModule('/src/components/Common.jsx');
+ const {default:MetadataPanel}=await server.ssrLoadModule('/src/components/MetadataPanel.jsx');
+ const {default:Overview}=await server.ssrLoadModule('/src/components/Overview.jsx');
+ function measure(name,size,run,repeats=3){
+ const samples=[];let result;
+ for(let i=0;ia-b),row={name,size,samples_ms:samples,median_ms:sorted[Math.floor(sorted.length/2)],result};output.measurements.push(row);console.log(JSON.stringify(row));return row;
+ }
+ const cells=n=>Array.from({length:n},(_,i)=>({cell_uuid:`cell-${i}`,label:`Cell${i}`,date:`2026-09-${String(1+i%28).padStart(2,'0')}`,cell_type:['On Parasol','Off Parasol','Midget'][i%3],epochs:100,duration_seconds:500}));
+ for(const n of [100,1000,10000,50000]){
+ const records=cells(n),props={cells:records,targets:[],source:{kind:'protocol',protocolId:'synthetic',query:''},revision:0};
+ measure('default InspectionCellTree SSR; all dates closed',n,()=>{const html=renderToString(createElement(InspectionCellTree,props));return {cell_details_elements:(html.match(/class="cell-tree-cell"/g)||[]).length,html_bytes:Buffer.byteLength(html)};});
+ measure('bounded CellList SSR',n,()=>{const html=renderToString(createElement(CellList,{cells:records}));return {cell_rows:(html.match(/class="cell-row"/g)||[]).length,html_bytes:Buffer.byteLength(html)};});
+ measure('groupedCellPage helper',n,()=>{const page=groupedCellPage(records,0);return {total:page.total,visible:page.groups.reduce((sum,g)=>sum+g.cells.length,0)};});
+ }
+ for(const n of [100,1000,5000]){
+ const protocols=Array.from({length:n},(_,i)=>({protocol_uuid:`protocol-${i}`,name:`Synthetic Protocol ${i}`,counts:{epochs:i+1,cells:5,duration_seconds:100}}));
+ measure('Overview SSR varying protocol count',n,()=>{const html=renderToString(createElement(Overview,{data:{cells:[],protocols,counts:{},sources:[],events:[]}}));return {protocol_rows:(html.match(/class="ov-protocol-row"/g)||[]).length,html_bytes:Buffer.byteLength(html)};});
+ measure('ProtocolVolumes current exact max-per-row expression',n,()=>{let sum=0;for(const protocol of protocols){const count=protocol.counts?.epochs??protocol.epoch_count??0;sum+=count/Math.max(1,...protocols.map(item=>item.counts?.epochs??item.epoch_count??0))*100;}return {sum};});
+ measure('proposed max-once expression',n,()=>{const max=Math.max(1,...protocols.map(item=>item.counts?.epochs??item.epoch_count??0));let sum=0;for(const protocol of protocols)sum+=(protocol.counts?.epochs??protocol.epoch_count??0)/max*100;return {sum};});
+ }
+ for(const n of [100,1000,10000]){
+ const fields=Array.from({length:n},(_,i)=>({id:`parameters/p${i}`,grouping_priority:i,grouping_role:'technical'}));
+ const parameters=Object.fromEntries(Array.from({length:100},(_,i)=>[`p${n-100+i}`,i]));
+ measure('MetadataPanel SSR 100 parameters near end of field catalog',n,()=>{const html=renderToString(createElement(MetadataPanel,{epoch:{epoch_uuid:'synthetic',parameters},catalog:{data:{fields}}}));return {html_bytes:Buffer.byteLength(html)};});
+ const paths=Object.keys(parameters).map(key=>['parameters',key]);
+ measure('100 registeredMetadataField lookups',n,()=>({found:paths.filter(path=>registeredMetadataField(path,fields)).length}));
+ measure('proposed predecoded field map plus 100 lookups',n,()=>{const index=new Map(fields.map(field=>[JSON.stringify(field.id.split('/').map(part=>decodeURIComponent(part).replace(/~1/g,'/').replace(/~0/g,'~'))),field]));return {found:paths.filter(path=>index.has(JSON.stringify(path))).length};});
+ }
+ const values=Array.from({length:20000},(_,i)=>Math.sin(i/20)*50);
+ measure('bounded 20k trace finiteExtent',20000,()=>finiteExtent(values),5);
+ measure('bounded 20k trace JSON stringify + parse',20000,()=>{const parsed=JSON.parse(JSON.stringify({values}));return {samples:parsed.values.length};},5);
+ const cache=createResourceCache();
+ measure('bounded 20k trace cache put including serialization estimate',20000,()=>{cache.put('/epochs/synthetic/trace',0,{values});return cache.stats();},5);
+ let requests=0;const duplicateCache=createResourceCache();const request=async()=>{requests++;await new Promise(resolve=>setTimeout(resolve,10));return {epoch_uuid:'synthetic'};};
+ await Promise.all([cachedResourceRequest('/epochs/synthetic',{cache:duplicateCache,request}),cachedResourceRequest('/epochs/synthetic',{cache:duplicateCache,request})]);
+ output.duplicate_miss={concurrent_same_key_calls:2,underlying_request_calls:requests};
+ output.trace_window={full_stream_samples:100000000,initial_path:initialEpochTracePath({epoch_uuid:'synthetic',streams:[{kind:'responses',uuid:'response',sample_count:100000000}]})};
+ const boundedCache=createResourceCache();for(let i=0;i<1000;i++)boundedCache.put(`/epochs/synthetic-${i}`,0,{values});output.cache_after_1000_trace_sized_entries=boundedCache.stats();
+ // Matches Inspector.jsx's Promise.all(uuids.map(api(...))) fan-out, without network.
+ for(const n of [1,60,1000]){let active=0,peak=0;await Promise.all(Array.from({length:n},async()=>{active++;peak=Math.max(peak,active);await new Promise(resolve=>setTimeout(resolve,1));active--;}));output.measurements.push({name:'Inspector curation Promise.all GET fan-out simulation',size:n,peak_concurrent_calls:peak});}
+}finally{await server.close();console.error=previous;await writeFile(new URL('./frontend-benchmark.json',import.meta.url),JSON.stringify(output,null,2)+'\n');}
diff --git a/docs/dev/scale-audit-2026-09-29/frontend-selection-race.mjs b/docs/dev/scale-audit-2026-09-29/frontend-selection-race.mjs
new file mode 100644
index 0000000..a7bbbd1
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/frontend-selection-race.mjs
@@ -0,0 +1,22 @@
+// Mounted React lifecycle recovery gate; never contacts a production API.
+// Default writes diagnostic JSON; --strict fails on any stale publication.
+import {writeFile} from 'node:fs/promises';
+import {selectionRaceCases} from '../../../workspace-app/src/test-support/pagedTreeHarness.js';
+const strict=process.argv.includes('--strict');
+let output;
+for(let i=2;iitem.kind==='cell'&&item.change==='filter');
+const failures=cases.flatMap(item=>item.assertions.filter(a=>!a.passed).map(a=>({case:`${item.kind}:${item.change}`,assertion:a.name})));
+// Preserve the original default diagnostic keys for existing readers.
+const result={kind:'Mounted PagedTree React lifecycle with deferred fake API, not browser E2E',started_filter:filterCell.started_filter,current_filter:filterCell.current_filter,
+ old_request_aborted:filterCell.old_request_aborted,published_after_filter_change:filterCell.published_after_rerender.map(({phase,...rest})=>rest),
+ lifecycle:'Real React mount, update, layout effects and unmount; child presentation and API are fixtures.',
+ cases,gate:{strict,passed:failures.length===0,failures,description:'Late cell/range completion must retain current scope and exact UUIDs.'}};
+console.log(JSON.stringify(result,null,2));
+await writeFile(output||new URL(strict?'./frontend-selection-race.strict.json':'./frontend-selection-race.json',import.meta.url),JSON.stringify(result,null,2)+'\n');
+if(strict&&failures.length)process.exitCode=1;
diff --git a/docs/dev/scale-audit-2026-09-29/metadata-field-index.fixed.mjs b/docs/dev/scale-audit-2026-09-29/metadata-field-index.fixed.mjs
new file mode 100644
index 0000000..cb59d45
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/metadata-field-index.fixed.mjs
@@ -0,0 +1,19 @@
+// Synthetic read-only comparison; no production API or recordings.
+import assert from 'node:assert/strict';
+import {performance} from 'node:perf_hooks';
+import {writeFile} from 'node:fs/promises';
+import {registeredMetadataField} from '../../../workspace-app/src/components/metadataValues.js';
+const fields=Array.from({length:10000},(_,i)=>({id:`parameters/p${i}`}));
+const paths=Array.from({length:100},(_,i)=>['parameters',`p${9900+i}`]);
+function original(path,fields){return fields.find(field=>{
+ if(!field.id.includes('/'))return path.length===1&&field.path===path[0];
+ try{const parts=field.id.split('/').map(part=>decodeURIComponent(part).replace(/~1/g,'/').replace(/~0/g,'~'));return parts.length===path.length&&parts.every((part,index)=>part===path[index]);}catch{return false;}
+});}
+const measure=run=>{const start=performance.now();const found=paths.map(run);assert.deepEqual(found,fields.slice(9900));return performance.now()-start;};
+const cold=measure(path=>registeredMetadataField(path,fields));
+const samples=Array.from({length:5},()=>measure(path=>registeredMetadataField(path,fields)));
+const originalSamples=Array.from({length:3},()=>measure(path=>original(path,fields)));
+const median=values=>[...values].sort((a,b)=>a-b)[Math.floor(values.length/2)];
+const result={measured_at:new Date().toISOString(),node:process.version,platform:process.platform,arch:process.arch,field_count:fields.length,lookup_count:paths.length,cold_index_and_lookups_ms:cold,warm_lookup_samples_ms:samples,warm_lookup_median_ms:median(samples),original_scan_samples_ms:originalSamples,original_scan_median_ms:median(originalSamples),exact_field_identity_order_preserved:true,limitations:['Synthetic helper measurement, not browser commit time or end-to-end latency.','Original comparator is the pre-change lookup expression; both implementations return the exact same 100 field objects.']};
+console.log(JSON.stringify(result,null,2));
+await writeFile(new URL('./metadata-field-index.fixed.json',import.meta.url),JSON.stringify(result,null,2)+'\n');
diff --git a/docs/dev/scale-audit-2026-09-29/metadata_ancestor_physical_oracle.py b/docs/dev/scale-audit-2026-09-29/metadata_ancestor_physical_oracle.py
new file mode 100644
index 0000000..624d6b8
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/metadata_ancestor_physical_oracle.py
@@ -0,0 +1,195 @@
+"""Read-only physical-source metadata oracle; all derived artifacts are disposable.
+
+Reads already imported acquisition metadata and H5 owner identities. It tests
+normalization, not parser correctness or waveform values. The duplicated-detail
+comparison is a controlled representation clone of the current index schema,
+not an executable historical product build.
+"""
+import argparse
+import copy
+import datetime as dt
+import hashlib
+import json
+from pathlib import Path
+import resource
+import shutil
+import sqlite3
+import statistics
+import tempfile
+import threading
+import time
+import traceback
+import uuid
+import zlib
+
+import h5py
+from workspace_disk_index import DiskMetadataIndex
+from workspace_projection_cache import ProjectionCache
+from workspace_recipes import checksum
+import workspace_metadata_objects as objects
+import workspace_disk_index as disk_module
+import workspace_projection_cache as projection_module
+
+
+def raw(value):return json.dumps(value,sort_keys=True,ensure_ascii=False,separators=(',',':'),allow_nan=False).encode()
+def sha(path):
+ value=hashlib.sha256()
+ with open(path,'rb') as stream:
+ for block in iter(lambda:stream.read(1024*1024),b''):value.update(block)
+ return value.hexdigest()
+def signature(path):
+ stat=Path(path).stat();return [stat.st_dev,stat.st_ino,stat.st_size,stat.st_mtime_ns,stat.st_ctime_ns]
+def storage(path):
+ files=[path] if path.is_file() else [item for item in path.rglob('*') if item.is_file() and not item.is_symlink()]
+ return {'apparent_bytes':sum(item.stat().st_size for item in files),'allocated_bytes':sum(item.stat().st_blocks*512 for item in files),'files':len(files)}
+def date(value):
+ for pattern in ('%m/%d/%Y','%Y-%m-%d'):
+ try:return dt.datetime.strptime(value[:10],pattern).date().isoformat()
+ except ValueError:pass
+ raise ValueError('Unsupported source date')
+def identity(value):return str(uuid.UUID(value.decode() if isinstance(value,bytes) else str(value)))
+def timed(callback,repeat=1):
+ samples=[];value=None
+ for _ in range(repeat):
+ started=time.perf_counter();value=callback();samples.append(time.perf_counter()-started)
+ return value,{'samples_seconds':samples,'median_seconds':statistics.median(samples),'maximum_seconds':max(samples)}
+def count_shapes(value):
+ if isinstance(value,list):return [len(value),[count_shapes(item) for item in value]]
+ return type(value).__name__
+
+
+def main():
+ parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--manifest',type=Path,required=True)
+ parser.add_argument('--block-uuid',required=True);parser.add_argument('--output',type=Path,required=True);args=parser.parse_args()
+ if args.output.exists():parser.error('Choose an unused receipt')
+ modules=(objects,disk_module,projection_module);hashes=lambda:{module.__name__:sha(module.__file__) for module in modules}
+ report={'scope':__doc__,'source_hashes_before':hashes(),'probe_sha256':sha(__file__),'cases':[],'release_ready':False,
+ 'started_at':dt.datetime.now(dt.timezone.utc).isoformat()}
+ temporary=Path(tempfile.mkdtemp(prefix='rieke-real-metadata-oracle-')).resolve();index=None
+ stopping=threading.Event();phase=['input_verification'];peaks={}
+ def sample_storage():
+ while not stopping.is_set():
+ apparent=allocated=files=0
+ for path in temporary.rglob('*'):
+ try:
+ if path.is_file() and not path.is_symlink():
+ stat=path.stat();apparent+=stat.st_size;allocated+=stat.st_blocks*512;files+=1
+ except FileNotFoundError:pass
+ value=peaks.setdefault(phase[0],{'apparent_bytes':0,'allocated_bytes':0,'files':0,'samples':0})
+ for field,current in [('apparent_bytes',apparent),('allocated_bytes',allocated),('files',files)]:value[field]=max(value[field],current)
+ value['samples']+=1;stopping.wait(.02)
+ monitor=threading.Thread(target=sample_storage,daemon=True);monitor.start()
+ def check(name,passed,**detail):
+ report['cases'].append({'name':name,'passed':bool(passed),**detail});print(json.dumps(report['cases'][-1]),flush=True)
+ if not passed:raise AssertionError(name)
+ try:
+ manifest=json.loads(args.manifest.read_text());metadata_path=Path(manifest['metadata_path']);h5_path=Path(manifest['source_path'])
+ inputs={str(path):signature(path) for path in (args.manifest,metadata_path,h5_path)}
+ check('metadata_matches_imported_sha256',sha(metadata_path)==manifest['metadata_sha256'])
+ source_sha,report['source_hash_verification']=timed(lambda:sha(h5_path))
+ check('physical_recording_matches_imported_sha256',source_sha==manifest['source_sha256'])
+ document=json.loads(metadata_path.read_text());selected=[]
+ for animal in document['animals']:
+ for preparation in animal['preparations']:
+ for cell in preparation['cells']:
+ for group in cell['epoch_groups']:
+ for block in group['epoch_blocks']:
+ if block['uuid']==args.block_uuid:selected.append((cell,group,block))
+ check('block_identity_is_unique_in_source',len(selected)==1)
+ cell,group,block=selected[0];rows={};details={};fingerprints={};frames=block['properties']['frameTimesMs']
+ expected_frames_sha=hashlib.sha256(raw(frames)).hexdigest();expected_shape_sha=hashlib.sha256(raw(count_shapes(frames))).hexdigest()
+ with h5py.File(h5_path,'r') as h5:
+ for epoch in block['epochs']:
+ streams=[]
+ for kind in ('responses','stimuli'):
+ for device,stream in epoch.get(kind,{}).items():
+ handle=h5[stream['h5path']];owner=handle.parent.parent;ancestor=owner.parent.parent
+ assert identity(handle.attrs['uuid'])==stream['uuid'] and identity(owner.attrs['uuid'])==epoch['uuid'] and identity(ancestor.attrs['uuid'])==block['uuid']
+ data=handle.get('data');units=None
+ if kind=='responses' and data is not None and len(data):
+ unit=data[0]['units'];units=unit.decode() if isinstance(unit,bytes) else str(unit)
+ streams.append({'uuid':stream['uuid'],'device':device,'kind':kind,'sample_rate':stream.get('sampleRate'),
+ 'sample_rate_units':stream.get('sampleRateUnits'),'sample_count':len(data) if data is not None else None,
+ 'units':units,'h5_path':stream['h5path'],'data_path':data.name if data is not None else None})
+ duration=max([item['sample_count']/item['sample_rate'] for item in streams if item['kind']=='responses' and item['sample_rate'] and item['sample_count'] is not None],default=0)
+ row={'epoch_uuid':epoch['uuid'],'cell_uuid':cell['uuid'],'cell_label':cell['label'],'cell_type':cell.get('type'),
+ 'date':date(epoch['start_time']),'start_time':epoch['start_time'],'group_uuid':group['uuid'],'group_label':group.get('label'),
+ 'block_uuid':block['uuid'],'block_start_time':block.get('start_time'),'block_end_time':block.get('end_time'),
+ 'protocol_name':block['protocolID'],'duration_seconds':duration,'streams':streams,'source_sha256':source_sha,
+ 'metadata_hash':hashlib.sha256(json.dumps(epoch,sort_keys=True).encode()).hexdigest()}
+ detail={'parameters':epoch.get('parameters',{}),'properties':epoch.get('properties',{}),'attributes':epoch.get('attributes',{}),
+ 'metadata':{'cell':{key:value for key,value in cell.items() if key!='epoch_groups'},
+ 'group':{key:value for key,value in group.items() if key!='epoch_blocks'},
+ 'block':{key:value for key,value in block.items() if key!='epochs'},'epoch':epoch}}
+ rows[epoch['uuid']]=row;details[epoch['uuid']]=detail
+ fingerprints[epoch['uuid']]=hashlib.sha256(json.dumps({'epoch':detail,'source_sha256':source_sha},sort_keys=True,allow_nan=False).encode()).hexdigest()
+ check('physical_stream_parent_ownership_matches_every_selected_epoch',True,epochs=len(rows),streams=sum(len(row['streams']) for row in rows.values()))
+ report['fixture']={'metadata_path':str(metadata_path),'source_path':str(h5_path),'source_sha256':source_sha,
+ 'source_bytes':h5_path.stat().st_size,'metadata_bytes':metadata_path.stat().st_size,'block_uuid':block['uuid'],'epochs':len(rows),
+ 'frame_times_outer_count':len(frames),'frame_times_json_bytes':len(json.dumps(frames).encode()),'frame_times_canonical_bytes':len(raw(frames)),
+ 'frame_times_sha256':expected_frames_sha,'frame_times_shape_sha256':expected_shape_sha}
+ source={'source_sha256':source_sha,'filename':h5_path.name,'source_path':str(h5_path),'experiment_uuid':document['uuid'],
+ 'metadata':{key:value for key,value in document.items() if key!='animals'}}
+ cells={cell['uuid']:{'cell_uuid':cell['uuid'],'label':cell['label'],'cell_type':cell.get('type'),'date':date(cell['start_time']),'start_time':cell['start_time']}}
+ projection={'rows':rows,'details':details,'cells':cells,'source':source,'fingerprints':fingerprints}
+ expected={key:hashlib.sha256(raw(value)).hexdigest() for key,value in details.items()}
+ phase[0]='index_build'
+ index,report['index_build']=timed(lambda:DiskMetadataIndex.build(temporary/'normalized'/'index.sqlite',rows,details,[source],'physical-source-normalization',manifest['project_uuid']))
+ phase[0]='index_validation'
+ check('index_row_ownership_and_fingerprints_exact',index.rows()==rows and index.fingerprints()==fingerprints)
+ def verify_details(mapping):
+ for key in rows:
+ actual=mapping[key]
+ assert hashlib.sha256(raw(actual)).hexdigest()==expected[key]
+ array=actual['metadata']['block']['properties']['frameTimesMs']
+ assert hashlib.sha256(raw(array)).hexdigest()==expected_frames_sha
+ assert hashlib.sha256(raw(count_shapes(array))).hexdigest()==expected_shape_sha
+ return True
+ _,report['all_index_details_read']=timed(lambda:verify_details(index.details))
+ check('all_index_details_arrays_shapes_and_types_exact',True,decoded_ancestors=index._metadata_decoder.object_decodes)
+ check('one_block_decoded_once_across_all_epochs',index._metadata_decoder.object_decodes==3)
+ first=next(iter(rows));changed=index.details[first];changed['metadata']['block']['properties']['frameTimesMs'][0].append(-999)
+ check('mutating_public_index_detail_does_not_change_next_result',hashlib.sha256(raw(index.details[first])).hexdigest()==expected[first])
+ _,report['warm_single_detail_read']=timed(lambda:index.details[first],repeat=20)
+ for lazy in (False,True):
+ value,timing=timed(lambda:index.source_projection(source_sha,lazy_details=lazy));report['source_projection_'+('lazy' if lazy else 'eager')]=timing
+ check('source_projection_'+('lazy' if lazy else 'eager')+'_preserves_exact_details',verify_details(value['details']) and value['fingerprints']==fingerprints)
+ phase[0]='duplicated_index_comparison';baseline=temporary/'duplicated-detail-comparison.sqlite'
+ with sqlite3.connect(index.path.as_uri()+'?mode=ro',uri=True) as source_db:
+ with sqlite3.connect(baseline) as target:
+ source_db.backup(target)
+ target.executemany('UPDATE epochs SET detail_blob=? WHERE epoch_uuid=?',[(zlib.compress(raw(value)),key) for key,value in details.items()])
+ target.execute('DROP TABLE metadata_objects');target.commit();target.execute('VACUUM')
+ report['storage']={'normalized_index':storage(index.path),'duplicated_detail_index_comparison':storage(baseline)}
+ with sqlite3.connect(index.path.as_uri()+'?mode=ro',uri=True) as database:
+ report['index_sql_bytes']={'objects':database.execute('SELECT COUNT(*),SUM(LENGTH(payload)) FROM metadata_objects').fetchone(),
+ 'per_epoch_detail_blobs':database.execute('SELECT SUM(LENGTH(detail_blob)) FROM epochs').fetchone()[0]}
+ with sqlite3.connect(baseline) as database:
+ report['duplicated_detail_blob_bytes']=database.execute('SELECT SUM(LENGTH(detail_blob)) FROM epochs').fetchone()[0]
+ cache=ProjectionCache(temporary/'projections');key={'source_sha256':source_sha,'metadata_sha256':manifest['metadata_sha256'],'oracle':'physical-block'}
+ phase[0]='projection_write'
+ _,report['projection_write']=timed(lambda:cache.write(key,projection))
+ phase[0]='projection_read_and_validation';loaded,report['projection_read']=timed(lambda:cache.read(key))
+ check('projection_cache_preserves_every_detail_and_fingerprint',loaded is not None and verify_details(loaded['details']) and loaded['fingerprints']==fingerprints and loaded['rows']==rows)
+ loaded['details'][first]['metadata']['block']['properties']['frameTimesMs'][0].append(-999)
+ reread=cache.read(key);check('projection_public_mutation_does_not_change_stored_result',hashlib.sha256(raw(reread['details'][first])).hexdigest()==expected[first])
+ report['storage']['normalized_projection']=storage(temporary/'projections')
+ phase[0]='duplicated_projection_comparison';old=temporary/'duplicated-projection-comparison.json.zlib'
+ old.write_bytes(zlib.compress(json.dumps({'version':1,'key_hash':checksum(key),'projection':projection},allow_nan=False,separators=(',',':')).encode(),level=1))
+ report['storage']['duplicated_projection_comparison']=storage(old)
+ report['storage']['all_temporary_artifacts']=storage(temporary)
+ check('all_original_input_signatures_unchanged',all(signature(path)==before for path,before in inputs.items()))
+ report['source_hashes_after']=hashes();report['source_unchanged']=report['source_hashes_before']==report['source_hashes_after'];report['peak_rss_bytes']=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ except Exception as error:report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};print(traceback.format_exc(),flush=True)
+ finally:
+ stopping.set();monitor.join(timeout=2)
+ report['peak_temporary_storage_by_phase']=peaks
+ report['peak_storage_sampling_note']='20ms sampling of disposable files; each phase includes already-built artifacts. Values are observed lower bounds on instantaneous staging peaks.'
+ report['finished_at']=dt.datetime.now(dt.timezone.utc).isoformat()
+ if index is not None:index.close()
+ shutil.rmtree(temporary);report['temporary_artifacts_removed']=not temporary.exists()
+ report['passed']=not report.get('error') and report.get('source_unchanged',False) and all(case['passed'] for case in report['cases']) and report['temporary_artifacts_removed']
+ args.output.write_text(json.dumps(report,indent=2)+'\n')
+ return int(not report['passed'])
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/mounted_scientist_workflow.mjs b/docs/dev/scale-audit-2026-09-29/mounted_scientist_workflow.mjs
new file mode 100644
index 0000000..5c25954
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/mounted_scientist_workflow.mjs
@@ -0,0 +1,45 @@
+// Controlled transport-delay experiment, not browser paint or native HTTP timing.
+import {createWorkflowHarness} from '../../../workspace-app/src/test-support/workflowHarness.js';
+import {writeFile,readFile} from 'node:fs/promises';
+import {createHash} from 'node:crypto';
+import {resolve} from 'node:path';
+const destination=process.argv[2];
+if(!destination)throw Error('Provide a new JSON receipt path and optional immutable baseline src directory');
+const root=resolve(import.meta.dirname,'../../..');
+const baselineRoot=resolve(process.argv[3]||process.env.RIEKE_WORKFLOW_BASELINE_ROOT||resolve(root,'docs/dev/scale-audit-2026-09-29/frozen-100000-dense-recovery/snapshot/workspace-app/src'));
+const baselineFiles=['App.jsx','components/Inspector.jsx','components/AnnotationTags.jsx'];
+const files=['workspace-app/src/App.jsx','workspace-app/src/components/Inspector.jsx','workspace-app/src/components/AnnotationTags.jsx','workspace-app/src/useWorkspaceChanges.js','workspace-app/src/epochBrowserSource.js','workspace-app/src/test-support/workflowHarness.js','workspace-app/src/api.js','workspace-app/src/resourceRequest.js','workspace-app/src/resourceCache.js','workspace-app/src/annotationTags.js','workspace-app/src/curationSelection.js','workspace-app/src/epochSelection.js','workspace-app/src/inspectionCellTree.js','workspace-app/src/components/InspectionCellTree.jsx','workspace-app/src/components/EpochTags.jsx','workspace-app/src/components/ProtocolViewFilter.jsx','workspace-app/src/protocolViewFilter.js','workspace-app/src/components/Common.jsx','workspace-app/src/useImportMonitor.js','workspace-app/package-lock.json'];
+const hash=async path=>createHash('sha256').update(await readFile(path)).digest('hex');
+const hashes=async()=>Object.fromEntries(await Promise.all(files.map(async file=>[file,await hash(resolve(root,file))])));
+const baselineHashes=async()=>Object.fromEntries(await Promise.all(baselineFiles.map(async file=>[file,await hash(resolve(baselineRoot,file))])));
+const report={baseline_root:baselineRoot,baseline_components_before:await baselineHashes(),baseline_description:'Prior App/Inspector/AnnotationTags flow components with current harness and support modules; not a full historical application build.',scope:'Mounted React product request/authority lifecycle with simulated transport; no browser rendering, native database, H5 trace or real-scientist latency claim.',started_at:new Date().toISOString(),before:await hashes(),transport_ms:{save:25,overview:120,page:40,metadata_fields:40,focused_epoch:20,other:10},cases:[]};
+const predicate=name=>({tag_predicate:JSON.stringify({all:[{field:'annotations/effective/tags',operator:'contains',value:name},{not:{field:'annotations/epoch/tags',operator:'contains',value:'excluded'}}]})});
+const delay=record=>record.method==='POST'?25:/^\/(overview|projects|protocol-suggestions|protocols\/[^/?]+)(?:\?|$)/.test(record.path)?120:record.path.includes('/epochs?')||record.path.includes('/tree-fields')||record.path==='/metadata/fields'?40:record.path.startsWith('/epochs/')?20:10;
+const serialize=(records,start)=>records.map(record=>({...record,started_ms:record.started-start,completed_ms:record.completed-start,started:undefined,completed:undefined}));
+for(const total of [500,1000,5000,100000])for(const baseline of [true,false]){
+ const h=await createWorkflowHarness({total,baseline,delay,baselineRoot});
+ try{
+ await h.mount();await h.waitFor(()=>{try{return h.viewer.epoch&&!h.root.findByProps({'aria-label':'Tag this epoch'}).props.disabled;}catch{return false;}});
+ await h.settle(260);
+ const item={epochs:total,implementation:baseline?'prior flow components with current harness/support':'current flow components with current harness/support',operations:[],initial_inspector_mounts:h.fixture.mounts};
+ let mark=h.fixture.requests.length,start=performance.now();
+ await h.act(()=>h.root.findByProps({'aria-label':'Tag this epoch'}).props.onChange({target:{value:'workflow saved tag'}}));
+ await h.act(()=>h.root.findByProps({'aria-label':'Tag this epoch'}).parent.props.onSubmit({preventDefault(){}}));
+ await h.waitFor(()=>{try{return h.viewer.epoch?.annotations.epoch_tags.some(tag=>tag.tag==='workflow saved tag')&&!h.root.findByProps({'aria-label':'Tag this epoch'}).props.disabled;}catch{return false;}});
+ item.operations.push({action:'save shared epoch tag then current authority editable',controlled_completion_ms:performance.now()-start,requests:serialize(h.fixture.requests.slice(mark),start)});
+ await h.settle(260);
+ for(const name of ['A','B','A']){
+ mark=h.fixture.requests.length;start=performance.now();
+ await h.act(()=>h.viewer.onViewFilters(predicate(name)));
+ await h.waitFor(()=>{try{return !h.viewer.navigation.loading&&h.viewer.navigation.total===total/2;}catch{return false;}});
+ item.operations.push({action:`filter ${name}`,controlled_completion_ms:performance.now()-start,requests:serialize(h.fixture.requests.slice(mark),start)});
+ await h.settle(260);
+ }
+ item.final_inspector_mounts=h.fixture.mounts;item.inspector_unmounts=h.fixture.unmounts;
+ report.cases.push(item);
+ console.log(JSON.stringify({epochs:total,baseline,mounts:item.final_inspector_mounts,operations:item.operations.map(operation=>({action:operation.action,requests:operation.requests.length,ms:Math.round(operation.controlled_completion_ms)}))}));
+ }finally{await h.close();}
+}
+report.after=await hashes();report.baseline_components_after=await baselineHashes();report.source_unchanged=JSON.stringify(report.before)===JSON.stringify(report.after)&&JSON.stringify(report.baseline_components_before)===JSON.stringify(report.baseline_components_after);report.finished_at=new Date().toISOString();
+report.limitations=['The renderer mounts actual App/Protocol/Inspector/tag/filter/cell-tree code but replaces layout/trace drawing and unrelated screens.','Synthetic transport delay is controlled; timing differences show dependency order and request fanout, not production p95.','All four declared dataset sizes create bounded page responses and at most one cell summary per500epochs; no raw100k-row React view is mounted.','Open tree/column branches and actual H5 trace latency are outside this mounted collapsed-cell-list measurement.','Native workflow receipts separately measure server operations; neither receipt alone measures browser paint.'];
+await writeFile(destination,JSON.stringify(report,null,2)+'\n',{flag:'wx'});
diff --git a/docs/dev/scale-audit-2026-09-29/mysql_generation_primitives.py b/docs/dev/scale-audit-2026-09-29/mysql_generation_primitives.py
new file mode 100644
index 0000000..ad56ffa
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/mysql_generation_primitives.py
@@ -0,0 +1,147 @@
+"""Probe actual MySQL generation/DDL semantics in an owned disposable server.
+
+This validates native primitives, not the application's forthcoming authority
+module. Never accepts an existing project or database address.
+"""
+import argparse
+import json
+import os
+from pathlib import Path
+import shutil
+import tempfile
+import time
+
+import pymysql
+from workspace_projects import create_project
+from workspace_native_mysql import ensure_native_database,stop_native_database,connection_parameters
+
+DB='rieke_generation_probe'
+
+
+def main():
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--output',type=Path,required=True)
+ parser.add_argument('--recreate-attempts',type=int,default=100)
+ args=parser.parse_args()
+ if args.output.exists():parser.error('Choose an unused output file')
+ report={'scope':'Disposable native SQL primitive probes; not application authority/cache certification','cases':[]}
+ root=Path(tempfile.mkdtemp(prefix='rieke-generation-primitives-'));project=None;connection=None
+ def emit(name,**values):
+ record={'name':name,**values};report['cases'].append(record);print(json.dumps(record,default=str),flush=True)
+ try:
+ project=Path(create_project(root/'projects','Generation primitives')['path'])
+ ensure_native_database(project)
+ connection=pymysql.connect(**connection_parameters(project),autocommit=True)
+ def sql(statement,args=None):
+ with connection.cursor() as cursor:
+ cursor.execute(statement,args);return cursor.fetchall()
+ report['mysql_version']=sql('SELECT VERSION()')[0][0]
+ sql(f'CREATE DATABASE `{DB}`')
+ source_sql=f'''CREATE TABLE `{DB}`.`source` (
+ project_uuid VARCHAR(36) NOT NULL, protocol_uuid VARCHAR(36) NOT NULL,
+ epoch_uuid VARCHAR(36) NOT NULL, tags JSON NOT NULL, revision INT UNSIGNED NOT NULL,
+ PRIMARY KEY(project_uuid,protocol_uuid,epoch_uuid)) ENGINE=InnoDB'''
+ clock_sql=f'''CREATE TABLE `{DB}`.`clock` (
+ project_uuid VARCHAR(36) NOT NULL, protocol_uuid VARCHAR(36) NOT NULL,
+ generation BIGINT UNSIGNED NOT NULL, PRIMARY KEY(project_uuid,protocol_uuid)) ENGINE=InnoDB'''
+ sql(source_sql);sql(clock_sql)
+ def bump(prefix):
+ return f'INSERT INTO `{DB}`.`clock` VALUES ({prefix}.project_uuid,{prefix}.protocol_uuid,1) ON DUPLICATE KEY UPDATE generation=generation+1;'
+ definitions={
+ 'insert':f'CREATE TRIGGER `{DB}`.`bump_insert` AFTER INSERT ON `{DB}`.`source` FOR EACH ROW BEGIN {bump("NEW")} END',
+ 'update':f'CREATE TRIGGER `{DB}`.`bump_update` AFTER UPDATE ON `{DB}`.`source` FOR EACH ROW BEGIN {bump("OLD")} IF OLD.project_uuid<>NEW.project_uuid OR OLD.protocol_uuid<>NEW.protocol_uuid THEN {bump("NEW")} END IF; END',
+ 'delete':f'CREATE TRIGGER `{DB}`.`bump_delete` AFTER DELETE ON `{DB}`.`source` FOR EACH ROW BEGIN {bump("OLD")} END',
+ }
+ for definition in definitions.values():sql(definition)
+ def ddl_status(conn=None):
+ with (conn or connection).cursor() as cursor:
+ cursor.execute("SHOW GLOBAL STATUS WHERE Variable_name IN ('Com_create_trigger','Com_drop_trigger','Com_create_table','Com_drop_table','Com_alter_table','Com_rename_table','Com_truncate','Com_create_db','Com_drop_db')")
+ return {name:int(value) for name,value in cursor.fetchall()}
+ def clock():return sql(f'SELECT project_uuid,protocol_uuid,generation FROM `{DB}`.`clock` ORDER BY project_uuid,protocol_uuid')
+ def table_id(table):return sql('SELECT TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME=%s',(DB+'/'+table,))[0][0]
+ def triggers():return sql('SELECT TRIGGER_NAME,EVENT_MANIPULATION,ACTION_TIMING,ACTION_STATEMENT,CREATED,DEFINER FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s ORDER BY TRIGGER_NAME',(DB,))
+ def source():return sql(f'SELECT project_uuid,protocol_uuid,epoch_uuid,tags,revision FROM `{DB}`.`source` ORDER BY project_uuid,protocol_uuid,epoch_uuid')
+ def insert():sql(f'INSERT INTO `{DB}`.`source` VALUES (%s,%s,%s,%s,1)',('project-A','protocol-A','epoch-A',json.dumps(['QC','tag-A'])))
+ insert();emit('insert',clock=clock(),source=source())
+ previous=clock();sql(f'UPDATE `{DB}`.`source` SET tags=%s',(json.dumps(['QC','tag-B']),))
+ emit('same_revision_update',clock_before=previous,clock_after=clock(),source=source())
+ previous=clock();sql(f'REPLACE INTO `{DB}`.`source` VALUES (%s,%s,%s,%s,1)',('project-A','protocol-A','epoch-A',json.dumps(['QC','tag-C'])))
+ emit('replace',clock_before=previous,clock_after=clock())
+ previous=clock();sql(f'UPDATE `{DB}`.`source` SET project_uuid=%s,protocol_uuid=%s',('project-B','protocol-B'))
+ emit('project_and_protocol_move',clock_before=previous,clock_after=clock())
+ previous=clock();before=source();connection.begin();sql(f'UPDATE `{DB}`.`source` SET tags=%s',(json.dumps(['rollback']),));inside=clock();connection.rollback()
+ emit('rollback',clock_before=previous,clock_inside=inside,clock_after=clock(),source_preserved=source()==before)
+ previous=clock();sql(f'DELETE FROM `{DB}`.`source`');emit('delete_last_row',clock_before=previous,clock_after=clock(),source=source())
+ insert();previous=clock();old_table_id=table_id('source');old_triggers=triggers()
+ sql(f'TRUNCATE TABLE `{DB}`.`source`')
+ emit('truncate_source',clock_unchanged=clock()==previous,table_id_before=old_table_id,table_id_after=table_id('source'),triggers_unchanged=triggers()==old_triggers)
+ insert();previous=clock();old_table_id=table_id('source')
+ sql(f'DROP TABLE `{DB}`.`source`');sql(source_sql)
+ for definition in definitions.values():sql(definition)
+ emit('drop_recreate_source',clock_unchanged=clock()==previous,table_id_before=old_table_id,table_id_after=table_id('source'))
+ insert()
+ # The exposed CREATED timestamp may be too coarse to establish that
+ # coverage never disappeared between reads, even with identical bodies.
+ collision=None;durations=[]
+ for attempt in range(args.recreate_attempts):
+ sql(f'DROP TRIGGER `{DB}`.`bump_update`');sql(definitions['update'])
+ before=triggers();previous=clock();rows_before=source();ddl_before=ddl_status();tick=time.perf_counter()
+ sql(f'DROP TRIGGER `{DB}`.`bump_update`')
+ sql(f'UPDATE `{DB}`.`source` SET tags=%s',(json.dumps(['QC',f'untracked-{attempt}']),))
+ sql(definitions['update']);elapsed=time.perf_counter()-tick;durations.append(elapsed)
+ after=triggers()
+ if before==after:
+ collision={'attempt':attempt+1,'seconds':elapsed,'trigger_signature_unchanged':True,
+ 'source_table_id':table_id('source'),'clock_unchanged':clock()==previous,
+ 'source_changed':source()!=rows_before,'trigger_metadata':after,
+ 'ddl_before':ddl_before,'ddl_after':ddl_status()}
+ break
+ emit('drop_recreate_trigger_coverage_gap',attempts=len(durations),collision=collision,
+ minimum_seconds=min(durations) if durations else None,
+ conclusion='A matching metadata signature hid a missed write' if collision else 'No metadata-signature collision observed; absence is not a proof of continuous trigger coverage')
+ before=ddl_status();sql('FLUSH STATUS');emit('flush_status_same_connection',before=before,after=ddl_status())
+ other=pymysql.connect(**connection_parameters(project),autocommit=True)
+ try:
+ before=ddl_status()
+ with other.cursor() as cursor:cursor.execute('FLUSH STATUS')
+ emit('flush_status_other_connection',before=before,after=ddl_status())
+ before=ddl_status()
+ with other.cursor() as cursor:
+ cursor.execute(f'CREATE TABLE `{DB}`.`scratch` (id INT PRIMARY KEY) ENGINE=InnoDB')
+ cursor.execute(f'DROP TABLE `{DB}`.`scratch`')
+ live=ddl_status()
+ finally:other.close()
+ emit('other_connection_disconnect',before=before,while_connected=live,after_disconnect=ddl_status())
+ for kind,statement in [('drop_trigger',f'DROP TRIGGER `{DB}`.`bump_update`'),('create_trigger',definitions['update']),('alter_table',f'ALTER TABLE `{DB}`.`source` ADD COLUMN probe_column INT NULL')]:
+ before=ddl_status();error=None;prepared=False
+ try:
+ sql('PREPARE ddl_probe FROM %s',(statement,));prepared=True;sql('EXECUTE ddl_probe')
+ except Exception as exc:error={'type':type(exc).__name__,'message':str(exc)}
+ finally:
+ if prepared:sql('DEALLOCATE PREPARE ddl_probe')
+ emit('prepared_'+kind,before=before,after=ddl_status(),error=error)
+ if not any(row[0]=='bump_update' for row in triggers()):sql(definitions['update'])
+ old=table_id('clock');sql(f'TRUNCATE TABLE `{DB}`.`clock`');emit('truncate_generation_table',table_id_before=old,table_id_after=table_id('clock'),clock=clock(),source_count=len(source()))
+ sql(f'UPDATE `{DB}`.`source` SET tags=%s',(json.dumps(['QC','after-clock-reset']),))
+ emit('write_after_missing_generation_row',clock=clock())
+ sql(f'DELETE FROM `{DB}`.`clock`');emit('delete_generation_row',table_id=table_id('clock'),clock=clock(),source_count=len(source()))
+ report['trigger_created_column']=sql('SELECT DATA_TYPE,DATETIME_PRECISION FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s AND COLUMN_NAME=%s',('information_schema','TRIGGERS','CREATED'))
+ except Exception as error:
+ import traceback
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()}
+ print(traceback.format_exc(),flush=True)
+ finally:
+ if connection is not None:connection.close()
+ clean=False
+ if project is not None:
+ try:clean=stop_native_database(project)
+ except Exception as error:report['shutdown_error']=str(error)
+ report['clean_native_shutdown']=clean
+ if clean:shutil.rmtree(root);report['temporary_project_removed']=True
+ else:report['temporary_project_preserved']=str(root)
+ report['release_ready']=False
+ args.output.write_text(json.dumps(report,indent=2,default=str)+'\n')
+ return int(bool(report.get('error')) or not clean)
+
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/mysql_inflight_ddl_probe.py b/docs/dev/scale-audit-2026-09-29/mysql_inflight_ddl_probe.py
new file mode 100644
index 0000000..c2400b0
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/mysql_inflight_ddl_probe.py
@@ -0,0 +1,112 @@
+"""Test counter and metadata visibility while real trigger DDL waits on MDL."""
+import argparse
+import json
+from pathlib import Path
+import shutil
+import struct
+import tempfile
+import threading
+import time
+import traceback
+
+import pymysql
+from workspace_projects import create_project
+from workspace_native_mysql import ensure_native_database,stop_native_database,connection_parameters
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--output',type=Path,required=True);args=ap.parse_args()
+ if args.output.exists():ap.error('Choose unused receipt')
+ root=Path(tempfile.mkdtemp(prefix='rieke-inflight-ddl-'));project=None;clients=[];threads=[];report={'cases':[],'release_ready':False}
+ try:
+ project=Path(create_project(root/'projects','Inflight DDL probe')['path']);ensure_native_database(project)
+ def client():
+ conn=pymysql.connect(**connection_parameters(project),autocommit=True);clients.append(conn);return conn
+ primary=client();holder=client();dropper=client();writer=client();creator=client();inspector=client()
+ def sql(conn,statement,args=None):
+ with conn.cursor() as cur:cur.execute(statement,args);return cur.fetchall()
+ report['mysql_version']=sql(primary,'SELECT VERSION()')[0][0]
+ sql(primary,'CREATE DATABASE ddl_probe');sql(primary,'CREATE TABLE ddl_probe.source(id INT PRIMARY KEY,value INT) ENGINE=InnoDB');sql(primary,'CREATE TABLE ddl_probe.clock(value INT) ENGINE=InnoDB');sql(primary,'INSERT INTO ddl_probe.clock VALUES (0)')
+ sql(primary,'INSERT INTO ddl_probe.source VALUES (1,0)')
+ definition='CREATE TRIGGER ddl_probe.tracked AFTER UPDATE ON ddl_probe.source FOR EACH ROW UPDATE ddl_probe.clock SET value=value+1'
+ sql(primary,definition)
+ def counters():return dict(sql(primary,"SHOW GLOBAL STATUS WHERE Variable_name IN ('Com_create_trigger','Com_drop_trigger')"))
+ report['counters_before']=counters()
+ holder.begin();sql(holder,'SELECT * FROM ddl_probe.source')
+ events={}
+ def background(name,conn,statement):
+ def run():
+ start=time.perf_counter();events[name]={'started':start}
+ try:events[name]['rows']=statement(conn) if callable(statement) else sql(conn,statement)
+ except Exception as error:events[name]['error']=str(error)
+ finally:events[name]['finished']=time.perf_counter();events[name]['seconds']=events[name]['finished']-start
+ thread=threading.Thread(target=run,daemon=True);threads.append(thread);thread.start()
+ background('drop',dropper,'DROP TRIGGER ddl_probe.tracked');time.sleep(.05)
+ background('write',writer,'UPDATE ddl_probe.source SET value=99');time.sleep(.05)
+ background('create',creator,definition);time.sleep(.05)
+ report['counters_while_all_blocked']=counters()
+ report['pending_mdl']=sql(primary,"SELECT OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_TYPE,LOCK_STATUS FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA='ddl_probe'")
+ background('inspect',inspector,"SELECT TRIGGER_NAME,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA='ddl_probe'")
+ time.sleep(.15)
+ report['inspection_finished_before_release']='finished' in events['inspect']
+ report['events_before_release']={name:dict(values) for name,values in events.items()}
+ report['active_statement_instrumentation']=sql(primary,"SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME='events_statements_current'")
+ report['active_ddl_statements']=sql(primary,"SELECT EVENT_NAME,END_EVENT_ID,SQL_TEXT FROM performance_schema.events_statements_current WHERE EVENT_NAME IN ('statement/sql/drop_trigger','statement/sql/create_trigger') AND END_EVENT_ID IS NULL")
+ holder.rollback()
+ for thread in threads:thread.join(timeout=5)
+ report['events']=events;report['counters_after']=counters();report['final_source']=sql(primary,'SELECT * FROM ddl_probe.source');report['final_clock']=sql(primary,'SELECT * FROM ddl_probe.clock')
+ report['all_threads_finished']=all(not thread.is_alive() for thread in threads)
+ # PREPARE exposes the executing underlying DDL instrument, rather than
+ # hiding it behind only a generic EXECUTE event.
+ holder.begin();sql(holder,'SELECT * FROM ddl_probe.source')
+ sql(dropper,"PREPARE pending_alter FROM 'ALTER TABLE ddl_probe.source ADD COLUMN extra_column INT NULL'")
+ background('prepared_alter',dropper,'EXECUTE pending_alter');time.sleep(.1)
+ report['prepared_alter_active']=sql(primary,"SELECT EVENT_NAME,END_EVENT_ID,SQL_TEXT FROM performance_schema.events_statements_current WHERE END_EVENT_ID IS NULL")
+ report['instrumented_foreground']=sql(primary,"SELECT INSTRUMENTED,COUNT(*) FROM performance_schema.threads WHERE TYPE='FOREGROUND' GROUP BY INSTRUMENTED")
+ report['required_consumers']=sql(primary,"SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('global_instrumentation','thread_instrumentation','events_statements_current')")
+ report['ddl_instruments']=sql(primary,"SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN ('statement/sql/create_trigger','statement/sql/drop_trigger','statement/sql/create_table','statement/sql/drop_table','statement/sql/alter_table','statement/sql/rename_table','statement/sql/truncate','statement/sql/create_db','statement/sql/drop_db')")
+ report['executor_instruments']=sql(primary,"SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME LIKE '%Execute%' OR NAME LIKE '%call%' OR NAME LIKE '%event%' OR NAME LIKE '%tablespace%'")
+ report['tablespace_counters']=sql(primary,"SHOW GLOBAL STATUS WHERE Variable_name LIKE 'Com%tablespace%'")
+ report['scheduler_settings']=sql(primary,'SELECT @@event_scheduler,@@log_bin')
+ report['performance_schema_losses']=sql(primary,"SHOW GLOBAL STATUS WHERE Variable_name LIKE 'Performance_schema%lost'")
+ report['uninstrumented_threads']=sql(primary,"SELECT NAME,TYPE,PROCESSLIST_USER FROM performance_schema.threads WHERE INSTRUMENTED='NO'")
+ report['actual_connection_count']=sql(primary,"SHOW GLOBAL STATUS WHERE Variable_name IN ('Connections','Threads_connected')")
+ report['instrumented_connection_population']=sql(primary,"SELECT NAME,PROCESSLIST_ID,PROCESSLIST_USER,PROCESSLIST_COMMAND,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='FOREGROUND' AND PROCESSLIST_ID IS NOT NULL")
+ holder.rollback()
+ for thread in threads:thread.join(timeout=5)
+ report['all_threads_finished']=all(not thread.is_alive() for thread in threads)
+ holder.begin();sql(holder,'SELECT * FROM ddl_probe.source')
+ from pymysql.constants import COMMAND
+ dropper._execute_command(COMMAND.COM_STMT_PREPARE,'ALTER TABLE ddl_probe.source ADD COLUMN binary_column INT NULL')
+ packet=dropper._read_packet();packet.check_error();packet.read_uint8();statement_id=packet.read_uint32()
+ columns=packet.read_uint16();parameters=packet.read_uint16()
+ if columns or parameters:raise AssertionError('Unexpected parameterless DDL preparation metadata')
+ def binary_execute(conn):
+ conn._execute_command(COMMAND.COM_STMT_EXECUTE,struct.pack('=%s AND fold_key<%s",
+ "seconds": 0.0001492909505032003,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.00015729200094938278,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "seconds": 0.0001496669719927013,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00015233299927785993,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013812503311783075,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013883301289752126,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00019345799228176475,
+ "result_rows": 3
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.00014100002590566874,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "seconds": 0.00015254097525030375,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.00016245798906311393,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "seconds": 0.00017329101683571935,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00016587501158937812,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00014037505025044084,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001366669894196093,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00019987503765150905,
+ "result_rows": 3
+ }
+ ],
+ "autocomplete_empty_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.0002447500010021031,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s",
+ "seconds": 0.00015116599388420582,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_rank) WHERE project_uuid=%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.00016816600691527128,
+ "result_rows": 12
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00023812497965991497,
+ "result_rows": 24
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00014874996850267053,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00017024995759129524,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00029045797418802977,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012725003762170672,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00020070798927918077,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001289580250158906,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013695796951651573,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012425001477822661,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012670899741351604,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011995801469311118,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011025002459064126,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011108402395620942,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001033750013448298,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011508399620652199,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011308299144729972,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00014779099728912115,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011537497630342841,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010775000555440784,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011224998161196709,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010175001807510853,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010091596050187945,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010791700333356857,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013529200805351138,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011187494965270162,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00030766602139919996,
+ "result_rows": 12
+ }
+ ]
+ },
+ "autocomplete_requests": {
+ "autocomplete_cold_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_warm_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_empty_diagnostic": {
+ "q": "",
+ "limit": 12,
+ "total": 41
+ }
+ },
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": "const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using index condition; Using filesort"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_authors",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1098",
+ "ref": "const,const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Backward index scan; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 41,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_rank) WHERE project_uuid=%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_rank",
+ "key": "by_rank",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_authors",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 24,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 12,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ }
+ ],
+ "filter20_cpu_profile": {
+ "top_functions": [
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 367,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.709000000000001e-06,
+ "cumulative_seconds": 0.299808417
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 178,
+ "function": "request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.5840000000000005e-06,
+ "cumulative_seconds": 0.29978295800000004
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1163,
+ "function": "get",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.167e-06,
+ "cumulative_seconds": 0.29894175
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/testing.py",
+ "line": 204,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.2415e-05,
+ "cumulative_seconds": 0.298935583
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1062,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.7080000000000005e-06,
+ "cumulative_seconds": 0.29864266700000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 984,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.876e-06,
+ "cumulative_seconds": 0.298629209
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1230,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.707e-06,
+ "cumulative_seconds": 0.298439083
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1529,
+ "function": "__call__",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.375000000000001e-06,
+ "cumulative_seconds": 0.298408708
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1479,
+ "function": "wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.4583e-05,
+ "cumulative_seconds": 0.298402333
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 904,
+ "function": "full_dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.332000000000001e-06,
+ "cumulative_seconds": 0.298125625
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 879,
+ "function": "dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00010991700000000001,
+ "cumulative_seconds": 0.297899792
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 716,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.000116674,
+ "cumulative_seconds": 0.297783083
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 27,
+ "calls": 38,
+ "self_seconds": 3.0665e-05,
+ "cumulative_seconds": 0.14365666600000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 249,
+ "function": "native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.2001e-05,
+ "cumulative_seconds": 0.14359354100000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 268,
+ "function": "_native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.2542e-05,
+ "cumulative_seconds": 0.127715832
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 311,
+ "function": "native_context",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.6543000000000005e-05,
+ "cumulative_seconds": 0.127648709
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 207,
+ "function": "_metadata_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.006965624,
+ "cumulative_seconds": 0.12444129200000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 22,
+ "calls": 4058,
+ "self_seconds": 0.001259605,
+ "cumulative_seconds": 0.11774566800000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 212,
+ "function": "",
+ "primitive_calls": 18,
+ "calls": 18,
+ "self_seconds": 2.0916e-05,
+ "cumulative_seconds": 0.11746775000000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 748,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.000894459,
+ "cumulative_seconds": 0.11737054200000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 778,
+ "function": "_epoch_page_identities",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.007612187,
+ "cumulative_seconds": 0.11643441700000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 55,
+ "function": "_epoch_query_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.08887966400000001,
+ "cumulative_seconds": 0.11188629200000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 261,
+ "function": "filter_epoch_ids",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.016227171000000002,
+ "cumulative_seconds": 0.08914675000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 141,
+ "function": "__exit__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 9.626e-06,
+ "cumulative_seconds": 0.07195900000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 132,
+ "function": "__enter__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 2.21e-06,
+ "cumulative_seconds": 0.07164325
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 8,
+ "function": "matching",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.045147576,
+ "cumulative_seconds": 0.066060375
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 358,
+ "function": "traced_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00021226300000000002,
+ "cumulative_seconds": 0.025589415
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 295,
+ "function": "for_epochs",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00012045400000000001,
+ "cumulative_seconds": 0.0255335
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 412,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00012195600000000001,
+ "cumulative_seconds": 0.024990334000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 402,
+ "function": "_execute_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000130289,
+ "cumulative_seconds": 0.0246865
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 150,
+ "function": "execute",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 5.9329e-05,
+ "cumulative_seconds": 0.024284916
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 332,
+ "function": "_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.950000000000001e-05,
+ "cumulative_seconds": 0.023814921000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 575,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.811600000000001e-05,
+ "cumulative_seconds": 0.023325748
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 535040,
+ "calls": 535040,
+ "self_seconds": 0.022528403000000002,
+ "cumulative_seconds": 0.022528403000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 851,
+ "function": "_read_query_result",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 5.7166e-05,
+ "cumulative_seconds": 0.022226916000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1288,
+ "function": "read",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.6334e-05,
+ "cumulative_seconds": 0.022138332
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 128,
+ "function": "deepcopy",
+ "primitive_calls": 78,
+ "calls": 17170,
+ "self_seconds": 0.009486142000000001,
+ "cumulative_seconds": 0.016137543
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 201,
+ "function": "_deepcopy_list",
+ "primitive_calls": 62,
+ "calls": 102,
+ "self_seconds": 0.000323195,
+ "cumulative_seconds": 0.016002293
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 101,
+ "function": "_rows",
+ "primitive_calls": 46,
+ "calls": 46,
+ "self_seconds": 5.5412000000000004e-05,
+ "cumulative_seconds": 0.015927913000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 197,
+ "function": "response_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.7125000000000002e-05,
+ "cumulative_seconds": 0.015823083
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 233,
+ "function": "_attest_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 6.5579e-05,
+ "cumulative_seconds": 0.015792167
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 227,
+ "function": "_deepcopy_dict",
+ "primitive_calls": 1242,
+ "calls": 1284,
+ "self_seconds": 0.0028571290000000004,
+ "cumulative_seconds": 0.014009929
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 763,
+ "function": "_read_packet",
+ "primitive_calls": 769,
+ "calls": 769,
+ "self_seconds": 0.001213818,
+ "cumulative_seconds": 0.013894895
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1370,
+ "function": "_read_result_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.137200000000001e-05,
+ "cumulative_seconds": 0.011831251000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 869,
+ "function": "to_dicts",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 2.9538e-05,
+ "cumulative_seconds": 0.011663040000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 813,
+ "function": "_read_bytes",
+ "primitive_calls": 1538,
+ "calls": 1538,
+ "self_seconds": 0.0005588720000000001,
+ "cumulative_seconds": 0.010801998
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 1538,
+ "calls": 1538,
+ "self_seconds": 0.000212718,
+ "cumulative_seconds": 0.010166722000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/socket.py",
+ "line": 704,
+ "function": "readinto",
+ "primitive_calls": 69,
+ "calls": 69,
+ "self_seconds": 8.1743e-05,
+ "cumulative_seconds": 0.009954004
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1299,
+ "function": "recv_into",
+ "primitive_calls": 69,
+ "calls": 69,
+ "self_seconds": 8.1827e-05,
+ "cumulative_seconds": 0.009832420000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1157,
+ "function": "read",
+ "primitive_calls": 69,
+ "calls": 69,
+ "self_seconds": 4.7619000000000006e-05,
+ "cumulative_seconds": 0.00973996
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 69,
+ "calls": 69,
+ "self_seconds": 0.009687419000000001,
+ "cumulative_seconds": 0.009687419000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 1231,
+ "function": "cursor",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 1.8547e-05,
+ "cumulative_seconds": 0.009504543
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 845,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.009479084,
+ "cumulative_seconds": 0.009479084
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 797,
+ "function": "",
+ "primitive_calls": 100001,
+ "calls": 100001,
+ "self_seconds": 0.009434314000000001,
+ "cumulative_seconds": 0.009434314000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 34,
+ "function": "_exact_equal",
+ "primitive_calls": 22,
+ "calls": 14072,
+ "self_seconds": 0.0035740480000000002,
+ "cumulative_seconds": 0.009078833000000001
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 134,
+ "function": "_active_ddl_guard",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 3.0915000000000005e-05,
+ "cumulative_seconds": 0.009070625
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1416,
+ "function": "_read_rowdata_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000352256,
+ "cumulative_seconds": 0.008963711000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 173,
+ "function": "_rows",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.3670000000000001e-05,
+ "cumulative_seconds": 0.008347375
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/__init__.py",
+ "line": 138,
+ "function": "jsonify",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.083e-06,
+ "cumulative_seconds": 0.007388958
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 189,
+ "function": "response",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.0583000000000003e-05,
+ "cumulative_seconds": 0.007375958
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 61,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.0917000000000006e-05,
+ "cumulative_seconds": 0.007303917
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1429,
+ "function": "_read_row_from_packet",
+ "primitive_calls": 432,
+ "calls": 432,
+ "self_seconds": 0.0009377070000000001,
+ "cumulative_seconds": 0.006798964
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 42,
+ "function": "",
+ "primitive_calls": 6012,
+ "calls": 16026,
+ "self_seconds": 0.0032064370000000003,
+ "cumulative_seconds": 0.006170135
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 62,
+ "function": "browser_safe",
+ "primitive_calls": 1,
+ "calls": 9641,
+ "self_seconds": 0.003123751,
+ "cumulative_seconds": 0.005727708000000001
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 66,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1363,
+ "self_seconds": 0.001448367,
+ "cumulative_seconds": 0.005725916
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 2,
+ "calls": 102,
+ "self_seconds": 0.000285465,
+ "cumulative_seconds": 0.005714417
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 205733,
+ "calls": 205735,
+ "self_seconds": 0.005465786,
+ "cumulative_seconds": 0.005466203
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 21,
+ "calls": 21,
+ "self_seconds": 0.005024334,
+ "cumulative_seconds": 0.005024334
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 100027,
+ "calls": 100027,
+ "self_seconds": 0.004798761,
+ "cumulative_seconds": 0.004798761
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 100008,
+ "calls": 100008,
+ "self_seconds": 0.004359609,
+ "cumulative_seconds": 0.004359609
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/converters.py",
+ "line": 166,
+ "function": "convert_datetime",
+ "primitive_calls": 144,
+ "calls": 144,
+ "self_seconds": 0.003843023,
+ "cumulative_seconds": 0.004071424000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 8.666e-06,
+ "cumulative_seconds": 0.0035269570000000003
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 44,
+ "function": "",
+ "primitive_calls": 2040,
+ "calls": 2040,
+ "self_seconds": 0.00043074000000000004,
+ "cumulative_seconds": 0.0034155410000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 106487,
+ "calls": 106487,
+ "self_seconds": 0.003149841,
+ "cumulative_seconds": 0.003149841
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 125,
+ "function": "_observer_status",
+ "primitive_calls": 8,
+ "calls": 8,
+ "self_seconds": 2.5582e-05,
+ "cumulative_seconds": 0.002923541
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 38,
+ "function": "walk",
+ "primitive_calls": 1,
+ "calls": 2,
+ "self_seconds": 0.00109325,
+ "cumulative_seconds": 0.002918541
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1448,
+ "function": "_get_descriptions",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000314686,
+ "cumulative_seconds": 0.0027843380000000004
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 115,
+ "function": "_triggers",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.125e-06,
+ "cumulative_seconds": 0.002511625
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 163,
+ "function": "read_length_coded_string",
+ "primitive_calls": 2720,
+ "calls": 2720,
+ "self_seconds": 0.000838437,
+ "cumulative_seconds": 0.002486345
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 107,
+ "function": "_ddl",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 2.6803e-05,
+ "cumulative_seconds": 0.0023111670000000003
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 101,
+ "function": "selected",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.5541000000000002e-05,
+ "cumulative_seconds": 0.002185667
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 898,
+ "function": "",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 5.4698000000000004e-05,
+ "cumulative_seconds": 0.002124293
+ },
+ {
+ "file": "python/workspace_curation.py",
+ "line": 170,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.196e-05,
+ "cumulative_seconds": 0.002065
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 0.001987042,
+ "cumulative_seconds": 0.001987042
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 185,
+ "function": "_metadata",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.5166000000000003e-05,
+ "cumulative_seconds": 0.0019185410000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 899,
+ "function": "",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 0.00034863200000000003,
+ "cumulative_seconds": 0.0018195440000000002
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 195,
+ "function": "_chips",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 4.0573e-05,
+ "cumulative_seconds": 0.0017897870000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 183,
+ "function": "dumps",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 2.9003000000000003e-05,
+ "cumulative_seconds": 0.0017761250000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 183,
+ "function": "encode",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 2.3078e-05,
+ "cumulative_seconds": 0.0017374570000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 205,
+ "function": "iterencode",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 0.00171046,
+ "cumulative_seconds": 0.00171046
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 188,
+ "function": "_validated_tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 6.466000000000001e-05,
+ "cumulative_seconds": 0.0016410840000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 232,
+ "function": "__init__",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 8.0037e-05,
+ "cumulative_seconds": 0.001546038
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 166,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.249e-06,
+ "cumulative_seconds": 0.0015152920000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 236,
+ "function": "_parse_field_descriptor",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 0.00033823,
+ "cumulative_seconds": 0.001445171
+ },
+ {
+ "file": "python/workspace_explorer.py",
+ "line": 60,
+ "function": "protocol_binding_header",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 1.8082e-05,
+ "cumulative_seconds": 0.001431708
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 282,
+ "function": "__and__",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 1.0334000000000001e-05,
+ "cumulative_seconds": 0.001288333
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 178,
+ "function": "restrict",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 8.7711e-05,
+ "cumulative_seconds": 0.0012779990000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 223,
+ "function": "attributes",
+ "primitive_calls": 1006,
+ "calls": 1006,
+ "self_seconds": 0.000372779,
+ "cumulative_seconds": 0.0011755560000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 24353,
+ "calls": 24353,
+ "self_seconds": 0.0010980340000000001,
+ "cumulative_seconds": 0.0011325320000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 299,
+ "function": "loads",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 6.333100000000001e-05,
+ "cumulative_seconds": 0.0010890840000000001
+ }
+ ],
+ "native_sql_calls": [
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0007105830009095371
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00037945801159366965
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00019045901717618108
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00038537499494850636
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00021208298858255148
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003357499954290688
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003048750222660601
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00013566698180511594
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00021674996241927147
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00018758297665044665
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00048754201270639896
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.001238292024936527
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00035670900251716375
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00018274999456480145
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00037170801078900695
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002002500114031136
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00031816697446629405
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00027650000993162394
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014091696357354522
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0002160840085707605
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00036791700404137373
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00018166599329560995
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0001682080328464508
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00022583396639674902
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00029612501384690404
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00046079204184934497
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "seconds": 0.0007599579985253513
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`epoch_uuid`,`included`,`tags`,`review_state`,`revision`,`metadata_fingerprint` FROM `recording_workspace`.`curation` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))AND( ( (`epoch_uuid`='?') OR (",
+ "seconds": 0.001080457994248718
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`dataset_uuid`,`created_at`,`artifact_sha256` FROM `recording_workspace`.`dataset_revision` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00025554205058142543
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.005455167032778263
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.0006175000453367829
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0003607079852372408
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0002090420457534492
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00024645798839628696
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0003185420064255595
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.000686249986756593
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.000373166985809803
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00021479197312146425
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004089579451829195
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00023245898773893714
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003435830003581941
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00036641600308939815
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.0001425419468432665
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00022420799359679222
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.0001866249949671328
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005071249906904995
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.001263874990399927
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003494580159895122
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.0002049999893642962
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00038958300137892365
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00023395800963044167
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00034787500044330955
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00028849998489022255
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00015437498223036528
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00026966602308675647
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004118329961784184
+ }
+ ],
+ "limits": "Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap."
+ },
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": "const,const",
+ "rows": 20,
+ "filtered": 10.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ }
+ ],
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.054597
+ }
+ },
+ "project_uuid": "4dff9ec3-f69a-4387-9e77-a43eb4b14f4a",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.139045
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.052419
+ }
+ },
+ "project_uuid": "4dff9ec3-f69a-4387-9e77-a43eb4b14f4a",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.181751
+ },
+ "seconds": 0.32084808399667963,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "workflow_peak_rss_before_durable_oracle_bytes": 988856320,
+ "durable_snapshot_verify_seconds": 7.740958750015125,
+ "loaded_product_modules": {
+ "workspace_tree": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tree.py",
+ "workspace_predicates": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_predicates.py",
+ "workspace_cache_lifecycle": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_cache_lifecycle.py",
+ "workspace_metadata_objects": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_metadata_objects.py",
+ "workspace_disk_index": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_disk_index.py",
+ "workspace_catalog_identity": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_catalog_identity.py",
+ "recording_workspace": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/recording_workspace.py",
+ "workspace_recipes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_recipes.py",
+ "workspace_tree_code": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tree_code.py",
+ "workspace_service": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_service.py",
+ "workspace_tree_pages": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tree_pages.py",
+ "workspace_projects": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_projects.py",
+ "workspace_mysql_profile": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_mysql_profile.py",
+ "workspace_native_mysql": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_native_mysql.py",
+ "workspace_mysql_runtime": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_mysql_runtime.py",
+ "workspace_storage": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_storage.py",
+ "workspace_diff": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_diff.py",
+ "workspace_protocol_identity": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_protocol_identity.py",
+ "workspace_import_check": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_import_check.py",
+ "workspace_audit": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_audit.py",
+ "workspace_api": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_api.py",
+ "workspace_state_snapshot": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_state_snapshot.py",
+ "workspace_author_preferences": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_author_preferences.py",
+ "workspace_curation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_curation.py",
+ "workspace_annotations": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_annotations.py",
+ "workspace_shared_tag_index": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_shared_tag_index.py",
+ "workspace_paths": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_paths.py",
+ "workspace_desktop_paths": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_desktop_paths.py",
+ "workspace_startup_registry": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_startup_registry.py",
+ "workspace_explorer": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_explorer.py",
+ "workspace_datastores": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_datastores.py",
+ "workspace_suggestions": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_suggestions.py",
+ "workspace_tag_predicates": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tag_predicates.py",
+ "workspace_export_folder": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_export_folder.py",
+ "workspace_import_progress": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_import_progress.py",
+ "workspace_project_preferences": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_project_preferences.py",
+ "workspace_protocol_state": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_protocol_state.py",
+ "workspace_state_generation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_state_generation.py",
+ "workspace_project_servers": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_project_servers.py",
+ "workspace_launcher": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_launcher.py",
+ "workspace_app_routes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_app_routes.py",
+ "workspace_folder_browser": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_folder_browser.py",
+ "workspace_h5_inbox": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_h5_inbox.py",
+ "workspace_tag_exchange": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tag_exchange.py",
+ "workspace_external_tags": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_external_tags.py",
+ "workspace_qc": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_qc.py",
+ "workspace_search": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_search.py",
+ "workspace_matlab_masks": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_matlab_masks.py",
+ "workspace_matlab_routes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_matlab_routes.py",
+ "workspace_mask_refresh": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_mask_refresh.py",
+ "workspace_matching_epochs": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_matching_epochs.py",
+ "workspace_candidate_exports": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_candidate_exports.py",
+ "workspace_backup_scheduler": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_backup_scheduler.py",
+ "workspace_recovery_store": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_recovery_store.py",
+ "workspace_recovery_generation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_recovery_generation.py",
+ "workspace_annotation_preparation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_annotation_preparation.py",
+ "workspace_native_tag_lookup": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_native_tag_lookup.py",
+ "workspace_lifecycle": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_lifecycle.py",
+ "workspace_native_tag_filter": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_native_tag_filter.py"
+ },
+ "owned_native_runtime_stopped": true,
+ "source_inventory_after": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "d81d9c5e03e62024c30474612596ee3fec2753a745e39c2db005010c85b8ffff",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_concurrent_requests.py": "f074a762536ab5bb52abe4ea56faada41babb1cd765512acb3caba18dd503648",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "b70fe1d82baa6ab40901d17e49ef8946297379a6d1adc3929a9cb979ae947e5a",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_empty_blocks.py": "ff0973074a72cf110b90dd8f76f032d5016315bbb21f84569e9fa701b11a7d88",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_event_paging.py": "e79cd1de3bd7469818e255cc01f087a608c11e799a48e81e4ca0ac29ff7a578e",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "3cf4ab9a1b5db849600c2716e9026cfb3d0817389f240d5d9cee9dbfe0b7922d",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "f852f8af8b9795c63772800c3ca3cb7203fa0a1b81e0ef562ef848c1e31a06b0",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7fe035916c152aad2df6f41cc6f5c55e8d30cb4168f53c4ab1aa9e171bdd7cfc",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "a7bc5ddb5f153aacb23c13eb11b1ca14515ddcd12526567f41e49252931ddf96",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6d89f8a735c34e6ec3f7f3db291e5d21c1bd8e64a60e7b12563e82dc18eb9f24",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "8efc55e5fb6079072014c616223872464c0451c6d85bfcb2c61693f9baaed1ff",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "9bf32c59a9847f5b7231987348fd54ad1f6eec4773323b125ae34d1239632fbb",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "8cb336a98cf2f487a3bf30db1ddc6ad5db1081018cee394d2690dffa04f8c1bd",
+ "python/workspace_native_tag_lookup.py": "ae8d7fd3b2f80d56078777b03de4189b648f536ead1ca1225ad6c72a7c59a466",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "40ba54acf2c5e9983c2e2766179a3d365b30ad1cdc4955410571936f7dfb4954",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "68e142d6008248c67310462b692b130f0951a87a802187f59d0e6302a78073fc",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "source_unchanged": true,
+ "harness_unchanged": true,
+ "seconds": 219.8653925419785,
+ "finished_at": "2026-09-30T15:43:14.953838+00:00",
+ "peak_rss_bytes": 988856320,
+ "passed": true
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-500.json b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-500.json
new file mode 100644
index 0000000..34c6ecd
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-500.json
@@ -0,0 +1,3743 @@
+{
+ "scope": "Persistent native tag lookup: tag-ten, tag-another-ten, filter-twenty and cell/epoch hierarchy.\n\nEvery project/database is disposable. Acquisition metadata is synthetic; actual\nMySQL, triggers, Flask routes, derived indexes and each version's save hooks\nare used. Native SQL commits are checked immediately; any background recovery\nmirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude\noracle work, and do not model browser concurrency/rendering. Sequential bundle\ntimes are sums of their separately measured requests. Profiles are separate.\n",
+ "epochs": 500,
+ "profiles": 3,
+ "tags_per_annotation": 5,
+ "protocols": 2,
+ "samples": 5,
+ "source_root": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq",
+ "source_inventory_before": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "d81d9c5e03e62024c30474612596ee3fec2753a745e39c2db005010c85b8ffff",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_concurrent_requests.py": "f074a762536ab5bb52abe4ea56faada41babb1cd765512acb3caba18dd503648",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "b70fe1d82baa6ab40901d17e49ef8946297379a6d1adc3929a9cb979ae947e5a",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_empty_blocks.py": "ff0973074a72cf110b90dd8f76f032d5016315bbb21f84569e9fa701b11a7d88",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_event_paging.py": "e79cd1de3bd7469818e255cc01f087a608c11e799a48e81e4ca0ac29ff7a578e",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "3cf4ab9a1b5db849600c2716e9026cfb3d0817389f240d5d9cee9dbfe0b7922d",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "f852f8af8b9795c63772800c3ca3cb7203fa0a1b81e0ef562ef848c1e31a06b0",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7fe035916c152aad2df6f41cc6f5c55e8d30cb4168f53c4ab1aa9e171bdd7cfc",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "a7bc5ddb5f153aacb23c13eb11b1ca14515ddcd12526567f41e49252931ddf96",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6d89f8a735c34e6ec3f7f3db291e5d21c1bd8e64a60e7b12563e82dc18eb9f24",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "8efc55e5fb6079072014c616223872464c0451c6d85bfcb2c61693f9baaed1ff",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "9bf32c59a9847f5b7231987348fd54ad1f6eec4773323b125ae34d1239632fbb",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "8cb336a98cf2f487a3bf30db1ddc6ad5db1081018cee394d2690dffa04f8c1bd",
+ "python/workspace_native_tag_lookup.py": "ae8d7fd3b2f80d56078777b03de4189b648f536ead1ca1225ad6c72a7c59a466",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "40ba54acf2c5e9983c2e2766179a3d365b30ad1cdc4955410571936f7dfb4954",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "68e142d6008248c67310462b692b130f0951a87a802187f59d0e6302a78073fc",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "fixture_module": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py",
+ "harness_sha256": "26d058c7a3b9c5f67071e38d70df66a566b37cef7b543656b0bcb521c04df133",
+ "started_at": "2026-09-30T15:38:53.807470+00:00",
+ "release_ready": false,
+ "operations": {
+ "first10_revision_preflight": {
+ "samples_seconds": [
+ 0.012720917002297938,
+ 0.006726875028107315,
+ 0.006913584016729146,
+ 0.006534375017508864,
+ 0.006501541996840388
+ ],
+ "p50_seconds": 0.006726875028107315,
+ "p95_seconds": 0.012720917002297938,
+ "p99_seconds": 0.012720917002297938,
+ "maximum_seconds": 0.012720917002297938,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 21988,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 15362,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ }
+ ]
+ },
+ "first10_save": {
+ "samples_seconds": [
+ 0.04435537499375641,
+ 0.020486874971538782,
+ 0.01931745803449303,
+ 0.020893458975479007,
+ 0.018624041986186057
+ ],
+ "p50_seconds": 0.020486874971538782,
+ "p95_seconds": 0.04435537499375641,
+ "p99_seconds": 0.04435537499375641,
+ "maximum_seconds": 0.04435537499375641,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 24816,
+ "Questions": 43
+ },
+ {
+ "Bytes_sent": 12452,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12423,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ }
+ ]
+ },
+ "second10_revision_preflight": {
+ "samples_seconds": [
+ 0.006306124967522919,
+ 0.005953916988801211,
+ 0.005990375007968396,
+ 0.006277291977312416,
+ 0.005971791979391128
+ ],
+ "p50_seconds": 0.005990375007968396,
+ "p95_seconds": 0.006306124967522919,
+ "p99_seconds": 0.006306124967522919,
+ "maximum_seconds": 0.006306124967522919,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15380,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15380,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ }
+ ]
+ },
+ "second10_save": {
+ "samples_seconds": [
+ 0.020426875038538128,
+ 0.019969207991380244,
+ 0.019461542018689215,
+ 0.022125250019598752,
+ 0.017878333979751915
+ ],
+ "p50_seconds": 0.019969207991380244,
+ "p95_seconds": 0.022125250019598752,
+ "p99_seconds": 0.022125250019598752,
+ "maximum_seconds": 0.022125250019598752,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12435,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter20": {
+ "samples_seconds": [
+ 0.03314845898421481,
+ 0.02440887497505173,
+ 0.024143457994796336,
+ 0.02458608301822096,
+ 0.024433874990791082
+ ],
+ "p50_seconds": 0.024433874990791082,
+ "p95_seconds": 0.03314845898421481,
+ "p99_seconds": 0.03314845898421481,
+ "maximum_seconds": 0.03314845898421481,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 164693,
+ "Questions": 71
+ },
+ {
+ "Bytes_sent": 151637,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 151638,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 151638,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 151638,
+ "Questions": 57
+ }
+ ]
+ },
+ "cell_save": {
+ "samples_seconds": [
+ 0.01293837500270456,
+ 0.007577875046990812,
+ 0.008321208006236702,
+ 0.007441041991114616,
+ 0.011222417000681162
+ ],
+ "p50_seconds": 0.008321208006236702,
+ "p95_seconds": 0.01293837500270456,
+ "p99_seconds": 0.01293837500270456,
+ "maximum_seconds": 0.01293837500270456,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4165,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_cell100": {
+ "samples_seconds": [
+ 0.043737958010751754,
+ 0.040947375004179776,
+ 0.04169049998745322,
+ 0.04436779202660546,
+ 0.061345916998106986
+ ],
+ "p50_seconds": 0.043737958010751754,
+ "p95_seconds": 0.061345916998106986,
+ "p99_seconds": 0.061345916998106986,
+ "maximum_seconds": 0.061345916998106986,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 184944,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 184946,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 184947,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 184947,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 184947,
+ "Questions": 57
+ }
+ ]
+ },
+ "filter_cell_and_epoch10": {
+ "samples_seconds": [
+ 0.01991629198892042,
+ 0.02576245804084465,
+ 0.019896209007129073,
+ 0.027239874994847924,
+ 0.02231991698499769
+ ],
+ "p50_seconds": 0.02231991698499769,
+ "p95_seconds": 0.027239874994847924,
+ "p99_seconds": 0.027239874994847924,
+ "maximum_seconds": 0.027239874994847924,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 142260,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 142262,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 142263,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 142263,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 142263,
+ "Questions": 58
+ }
+ ]
+ },
+ "remove_first10_revision_preflight": {
+ "samples_seconds": [
+ 0.00645416701445356,
+ 0.007006958010606468,
+ 0.0065083749941550195,
+ 0.008010582998394966,
+ 0.007031750050373375
+ ],
+ "p50_seconds": 0.007006958010606468,
+ "p95_seconds": 0.008010582998394966,
+ "p99_seconds": 0.008010582998394966,
+ "maximum_seconds": 0.008010582998394966,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15799,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_first10_save": {
+ "samples_seconds": [
+ 0.02056591701693833,
+ 0.020229208981618285,
+ 0.020022916025482118,
+ 0.023306625022087246,
+ 0.019760082999709994
+ ],
+ "p50_seconds": 0.020229208981618285,
+ "p95_seconds": 0.023306625022087246,
+ "p99_seconds": 0.023306625022087246,
+ "maximum_seconds": 0.023306625022087246,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12452,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12423,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_hierarchy_after_epoch_remove": {
+ "samples_seconds": [
+ 0.01473995903506875,
+ 0.01622966700233519,
+ 0.013781792018562555,
+ 0.01838954200502485,
+ 0.013457374996505678
+ ],
+ "p50_seconds": 0.01473995903506875,
+ "p95_seconds": 0.01838954200502485,
+ "p99_seconds": 0.01838954200502485,
+ "maximum_seconds": 0.01838954200502485,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 129792,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 129795,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 129795,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 129795,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 129795,
+ "Questions": 55
+ }
+ ]
+ },
+ "filter_remaining10": {
+ "samples_seconds": [
+ 0.020654416002798826,
+ 0.018453792028594762,
+ 0.03207512496737763,
+ 0.020965208997949958,
+ 0.02284891699673608
+ ],
+ "p50_seconds": 0.020965208997949958,
+ "p95_seconds": 0.03207512496737763,
+ "p99_seconds": 0.03207512496737763,
+ "maximum_seconds": 0.03207512496737763,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 141495,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 141498,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 141498,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 141498,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 141498,
+ "Questions": 57
+ }
+ ]
+ },
+ "remove_second10_revision_preflight": {
+ "samples_seconds": [
+ 0.006793082982767373,
+ 0.00652279204223305,
+ 0.006486875005066395,
+ 0.006847209006082267,
+ 0.006438792042899877
+ ],
+ "p50_seconds": 0.00652279204223305,
+ "p95_seconds": 0.006847209006082267,
+ "p99_seconds": 0.006847209006082267,
+ "maximum_seconds": 0.006847209006082267,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_second10_save": {
+ "samples_seconds": [
+ 0.021633083990309387,
+ 0.024006791994906962,
+ 0.024302124977111816,
+ 0.026654709014110267,
+ 0.020078167028259486
+ ],
+ "p50_seconds": 0.024006791994906962,
+ "p95_seconds": 0.026654709014110267,
+ "p99_seconds": 0.026654709014110267,
+ "maximum_seconds": 0.026654709014110267,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12435,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_after_all_epoch_removals": {
+ "samples_seconds": [
+ 0.015067917003761977,
+ 0.015604250016622245,
+ 0.015238874999340624,
+ 0.013582250045146793,
+ 0.013563792046625167
+ ],
+ "p50_seconds": 0.015067917003761977,
+ "p95_seconds": 0.015604250016622245,
+ "p99_seconds": 0.015604250016622245,
+ "maximum_seconds": 0.015604250016622245,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 129035,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 129038,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 129038,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 129038,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 129038,
+ "Questions": 54
+ }
+ ]
+ },
+ "remove_cell_save": {
+ "samples_seconds": [
+ 0.007775167003273964,
+ 0.008647124981507659,
+ 0.009320041979663074,
+ 0.007871415989939123,
+ 0.008884500013664365
+ ],
+ "p50_seconds": 0.008647124981507659,
+ "p95_seconds": 0.009320041979663074,
+ "p99_seconds": 0.009320041979663074,
+ "maximum_seconds": 0.009320041979663074,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4165,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_after_cell_removal": {
+ "samples_seconds": [
+ 0.014741291000973433,
+ 0.01508387498324737,
+ 0.014086208015214652,
+ 0.013328332977835089,
+ 0.015477415989153087
+ ],
+ "p50_seconds": 0.014741291000973433,
+ "p95_seconds": 0.015477415989153087,
+ "p99_seconds": 0.015477415989153087,
+ "maximum_seconds": 0.015477415989153087,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 129035,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 129038,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 129038,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 129038,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 129038,
+ "Questions": 54
+ }
+ ]
+ },
+ "tag10_then10_filter20_http_total": {
+ "samples_seconds": [
+ 0.11695775098633021,
+ 0.07754574995487928,
+ 0.07582641707267612,
+ 0.08041645900812,
+ 0.07340958493296057
+ ],
+ "p50_seconds": 0.07754574995487928,
+ "p95_seconds": 0.11695775098633021,
+ "p99_seconds": 0.11695775098633021,
+ "maximum_seconds": 0.11695775098633021,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "samples_seconds": [
+ 0.056676333013456315,
+ 0.04852525005117059,
+ 0.050011707993689924,
+ 0.05180883401772007,
+ 0.07256833399878815
+ ],
+ "p50_seconds": 0.05180883401772007,
+ "p95_seconds": 0.07256833399878815,
+ "p99_seconds": 0.07256833399878815,
+ "maximum_seconds": 0.07256833399878815,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "first10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.05707629199605435,
+ 0.027213749999646097,
+ 0.026231042051222175,
+ 0.02742783399298787,
+ 0.025125583983026445
+ ],
+ "p50_seconds": 0.027213749999646097,
+ "p95_seconds": 0.05707629199605435,
+ "p99_seconds": 0.05707629199605435,
+ "maximum_seconds": 0.05707629199605435,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "second10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.026733000006061047,
+ 0.025923124980181456,
+ 0.02545191702665761,
+ 0.028402541996911168,
+ 0.023850125959143043
+ ],
+ "p50_seconds": 0.025923124980181456,
+ "p95_seconds": 0.028402541996911168,
+ "p99_seconds": 0.028402541996911168,
+ "maximum_seconds": 0.028402541996911168,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "autocomplete_cold_diagnostic": {
+ "samples_seconds": [
+ 0.02161020803032443
+ ],
+ "p50_seconds": 0.02161020803032443,
+ "p95_seconds": 0.02161020803032443,
+ "p99_seconds": 0.02161020803032443,
+ "maximum_seconds": 0.02161020803032443,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 249865,
+ "Questions": 95
+ }
+ ]
+ },
+ "autocomplete_warm_diagnostic": {
+ "samples_seconds": [
+ 0.02496337500633672
+ ],
+ "p50_seconds": 0.02496337500633672,
+ "p95_seconds": 0.02496337500633672,
+ "p99_seconds": 0.02496337500633672,
+ "maximum_seconds": 0.02496337500633672,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 249865,
+ "Questions": 95
+ }
+ ]
+ },
+ "autocomplete_empty_diagnostic": {
+ "samples_seconds": [
+ 0.030342624988406897
+ ],
+ "p50_seconds": 0.030342624988406897,
+ "p95_seconds": 0.030342624988406897,
+ "p99_seconds": 0.030342624988406897,
+ "maximum_seconds": 0.030342624988406897,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 257580,
+ "Questions": 116
+ }
+ ]
+ },
+ "same_server_native_reuse_filter20": {
+ "samples_seconds": [
+ 0.026880667021032423
+ ],
+ "p50_seconds": 0.026880667021032423,
+ "p95_seconds": 0.026880667021032423,
+ "p99_seconds": 0.026880667021032423,
+ "maximum_seconds": 0.026880667021032423,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 151639,
+ "Questions": 57
+ }
+ ]
+ }
+ },
+ "checks": [
+ {
+ "name": "populated_native_migration_ready_before_timed_operations",
+ "passed": true
+ },
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "autocomplete_has_no_membership_aggregation",
+ "passed": true,
+ "membership_selects": 30,
+ "maximum_rows_per_seek": 1
+ },
+ {
+ "name": "autocomplete_compact_dictionary_fetches_are_bounded",
+ "passed": true,
+ "dictionary_selects": 9,
+ "scope": "Counts may scan matching compact entries; no unbounded dictionary rows are fetched for Python prefix filtering."
+ },
+ {
+ "name": "autocomplete_membership_winner_seeks_are_indexed",
+ "passed": true,
+ "plans": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1098",
+ "ref": "const,const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Backward index scan; Using index"
+ }
+ ]
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 167
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "page_cells": true,
+ "target_specification": "tag10_then10_filter20_and_cell_epoch_hierarchy",
+ "baseline_harness_sha256": "272fe050c8762ed4ea45bd013e236e8723d8fe46f5e7e293c58e2edf1fd52ddc",
+ "mysql_version": "8.4.2",
+ "seed_seconds": 0.08865733398124576,
+ "populated_native_startup_seconds": 1.002701541990973,
+ "fixture_construction": {
+ "strategy": "canonical seed before normal app startup and populated native migration",
+ "native_derived_tables_absent_during_seed": true,
+ "canonical_shared_records": 1515,
+ "canonical_curation_records": 1000,
+ "product_triggers_disabled_during_timed_operations": false
+ },
+ "seed_checkpoint_seconds": 0.05130733299301937,
+ "native_lookup_startup_preparation": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.06283
+ }
+ },
+ "project_uuid": "69b6a7e7-762d-4adf-ad65-b1c3a8eff7f1",
+ "reused": false,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.436276
+ },
+ "selected_epoch_uuids": {
+ "first10": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5"
+ ],
+ "second10": [
+ "ad573668-6512-6e55-649e-cb23ae1d4888",
+ "16dc368a-89b4-28b2-4854-84313ba67a39",
+ "37834f2f-2576-2f23-e1f7-4a531cbe445d",
+ "454f63ac-30c8-3229-97ef-025edff6abd2",
+ "5ef6fdf3-2513-aa7c-d11f-72beccf132b9",
+ "1253e937-3e78-1b75-0026-6caa55150e08",
+ "482d9673-cfee-5de3-91f9-7fde4d1c84f9",
+ "3346f2bb-f6c3-4bd2-dbe2-8bd1bb657d0e",
+ "9537f32e-c759-9e1a-e953-af6c9f929fe7",
+ "0fd42b3f-73c4-48b3-4940-b339f87d07ad"
+ ],
+ "cell": "0604cd31-38fe-ed20-2ef2-93e062da2f47",
+ "cell_children": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5",
+ "4a44dc15-3642-04a8-0fe8-0e9039455cc1",
+ "4fc82b26-aecb-47d2-868c-4efbe3581732",
+ "6b51d431-df5d-7f14-1cbe-ceccf79edf3d",
+ "3fdba35f-04dc-8c46-2986-c992bcf87554",
+ "8527a891-e224-1369-50ff-32ca212b45bc",
+ "e629fa65-98d7-3276-8f7c-726b4b621285",
+ "b17ef6d1-9c7a-5b1e-e83b-907c595526dc",
+ "4523540f-1504-cd17-100c-4835e85b7eef",
+ "4ec9599f-c203-d176-a301-536c2e091a19",
+ "9400f1b2-1cb5-27d7-fa3d-3eabba93557a",
+ "f5ca38f7-48a1-d6ea-f726-b8a42fb575c3",
+ "6f4b6612-125f-b3a0-daec-d2799dfd6c9c",
+ "785f3ec7-eb32-f30b-90cd-0fcf3657d388",
+ "535fa30d-7e25-dd8a-49f1-536779734ec8",
+ "c2356069-e9d1-e79c-a924-378153cfbbfb",
+ "b7a56873-cd77-1f2c-446d-369b649430b6",
+ "5f9c4ab0-8cac-7457-e911-1a30e4664920",
+ "670671cd-9740-4156-226e-507973f2ab83",
+ "59e19706-d51d-39f6-6711-c2653cd7eb12",
+ "35135aaa-6cc2-3891-b40c-b3f378c53a17",
+ "624b60c5-8c9d-8bfb-6ff1-886c2fd605d2",
+ "eb1e33e8-a81b-697b-7585-5af6bfcdbcbf",
+ "e29c9c18-0c62-79b0-b02a-bd6a1801c7c0",
+ "c6f3ac57-944a-5314-90cd-39902d0f7777",
+ "86e50149-6586-6131-2a9e-0b35558d84f6",
+ "9f14025a-f006-5b30-e47e-23ebb3b491d3",
+ "76a50887-d8f1-c2e9-3017-55428990ad81",
+ "7a61b537-01be-fdae-0eee-ffaecc73f14e",
+ "aea92132-c4cb-eb26-3e6a-c2bf6c183b5d",
+ "0b918943-df09-62bc-7a18-24c0555a3893",
+ "d59eced1-ded0-7f84-c145-592f65bdf854",
+ "3d914f93-48c9-cc0f-f8a7-9716700b9fcd",
+ "73475cb4-0a56-8e8d-a8a0-45ced110137e",
+ "44cb730c-4204-80a0-477b-505ae68af508",
+ "71ee45a3-c0db-9a98-65f7-313dd3372cf6",
+ "811786ad-1ae7-4adf-dd20-dd0372abaaeb",
+ "25fc0e70-96fc-6537-1820-2dc30b0c580b",
+ "31489056-e091-6d59-fe3a-dd79e63f095a",
+ "98010bd9-270f-9b10-0b62-14a21754fd33",
+ "0e17daca-5f3e-175f-448b-acace3bc0da4",
+ "1a656259-0ef1-9d10-45d0-6c4055742d38",
+ "031b4af5-197e-c30a-926f-48cf40e11a7d",
+ "41cfc0d1-f2d1-27b0-4555-b7246d84019b",
+ "2858dcd1-057d-3eae-7f7d-5f782167e24b",
+ "2fca346d-b656-1871-02ce-806ac732e06a",
+ "02d20bbd-7e39-4ad5-999a-4cebabac9619",
+ "7688b6ef-5255-5962-d008-fff894223582",
+ "c837649c-ce43-f272-9138-e72cc3152070",
+ "6208ef0f-7750-c111-548c-f90b6ea1d0d0",
+ "3e1e967e-9b79-3e90-8f8e-ae83c74dba9b",
+ "39fa9ec1-90ee-e7b6-f4df-f1100d6343e1",
+ "d029fa3a-95e1-74a1-9934-857f535eb942",
+ "81b8a03f-97e8-787c-53fe-1a86bda042b6",
+ "da4ea2a5-506f-2693-eae1-90d9360a1f31",
+ "a68b412c-4282-555f-1554-6cf6e1fc4289",
+ "108c995b-953c-8a35-5611-03e2014cf828",
+ "3ada92f2-8b4c-eda3-8562-ebf047c6ff05",
+ "49d180ec-f561-3281-9571-bf39d9b7b342",
+ "a21855da-08cb-102d-1d21-7c53dc5824a3",
+ "c75cb66a-e28d-8ebc-6ede-d002c28a8ba0",
+ "ff5a1ae0-12af-a5d4-c889-c50ad427aaf5",
+ "7f2253d7-e228-b22a-08bd-a1f09c516f6f",
+ "87226162-0421-7edd-b39e-7df969e0698a",
+ "96061e92-f58e-4bdc-dee7-3df36183fe3a",
+ "eb624dbe-56eb-6620-ae62-080c10a273ca",
+ "f369cb89-fc62-7e66-8987-007d121ed1ea",
+ "f74efabe-f12e-a619-e30b-79bddef89cff",
+ "a88a7902-cb4e-f697-ba0b-6759c50e8c10",
+ "349c4120-1b62-db85-1192-665c504b350f",
+ "98a3ab7c-340e-8a03-3e7b-37b6ef942875",
+ "48449a14-a4ff-7d79-bb7a-1b6f3d488eba",
+ "5316ca1c-5ddc-a8e6-cecc-fce58f3b8540",
+ "a46e3763-2fa6-ca51-a13f-e39a567b3c23",
+ "bbb965ab-0c80-d653-8cf2-184babad2a56",
+ "44c8031c-b036-a735-0d8b-9b8603af662a",
+ "b4944c6f-f08d-c6f4-3da2-e9c824669b7d",
+ "434c9b5a-e514-646b-bd91-b50032ca579e",
+ "bdd2d3af-3a5a-1213-497d-4f1f7bfcda89",
+ "8b940be7-fb78-aaa6-b656-7dd7a3987996",
+ "cd70bea0-23f7-52a0-564a-bb6ed08d42c1",
+ "69f59c27-3b6e-669a-c32a-6dd5e1b2cb63",
+ "1da51b8d-8ff9-8f6a-48f8-0ae79fe3ca6c",
+ "82416496-09f8-8ccd-2a0a-5b233a07a538",
+ "6e400187-1c0c-f27c-7634-ef1dc478408f",
+ "e3d6c4d4-599e-0088-2384-ca981ee287ed",
+ "ad48ff99-415b-2f00-7dc3-5b7eb553fd1e",
+ "7b1a278f-5abe-8e9d-a907-fc9c29dfd432",
+ "d6d824ab-ba4a-fde8-1129-c71dea75b810",
+ "29db0c67-82db-d500-0559-ef4d9e953e30",
+ "8c1f1046-219d-dd21-6a02-3f792356ddf1"
+ ]
+ },
+ "backup_receipts": [
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 1,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 2,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 3,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 4,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 5,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 6,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 7,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 8,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 9,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 10,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 11,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 12,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 13,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 14,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 15,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 16,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 17,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 18,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 19,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 20,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 21,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 22,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 23,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 24,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 25,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 26,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 27,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 28,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 29,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 30,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:39:03.414050+00:00",
+ "pending": true,
+ "requested_sequence": 31,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:39:03.414050+00:00",
+ "pending": true,
+ "requested_sequence": 32,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_first10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:39:03.771515+00:00",
+ "pending": true,
+ "requested_sequence": 33,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_second10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:39:03.771515+00:00",
+ "pending": true,
+ "requested_sequence": 34,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ }
+ ],
+ "preparation_index_seconds": 0,
+ "lookup_mode": "persistent_native_sql_no_post_seed_tag_cache_preparation",
+ "bundle_definitions": {
+ "tag10_then10_filter20_http_total": [
+ "first10_revision_preflight",
+ "first10_save",
+ "second10_revision_preflight",
+ "second10_save",
+ "filter20"
+ ],
+ "cell_tag_then_cell_filter_http_total": [
+ "cell_save",
+ "filter_cell100"
+ ],
+ "first10_selected_action_http_total": [
+ "first10_revision_preflight",
+ "first10_save"
+ ],
+ "second10_selected_action_http_total": [
+ "second10_revision_preflight",
+ "second10_save"
+ ]
+ },
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:39:04.669427+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.00012283300748094916,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "seconds": 0.0001301250304095447,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.00014424999244511127,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "seconds": 0.00013224995927885175,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001396249863319099,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013120798394083977,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011324998922646046,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00016866601072251797,
+ "result_rows": 3
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.0001443749642930925,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "seconds": 0.000144040968734771,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.0001740000443533063,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "seconds": 0.00016950001008808613,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00016325002070516348,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013179198140278459,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00019791699014604092,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00021045899484306574,
+ "result_rows": 3
+ }
+ ],
+ "autocomplete_empty_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.0002182909520342946,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s",
+ "seconds": 0.00022295804228633642,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_rank) WHERE project_uuid=%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.0002453330089338124,
+ "result_rows": 12
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0002537500113248825,
+ "result_rows": 24
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00014916702639311552,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013404199853539467,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011974998051300645,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00017691700486466289,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013070798013359308,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011962500866502523,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012362503912299871,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012741703540086746,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001378749730065465,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013474997831508517,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001284580212086439,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001437499886378646,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013579102233052254,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013800000306218863,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011987495236098766,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011399999493733048,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011145800817757845,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011766701936721802,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011616700794547796,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012120796600356698,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00020866701379418373,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00018604198703542352,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010916695464402437,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010958401253446937,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0004837500164285302,
+ "result_rows": 9
+ }
+ ]
+ },
+ "autocomplete_requests": {
+ "autocomplete_cold_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_warm_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_empty_diagnostic": {
+ "q": "",
+ "limit": 12,
+ "total": 41
+ }
+ },
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": "const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using index condition; Using filesort"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_authors",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1098",
+ "ref": "const,const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Backward index scan; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 41,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_rank) WHERE project_uuid=%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_rank",
+ "key": "by_rank",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_authors",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 24,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 9,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ }
+ ],
+ "filter20_cpu_profile": {
+ "top_functions": [
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 367,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.1124e-05,
+ "cumulative_seconds": 0.061389000000000006
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 178,
+ "function": "request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.125e-06,
+ "cumulative_seconds": 0.061336792
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1163,
+ "function": "get",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.792000000000001e-06,
+ "cumulative_seconds": 0.06049525
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/testing.py",
+ "line": 204,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.2833e-05,
+ "cumulative_seconds": 0.060489458
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1062,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.207e-06,
+ "cumulative_seconds": 0.060191083000000006
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 984,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0500000000000001e-05,
+ "cumulative_seconds": 0.06017675
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1230,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.792e-06,
+ "cumulative_seconds": 0.059981458
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1529,
+ "function": "__call__",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.2080000000000005e-06,
+ "cumulative_seconds": 0.059950750000000004
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1479,
+ "function": "wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.0751000000000002e-05,
+ "cumulative_seconds": 0.059944542
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 904,
+ "function": "full_dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.168000000000001e-06,
+ "cumulative_seconds": 0.059649958
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 879,
+ "function": "dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.000106582,
+ "cumulative_seconds": 0.059401958000000005
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 716,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.7089e-05,
+ "cumulative_seconds": 0.059291750000000004
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 358,
+ "function": "traced_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00026604600000000003,
+ "cumulative_seconds": 0.024827042
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 412,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00012503200000000002,
+ "cumulative_seconds": 0.024108872
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 27,
+ "calls": 38,
+ "self_seconds": 3.158e-05,
+ "cumulative_seconds": 0.024036416
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 249,
+ "function": "native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.2417000000000002e-05,
+ "cumulative_seconds": 0.0239665
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 402,
+ "function": "_execute_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000135208,
+ "cumulative_seconds": 0.023781291000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 150,
+ "function": "execute",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.367e-05,
+ "cumulative_seconds": 0.023339248
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 332,
+ "function": "_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 8.504e-05,
+ "cumulative_seconds": 0.022810372000000002
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 295,
+ "function": "for_epochs",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.000122533,
+ "cumulative_seconds": 0.022550583000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 575,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 8.654100000000001e-05,
+ "cumulative_seconds": 0.022253747
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 851,
+ "function": "_read_query_result",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.2786e-05,
+ "cumulative_seconds": 0.021033123
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1288,
+ "function": "read",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 8.592800000000001e-05,
+ "cumulative_seconds": 0.020935212
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 101,
+ "function": "_rows",
+ "primitive_calls": 46,
+ "calls": 46,
+ "self_seconds": 6.7877e-05,
+ "cumulative_seconds": 0.019479081000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 197,
+ "function": "response_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.0918000000000004e-05,
+ "cumulative_seconds": 0.019120916
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 233,
+ "function": "_attest_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 7.7917e-05,
+ "cumulative_seconds": 0.019074625
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 128,
+ "function": "deepcopy",
+ "primitive_calls": 78,
+ "calls": 17170,
+ "self_seconds": 0.009763469,
+ "cumulative_seconds": 0.016600959000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 201,
+ "function": "_deepcopy_list",
+ "primitive_calls": 62,
+ "calls": 102,
+ "self_seconds": 0.000320709,
+ "cumulative_seconds": 0.016466456
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 763,
+ "function": "_read_packet",
+ "primitive_calls": 769,
+ "calls": 769,
+ "self_seconds": 0.0012844870000000002,
+ "cumulative_seconds": 0.015917263
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 227,
+ "function": "_deepcopy_dict",
+ "primitive_calls": 1242,
+ "calls": 1284,
+ "self_seconds": 0.002916516,
+ "cumulative_seconds": 0.014378134
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 813,
+ "function": "_read_bytes",
+ "primitive_calls": 1538,
+ "calls": 1538,
+ "self_seconds": 0.0005917450000000001,
+ "cumulative_seconds": 0.012605694
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 141,
+ "function": "__exit__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 4.542e-06,
+ "cumulative_seconds": 0.012342083
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 1538,
+ "calls": 1538,
+ "self_seconds": 0.000241913,
+ "cumulative_seconds": 0.011928618
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/socket.py",
+ "line": 704,
+ "function": "readinto",
+ "primitive_calls": 69,
+ "calls": 69,
+ "self_seconds": 9.7377e-05,
+ "cumulative_seconds": 0.011686705
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 132,
+ "function": "__enter__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 3.0020000000000002e-06,
+ "cumulative_seconds": 0.011631209
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1299,
+ "function": "recv_into",
+ "primitive_calls": 69,
+ "calls": 69,
+ "self_seconds": 9.885900000000001e-05,
+ "cumulative_seconds": 0.011547792000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1157,
+ "function": "read",
+ "primitive_calls": 69,
+ "calls": 69,
+ "self_seconds": 5.5245000000000004e-05,
+ "cumulative_seconds": 0.011437707
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 69,
+ "calls": 69,
+ "self_seconds": 0.011377330000000001,
+ "cumulative_seconds": 0.011377330000000001
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 134,
+ "function": "_active_ddl_guard",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 4.6920000000000005e-05,
+ "cumulative_seconds": 0.01135325
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1370,
+ "function": "_read_result_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.7586e-05,
+ "cumulative_seconds": 0.008834206
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 869,
+ "function": "to_dicts",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 2.5211000000000003e-05,
+ "cumulative_seconds": 0.007760958
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/__init__.py",
+ "line": 138,
+ "function": "jsonify",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.208e-06,
+ "cumulative_seconds": 0.007474459
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 189,
+ "function": "response",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.9584e-05,
+ "cumulative_seconds": 0.0074592090000000005
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 61,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.8416000000000005e-05,
+ "cumulative_seconds": 0.007350541
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 62,
+ "function": "browser_safe",
+ "primitive_calls": 1,
+ "calls": 9641,
+ "self_seconds": 0.003248995,
+ "cumulative_seconds": 0.005733625
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 66,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1363,
+ "self_seconds": 0.001419317,
+ "cumulative_seconds": 0.0057311670000000006
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 2,
+ "calls": 102,
+ "self_seconds": 0.000207082,
+ "cumulative_seconds": 0.005717626
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1416,
+ "function": "_read_rowdata_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000383267,
+ "cumulative_seconds": 0.005678291
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 1231,
+ "function": "cursor",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 1.775e-05,
+ "cumulative_seconds": 0.00552504
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 268,
+ "function": "_native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.85e-05,
+ "cumulative_seconds": 0.004784626
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 311,
+ "function": "native_context",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.2168e-05,
+ "cumulative_seconds": 0.004706667
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 173,
+ "function": "_rows",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.4541e-05,
+ "cumulative_seconds": 0.004698583
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 125,
+ "function": "_observer_status",
+ "primitive_calls": 8,
+ "calls": 8,
+ "self_seconds": 3.8249e-05,
+ "cumulative_seconds": 0.0037859160000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1429,
+ "function": "_read_row_from_packet",
+ "primitive_calls": 432,
+ "calls": 432,
+ "self_seconds": 0.0009958130000000001,
+ "cumulative_seconds": 0.0034007150000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1448,
+ "function": "_get_descriptions",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000369963,
+ "cumulative_seconds": 0.003054204
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 115,
+ "function": "_triggers",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.416e-06,
+ "cumulative_seconds": 0.002936333
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 163,
+ "function": "read_length_coded_string",
+ "primitive_calls": 2720,
+ "calls": 2720,
+ "self_seconds": 0.0008828200000000001,
+ "cumulative_seconds": 0.002681516
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 107,
+ "function": "_ddl",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 6.2131e-05,
+ "cumulative_seconds": 0.002629792
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 898,
+ "function": "",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 5.5992e-05,
+ "cumulative_seconds": 0.0022069570000000003
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 185,
+ "function": "_metadata",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.0167e-05,
+ "cumulative_seconds": 0.0021745830000000003
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 195,
+ "function": "_chips",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 4.074e-05,
+ "cumulative_seconds": 0.0020089540000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 183,
+ "function": "dumps",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 3.0506000000000002e-05,
+ "cumulative_seconds": 0.0018869190000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 899,
+ "function": "",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 0.00036520000000000004,
+ "cumulative_seconds": 0.0018852920000000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 101,
+ "function": "selected",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.0709e-05,
+ "cumulative_seconds": 0.0018731660000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 188,
+ "function": "_validated_tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 6.663e-05,
+ "cumulative_seconds": 0.0018647120000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 183,
+ "function": "encode",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 3.1916000000000004e-05,
+ "cumulative_seconds": 0.0018468310000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 205,
+ "function": "iterencode",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 0.001810416,
+ "cumulative_seconds": 0.001810416
+ },
+ {
+ "file": "python/workspace_curation.py",
+ "line": 170,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.542e-06,
+ "cumulative_seconds": 0.0017622920000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 232,
+ "function": "__init__",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 9.110700000000001e-05,
+ "cumulative_seconds": 0.001666993
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 748,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.3248000000000002e-05,
+ "cumulative_seconds": 0.0016547500000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 37540,
+ "calls": 37540,
+ "self_seconds": 0.0016338400000000001,
+ "cumulative_seconds": 0.0016338400000000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 778,
+ "function": "_epoch_page_identities",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.6097e-05,
+ "cumulative_seconds": 0.0016087500000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 166,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.291e-06,
+ "cumulative_seconds": 0.0015685
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 236,
+ "function": "_parse_field_descriptor",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 0.00036829900000000005,
+ "cumulative_seconds": 0.0015537180000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 348,
+ "function": "token",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 1.2165000000000001e-05,
+ "cumulative_seconds": 0.001510124
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 360,
+ "function": "_fresh_token",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.9041000000000003e-05,
+ "cumulative_seconds": 0.001481791
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 312,
+ "function": "_scope",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 1.4876000000000001e-05,
+ "cumulative_seconds": 0.0014350410000000002
+ },
+ {
+ "file": "python/workspace_explorer.py",
+ "line": 60,
+ "function": "protocol_binding_header",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 1.2042000000000001e-05,
+ "cumulative_seconds": 0.0013835000000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 282,
+ "function": "__and__",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 1.1124e-05,
+ "cumulative_seconds": 0.001319457
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 178,
+ "function": "restrict",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 9.629500000000001e-05,
+ "cumulative_seconds": 0.001308333
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 34,
+ "function": "text",
+ "primitive_calls": 416,
+ "calls": 416,
+ "self_seconds": 0.000440176,
+ "cumulative_seconds": 0.0012498440000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 223,
+ "function": "attributes",
+ "primitive_calls": 1006,
+ "calls": 1006,
+ "self_seconds": 0.000390121,
+ "cumulative_seconds": 0.001217905
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 40,
+ "function": "tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 7.2111e-05,
+ "cumulative_seconds": 0.0011247100000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 869,
+ "function": "_execute_command",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00023370200000000002,
+ "cumulative_seconds": 0.0011203720000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 61,
+ "function": "read",
+ "primitive_calls": 2720,
+ "calls": 2720,
+ "self_seconds": 0.000959097,
+ "cumulative_seconds": 0.0010828930000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 299,
+ "function": "loads",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 5.9896000000000004e-05,
+ "cumulative_seconds": 0.001076963
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 22,
+ "calls": 78,
+ "self_seconds": 5.8478000000000005e-05,
+ "cumulative_seconds": 0.001068251
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 236,
+ "function": "source_scope",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.3665000000000004e-05,
+ "cumulative_seconds": 0.00104725
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 42,
+ "function": "",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 5.6657e-05,
+ "cumulative_seconds": 0.001038922
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 285,
+ "function": "__getitem__",
+ "primitive_calls": 745,
+ "calls": 745,
+ "self_seconds": 0.00014253,
+ "cumulative_seconds": 0.001019633
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/decoder.py",
+ "line": 332,
+ "function": "decode",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 8.9196e-05,
+ "cumulative_seconds": 0.000981916
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 20373,
+ "calls": 20373,
+ "self_seconds": 0.0009458390000000001,
+ "cumulative_seconds": 0.0009793430000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/condition.py",
+ "line": 278,
+ "function": "make_condition",
+ "primitive_calls": 14,
+ "calls": 56,
+ "self_seconds": 0.00027003600000000003,
+ "cumulative_seconds": 0.000960207
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 261,
+ "function": "filter_epoch_ids",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.955600000000001e-05,
+ "cumulative_seconds": 0.0009523330000000001
+ },
+ {
+ "file": "python/workspace_datastores.py",
+ "line": 192,
+ "function": "source_states",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 8.836e-06,
+ "cumulative_seconds": 0.000948293
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 456,
+ "calls": 456,
+ "self_seconds": 0.000248531,
+ "cumulative_seconds": 0.0008810030000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/decoder.py",
+ "line": 343,
+ "function": "raw_decode",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 0.0008460410000000001,
+ "cumulative_seconds": 0.0008460410000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/wrappers/response.py",
+ "line": 601,
+ "function": "get_json",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.126e-06,
+ "cumulative_seconds": 0.0008362920000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 839,
+ "function": "_write_bytes",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 4.7626e-05,
+ "cumulative_seconds": 0.0008345830000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 181,
+ "function": "loads",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.041e-06,
+ "cumulative_seconds": 0.0008183330000000001
+ }
+ ],
+ "native_sql_calls": [
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0007742500165477395
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0005213330150581896
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00025929202092811465
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004537919885478914
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00023424997925758362
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00036862504202872515
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003762500127777457
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014937500236555934
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00027541699819266796
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00023554201470687985
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005527909961529076
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.00138337496900931
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004224579897709191
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00020062498515471816
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004006249946542084
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00022650003666058183
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00041020900243893266
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003804579610005021
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00013950001448392868
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00024399999529123306
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00038479198701679707
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0003737080260179937
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0003434589598327875
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.0003375419764779508
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0003387089818716049
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00028745800955221057
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "seconds": 0.0004369160160422325
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`epoch_uuid`,`included`,`tags`,`review_state`,`revision`,`metadata_fingerprint` FROM `recording_workspace`.`curation` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))AND( ( (`epoch_uuid`='?') OR (",
+ "seconds": 0.0007845409563742578
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`dataset_uuid`,`created_at`,`artifact_sha256` FROM `recording_workspace`.`dataset_revision` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00028974999440833926
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.001720083993859589
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.000624708947725594
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00045970798237249255
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0001912500010803342
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.0002727499813772738
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0003328750026412308
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0006773329805582762
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0005508330068551004
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.0002952920040115714
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004585420247167349
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00024391704937443137
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00041437498293817043
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0005044999998062849
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014483300037682056
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0002448330051265657
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00019770802464336157
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005259579629637301
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0015403329744003713
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00045495800441130996
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00028795795515179634
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005116669926792383
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00045954197412356734
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004956669872626662
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00037408299976959825
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00017891702009364963
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0003070419770665467
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0006083330372348428
+ }
+ ],
+ "limits": "Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap."
+ },
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": "const,const",
+ "rows": 20,
+ "filtered": 10.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ }
+ ],
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.022153
+ }
+ },
+ "project_uuid": "69b6a7e7-762d-4adf-ad65-b1c3a8eff7f1",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.102921
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.02182
+ }
+ },
+ "project_uuid": "69b6a7e7-762d-4adf-ad65-b1c3a8eff7f1",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.175279
+ },
+ "seconds": 0.27823945798445493,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "workflow_peak_rss_before_durable_oracle_bytes": 173129728,
+ "durable_snapshot_verify_seconds": 0.03394283400848508,
+ "loaded_product_modules": {
+ "workspace_tree": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tree.py",
+ "workspace_predicates": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_predicates.py",
+ "workspace_cache_lifecycle": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_cache_lifecycle.py",
+ "workspace_metadata_objects": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_metadata_objects.py",
+ "workspace_disk_index": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_disk_index.py",
+ "workspace_catalog_identity": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_catalog_identity.py",
+ "recording_workspace": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/recording_workspace.py",
+ "workspace_recipes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_recipes.py",
+ "workspace_tree_code": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tree_code.py",
+ "workspace_service": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_service.py",
+ "workspace_tree_pages": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tree_pages.py",
+ "workspace_projects": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_projects.py",
+ "workspace_mysql_profile": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_mysql_profile.py",
+ "workspace_native_mysql": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_native_mysql.py",
+ "workspace_mysql_runtime": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_mysql_runtime.py",
+ "workspace_storage": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_storage.py",
+ "workspace_diff": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_diff.py",
+ "workspace_protocol_identity": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_protocol_identity.py",
+ "workspace_import_check": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_import_check.py",
+ "workspace_audit": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_audit.py",
+ "workspace_api": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_api.py",
+ "workspace_state_snapshot": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_state_snapshot.py",
+ "workspace_author_preferences": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_author_preferences.py",
+ "workspace_curation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_curation.py",
+ "workspace_annotations": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_annotations.py",
+ "workspace_shared_tag_index": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_shared_tag_index.py",
+ "workspace_paths": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_paths.py",
+ "workspace_desktop_paths": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_desktop_paths.py",
+ "workspace_startup_registry": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_startup_registry.py",
+ "workspace_explorer": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_explorer.py",
+ "workspace_datastores": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_datastores.py",
+ "workspace_suggestions": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_suggestions.py",
+ "workspace_tag_predicates": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tag_predicates.py",
+ "workspace_export_folder": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_export_folder.py",
+ "workspace_import_progress": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_import_progress.py",
+ "workspace_project_preferences": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_project_preferences.py",
+ "workspace_protocol_state": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_protocol_state.py",
+ "workspace_state_generation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_state_generation.py",
+ "workspace_project_servers": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_project_servers.py",
+ "workspace_launcher": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_launcher.py",
+ "workspace_app_routes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_app_routes.py",
+ "workspace_folder_browser": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_folder_browser.py",
+ "workspace_h5_inbox": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_h5_inbox.py",
+ "workspace_tag_exchange": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_tag_exchange.py",
+ "workspace_external_tags": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_external_tags.py",
+ "workspace_qc": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_qc.py",
+ "workspace_search": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_search.py",
+ "workspace_matlab_masks": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_matlab_masks.py",
+ "workspace_matlab_routes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_matlab_routes.py",
+ "workspace_mask_refresh": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_mask_refresh.py",
+ "workspace_matching_epochs": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_matching_epochs.py",
+ "workspace_candidate_exports": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_candidate_exports.py",
+ "workspace_backup_scheduler": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_backup_scheduler.py",
+ "workspace_recovery_store": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_recovery_store.py",
+ "workspace_recovery_generation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_recovery_generation.py",
+ "workspace_annotation_preparation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_annotation_preparation.py",
+ "workspace_native_tag_lookup": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_native_tag_lookup.py",
+ "workspace_lifecycle": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_lifecycle.py",
+ "workspace_native_tag_filter": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq/python/workspace_native_tag_filter.py"
+ },
+ "owned_native_runtime_stopped": true,
+ "source_inventory_after": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "d81d9c5e03e62024c30474612596ee3fec2753a745e39c2db005010c85b8ffff",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_concurrent_requests.py": "f074a762536ab5bb52abe4ea56faada41babb1cd765512acb3caba18dd503648",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "b70fe1d82baa6ab40901d17e49ef8946297379a6d1adc3929a9cb979ae947e5a",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_empty_blocks.py": "ff0973074a72cf110b90dd8f76f032d5016315bbb21f84569e9fa701b11a7d88",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_event_paging.py": "e79cd1de3bd7469818e255cc01f087a608c11e799a48e81e4ca0ac29ff7a578e",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "3cf4ab9a1b5db849600c2716e9026cfb3d0817389f240d5d9cee9dbfe0b7922d",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "f852f8af8b9795c63772800c3ca3cb7203fa0a1b81e0ef562ef848c1e31a06b0",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7fe035916c152aad2df6f41cc6f5c55e8d30cb4168f53c4ab1aa9e171bdd7cfc",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "a7bc5ddb5f153aacb23c13eb11b1ca14515ddcd12526567f41e49252931ddf96",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6d89f8a735c34e6ec3f7f3db291e5d21c1bd8e64a60e7b12563e82dc18eb9f24",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "8efc55e5fb6079072014c616223872464c0451c6d85bfcb2c61693f9baaed1ff",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "9bf32c59a9847f5b7231987348fd54ad1f6eec4773323b125ae34d1239632fbb",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "8cb336a98cf2f487a3bf30db1ddc6ad5db1081018cee394d2690dffa04f8c1bd",
+ "python/workspace_native_tag_lookup.py": "ae8d7fd3b2f80d56078777b03de4189b648f536ead1ca1225ad6c72a7c59a466",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "40ba54acf2c5e9983c2e2766179a3d365b30ad1cdc4955410571936f7dfb4954",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "68e142d6008248c67310462b692b130f0951a87a802187f59d0e6302a78073fc",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "source_unchanged": true,
+ "harness_unchanged": true,
+ "seconds": 11.342676208994817,
+ "finished_at": "2026-09-30T15:39:05.151038+00:00",
+ "peak_rss_bytes": 173129728,
+ "passed": true
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-100000.json b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-100000.json
new file mode 100644
index 0000000..6236c41
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-100000.json
@@ -0,0 +1,3307 @@
+{
+ "scope": "Persistent native tag lookup: tag-ten, tag-another-ten, filter-twenty and cell/epoch hierarchy.\n\nEvery project/database is disposable. Acquisition metadata is synthetic; actual\nMySQL, triggers, Flask routes, derived indexes and each version's save hooks\nare used. Native SQL commits are checked immediately; any background recovery\nmirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude\noracle work, and do not model browser concurrency/rendering. Sequential bundle\ntimes are sums of their separately measured requests. Profiles are separate.\n",
+ "epochs": 100000,
+ "profiles": 3,
+ "tags_per_annotation": 5,
+ "protocols": 2,
+ "samples": 5,
+ "source_root": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI",
+ "source_inventory_before": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "17c37104693d46b416064ab05027c54added8d26ddd2ba6b7de202911ce5d592",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "98e5ddf9f5ea388f8697f2c5ea4e176aaf860450e78e2993c0f576ac164e11b5",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "4c586415a445d806ffe8d64d74f4d53d6d453be6fdcb2b4812da49f69a4525a2",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7afc6c297770800fb90e148ffeb4ad3d5c24c8e84f5a788094f3246f59f97782",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6286df8eb8ea15f0c2c558a9a42c291167277cb5654ec4dd132ad739e96733b1",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "6587dcb57e94489175dad26bbbfaa9142170f7eb1b947fe7b3becf40707e11cf",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "66a6a04c107053b3c0492e5ccd2e6f6e10b8479c107c0b58d5b5fd712800312c",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "9f198eb78c0ccc44dfdd4aebd44c0dd890482d6020b0164c5acce4955e46f5e6",
+ "python/workspace_native_tag_lookup.py": "d549e8084af074ab484759a2851fdacde7e632c334f55e2d65caee368e04ffd5",
+ "python/workspace_paths.py": "a75c94ce4c2a5863fb2bf5d79c6abaf4304fb7aa90726d89d80a9e69114d5710",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "3f5ec51667618b9e0a215f6371dd22a9e1502afd54166da0efb9b677045d4216",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "35a10441d0ba59e43c59ad564879b2b73eccc29cd19a2558e3eaf8db1a9f5782",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "1917ae5b36553048027a19e1b68f99677a754dae1501d1e52be1cd3faaa2b4a5",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "fixture_module": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py",
+ "harness_sha256": "a2b5e70861826936fdb4c9d6a5c76efd7645486ad5ce05e1482e3e6aab694fde",
+ "started_at": "2026-09-30T15:02:54.636220+00:00",
+ "release_ready": false,
+ "operations": {
+ "first10_revision_preflight": {
+ "samples_seconds": [
+ 0.011627708037849516,
+ 0.09122683299938217,
+ 0.007156417006626725,
+ 0.040542583039496094,
+ 0.006898041989188641
+ ],
+ "p50_seconds": 0.011627708037849516,
+ "p95_seconds": 0.09122683299938217,
+ "p99_seconds": 0.09122683299938217,
+ "maximum_seconds": 0.09122683299938217,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 245961,
+ "Questions": 130
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ }
+ ]
+ },
+ "first10_save": {
+ "samples_seconds": [
+ 0.04437404201598838,
+ 0.020467541995458305,
+ 0.02093420800520107,
+ 0.020763959037140012,
+ 0.018009499995969236
+ ],
+ "p50_seconds": 0.020763959037140012,
+ "p95_seconds": 0.04437404201598838,
+ "p99_seconds": 0.04437404201598838,
+ "maximum_seconds": 0.04437404201598838,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 18600,
+ "Questions": 36
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12424,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ }
+ ]
+ },
+ "second10_revision_preflight": {
+ "samples_seconds": [
+ 0.006863999995402992,
+ 0.005781583022326231,
+ 0.006198541959747672,
+ 0.006267749995458871,
+ 0.026554333977401257
+ ],
+ "p50_seconds": 0.006267749995458871,
+ "p95_seconds": 0.026554333977401257,
+ "p99_seconds": 0.026554333977401257,
+ "maximum_seconds": 0.026554333977401257,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ }
+ ]
+ },
+ "second10_save": {
+ "samples_seconds": [
+ 0.027478916046675295,
+ 0.01964816596591845,
+ 0.019734667032025754,
+ 0.025464750011451542,
+ 0.02433695795480162
+ ],
+ "p50_seconds": 0.02433695795480162,
+ "p95_seconds": 0.027478916046675295,
+ "p99_seconds": 0.027478916046675295,
+ "maximum_seconds": 0.027478916046675295,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12436,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter20": {
+ "samples_seconds": [
+ 0.9843111250083894,
+ 0.11473174998536706,
+ 0.13281304098200053,
+ 0.12448295799549669,
+ 0.1226571670267731
+ ],
+ "p50_seconds": 0.12448295799549669,
+ "p95_seconds": 0.9843111250083894,
+ "p99_seconds": 0.9843111250083894,
+ "maximum_seconds": 0.9843111250083894,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 156852,
+ "Questions": 83
+ },
+ {
+ "Bytes_sent": 134745,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134745,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 396264,
+ "Questions": 183
+ },
+ {
+ "Bytes_sent": 134745,
+ "Questions": 57
+ }
+ ]
+ },
+ "cell_save": {
+ "samples_seconds": [
+ 0.008944916015025228,
+ 0.008186708961147815,
+ 0.008591292018536478,
+ 0.007889083004556596,
+ 0.008863541996106505
+ ],
+ "p50_seconds": 0.008591292018536478,
+ "p95_seconds": 0.008944916015025228,
+ "p99_seconds": 0.008944916015025228,
+ "maximum_seconds": 0.008944916015025228,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4166,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_cell100": {
+ "samples_seconds": [
+ 0.15682324999943376,
+ 0.1472697500139475,
+ 0.15188124997075647,
+ 0.1603592080064118,
+ 0.16214675002265722
+ ],
+ "p50_seconds": 0.15682324999943376,
+ "p95_seconds": 0.16214675002265722,
+ "p99_seconds": 0.16214675002265722,
+ "maximum_seconds": 0.16214675002265722,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ }
+ ]
+ },
+ "filter_cell_and_epoch10": {
+ "samples_seconds": [
+ 0.11638466699514538,
+ 0.13077333301771432,
+ 0.1285252090310678,
+ 0.12458279100246727,
+ 0.11700779100647196
+ ],
+ "p50_seconds": 0.12458279100246727,
+ "p95_seconds": 0.13077333301771432,
+ "p99_seconds": 0.13077333301771432,
+ "maximum_seconds": 0.13077333301771432,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ }
+ ]
+ },
+ "remove_first10_revision_preflight": {
+ "samples_seconds": [
+ 0.006347040995024145,
+ 0.006440584023948759,
+ 0.0074722919962368906,
+ 0.006093290983699262,
+ 0.007031665998511016
+ ],
+ "p50_seconds": 0.006440584023948759,
+ "p95_seconds": 0.0074722919962368906,
+ "p99_seconds": 0.0074722919962368906,
+ "maximum_seconds": 0.0074722919962368906,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_first10_save": {
+ "samples_seconds": [
+ 0.019374625000637025,
+ 0.02045441698282957,
+ 0.021038750011939555,
+ 0.019824667018838227,
+ 0.017425708007067442
+ ],
+ "p50_seconds": 0.019824667018838227,
+ "p95_seconds": 0.021038750011939555,
+ "p99_seconds": 0.021038750011939555,
+ "maximum_seconds": 0.021038750011939555,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12424,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_hierarchy_after_epoch_remove": {
+ "samples_seconds": [
+ 0.10092024999903515,
+ 0.1075972500257194,
+ 0.11051483399933204,
+ 0.12653983401833102,
+ 0.10249387501971796
+ ],
+ "p50_seconds": 0.1075972500257194,
+ "p95_seconds": 0.12653983401833102,
+ "p99_seconds": 0.12653983401833102,
+ "maximum_seconds": 0.12653983401833102,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ }
+ ]
+ },
+ "filter_remaining10": {
+ "samples_seconds": [
+ 0.11686000000918284,
+ 0.11886008305009454,
+ 0.12619370798347518,
+ 0.11449816601816565,
+ 0.1236068339785561
+ ],
+ "p50_seconds": 0.11886008305009454,
+ "p95_seconds": 0.12619370798347518,
+ "p99_seconds": 0.12619370798347518,
+ "maximum_seconds": 0.12619370798347518,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ }
+ ]
+ },
+ "remove_second10_revision_preflight": {
+ "samples_seconds": [
+ 0.009842916973866522,
+ 0.010070999967865646,
+ 0.009961250005289912,
+ 0.010147833032533526,
+ 0.006309708987828344
+ ],
+ "p50_seconds": 0.009961250005289912,
+ "p95_seconds": 0.010147833032533526,
+ "p99_seconds": 0.010147833032533526,
+ "maximum_seconds": 0.010147833032533526,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_second10_save": {
+ "samples_seconds": [
+ 0.01778425002703443,
+ 0.01926620799349621,
+ 0.02287991699995473,
+ 0.018311958003323525,
+ 0.01896583300549537
+ ],
+ "p50_seconds": 0.01896583300549537,
+ "p95_seconds": 0.02287991699995473,
+ "p99_seconds": 0.02287991699995473,
+ "maximum_seconds": 0.02287991699995473,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12436,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_after_all_epoch_removals": {
+ "samples_seconds": [
+ 0.10538366599939764,
+ 0.10673487500753254,
+ 0.1089959159726277,
+ 0.10726999997859821,
+ 0.10814420797396451
+ ],
+ "p50_seconds": 0.10726999997859821,
+ "p95_seconds": 0.1089959159726277,
+ "p99_seconds": 0.1089959159726277,
+ "maximum_seconds": 0.1089959159726277,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ }
+ ]
+ },
+ "remove_cell_save": {
+ "samples_seconds": [
+ 0.008432624978013337,
+ 0.007472749974112958,
+ 0.008027833013329655,
+ 0.0073897080146707594,
+ 0.00871045800158754
+ ],
+ "p50_seconds": 0.008027833013329655,
+ "p95_seconds": 0.00871045800158754,
+ "p99_seconds": 0.00871045800158754,
+ "maximum_seconds": 0.00871045800158754,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4166,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_after_cell_removal": {
+ "samples_seconds": [
+ 0.10941074998117983,
+ 0.10573266696883366,
+ 0.10774483397835866,
+ 0.1067830000538379,
+ 0.11562345898710191
+ ],
+ "p50_seconds": 0.10774483397835866,
+ "p95_seconds": 0.11562345898710191,
+ "p99_seconds": 0.11562345898710191,
+ "maximum_seconds": 0.11562345898710191,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ }
+ ]
+ },
+ "tag10_then10_filter20_http_total": {
+ "samples_seconds": [
+ 1.0746557911043055,
+ 0.2518558739684522,
+ 0.18683687498560175,
+ 0.2175220000790432,
+ 0.19845600094413385
+ ],
+ "p50_seconds": 0.2175220000790432,
+ "p95_seconds": 1.0746557911043055,
+ "p99_seconds": 1.0746557911043055,
+ "maximum_seconds": 1.0746557911043055,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "samples_seconds": [
+ 0.16576816601445898,
+ 0.1554564589750953,
+ 0.16047254198929295,
+ 0.1682482910109684,
+ 0.17101029201876372
+ ],
+ "p50_seconds": 0.16576816601445898,
+ "p95_seconds": 0.17101029201876372,
+ "p99_seconds": 0.17101029201876372,
+ "maximum_seconds": 0.17101029201876372,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "first10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.056001750053837895,
+ 0.11169437499484047,
+ 0.028090625011827797,
+ 0.061306542076636106,
+ 0.024907541985157877
+ ],
+ "p50_seconds": 0.056001750053837895,
+ "p95_seconds": 0.11169437499484047,
+ "p99_seconds": 0.11169437499484047,
+ "maximum_seconds": 0.11169437499484047,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "second10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.034342916042078286,
+ 0.025429748988244683,
+ 0.025933208991773427,
+ 0.031732500006910414,
+ 0.050891291932202876
+ ],
+ "p50_seconds": 0.031732500006910414,
+ "p95_seconds": 0.050891291932202876,
+ "p99_seconds": 0.050891291932202876,
+ "maximum_seconds": 0.050891291932202876,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "autocomplete_cold_diagnostic": {
+ "samples_seconds": [
+ 10.202606874983758
+ ],
+ "p50_seconds": 10.202606874983758,
+ "p95_seconds": 10.202606874983758,
+ "p99_seconds": 10.202606874983758,
+ "maximum_seconds": 10.202606874983758,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 214986,
+ "Questions": 89
+ }
+ ]
+ },
+ "autocomplete_warm_diagnostic": {
+ "samples_seconds": [
+ 9.474194333015475
+ ],
+ "p50_seconds": 9.474194333015475,
+ "p95_seconds": 9.474194333015475,
+ "p99_seconds": 9.474194333015475,
+ "maximum_seconds": 9.474194333015475,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 214986,
+ "Questions": 89
+ }
+ ]
+ },
+ "same_server_native_reuse_filter20": {
+ "samples_seconds": [
+ 0.1438881250214763
+ ],
+ "p50_seconds": 0.1438881250214763,
+ "p95_seconds": 0.1438881250214763,
+ "p99_seconds": 0.1438881250214763,
+ "maximum_seconds": 0.1438881250214763,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 134746,
+ "Questions": 57
+ }
+ ]
+ }
+ },
+ "checks": [
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 33334
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 66666
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 66667
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "page_cells": true,
+ "target_specification": "tag10_then10_filter20_and_cell_epoch_hierarchy",
+ "baseline_harness_sha256": "272fe050c8762ed4ea45bd013e236e8723d8fe46f5e7e293c58e2edf1fd52ddc",
+ "mysql_version": "8.4.2",
+ "seed_seconds": 115.08456225000555,
+ "seed_checkpoint_seconds": 14.813979834027123,
+ "native_lookup_startup_preparation": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "2aa92db9-6472-4c39-9292-5794937593db",
+ "reused": false,
+ "phase": "complete",
+ "seconds": 0.173335
+ },
+ "selected_epoch_uuids": {
+ "first10": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5"
+ ],
+ "second10": [
+ "ad573668-6512-6e55-649e-cb23ae1d4888",
+ "16dc368a-89b4-28b2-4854-84313ba67a39",
+ "37834f2f-2576-2f23-e1f7-4a531cbe445d",
+ "454f63ac-30c8-3229-97ef-025edff6abd2",
+ "5ef6fdf3-2513-aa7c-d11f-72beccf132b9",
+ "1253e937-3e78-1b75-0026-6caa55150e08",
+ "482d9673-cfee-5de3-91f9-7fde4d1c84f9",
+ "3346f2bb-f6c3-4bd2-dbe2-8bd1bb657d0e",
+ "9537f32e-c759-9e1a-e953-af6c9f929fe7",
+ "0fd42b3f-73c4-48b3-4940-b339f87d07ad"
+ ],
+ "cell": "3bb78535-cc95-55ff-19fe-3556aaa41c78",
+ "cell_children": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5",
+ "4a44dc15-3642-04a8-0fe8-0e9039455cc1",
+ "4fc82b26-aecb-47d2-868c-4efbe3581732",
+ "6b51d431-df5d-7f14-1cbe-ceccf79edf3d",
+ "3fdba35f-04dc-8c46-2986-c992bcf87554",
+ "8527a891-e224-1369-50ff-32ca212b45bc",
+ "e629fa65-98d7-3276-8f7c-726b4b621285",
+ "b17ef6d1-9c7a-5b1e-e83b-907c595526dc",
+ "4523540f-1504-cd17-100c-4835e85b7eef",
+ "4ec9599f-c203-d176-a301-536c2e091a19",
+ "9400f1b2-1cb5-27d7-fa3d-3eabba93557a",
+ "f5ca38f7-48a1-d6ea-f726-b8a42fb575c3",
+ "6f4b6612-125f-b3a0-daec-d2799dfd6c9c",
+ "785f3ec7-eb32-f30b-90cd-0fcf3657d388",
+ "535fa30d-7e25-dd8a-49f1-536779734ec8",
+ "c2356069-e9d1-e79c-a924-378153cfbbfb",
+ "b7a56873-cd77-1f2c-446d-369b649430b6",
+ "5f9c4ab0-8cac-7457-e911-1a30e4664920",
+ "670671cd-9740-4156-226e-507973f2ab83",
+ "59e19706-d51d-39f6-6711-c2653cd7eb12",
+ "35135aaa-6cc2-3891-b40c-b3f378c53a17",
+ "624b60c5-8c9d-8bfb-6ff1-886c2fd605d2",
+ "eb1e33e8-a81b-697b-7585-5af6bfcdbcbf",
+ "e29c9c18-0c62-79b0-b02a-bd6a1801c7c0",
+ "c6f3ac57-944a-5314-90cd-39902d0f7777",
+ "86e50149-6586-6131-2a9e-0b35558d84f6",
+ "9f14025a-f006-5b30-e47e-23ebb3b491d3",
+ "76a50887-d8f1-c2e9-3017-55428990ad81",
+ "7a61b537-01be-fdae-0eee-ffaecc73f14e",
+ "aea92132-c4cb-eb26-3e6a-c2bf6c183b5d",
+ "0b918943-df09-62bc-7a18-24c0555a3893",
+ "d59eced1-ded0-7f84-c145-592f65bdf854",
+ "3d914f93-48c9-cc0f-f8a7-9716700b9fcd",
+ "73475cb4-0a56-8e8d-a8a0-45ced110137e",
+ "44cb730c-4204-80a0-477b-505ae68af508",
+ "71ee45a3-c0db-9a98-65f7-313dd3372cf6",
+ "811786ad-1ae7-4adf-dd20-dd0372abaaeb",
+ "25fc0e70-96fc-6537-1820-2dc30b0c580b",
+ "31489056-e091-6d59-fe3a-dd79e63f095a",
+ "98010bd9-270f-9b10-0b62-14a21754fd33",
+ "0e17daca-5f3e-175f-448b-acace3bc0da4",
+ "1a656259-0ef1-9d10-45d0-6c4055742d38",
+ "031b4af5-197e-c30a-926f-48cf40e11a7d",
+ "41cfc0d1-f2d1-27b0-4555-b7246d84019b",
+ "2858dcd1-057d-3eae-7f7d-5f782167e24b",
+ "2fca346d-b656-1871-02ce-806ac732e06a",
+ "02d20bbd-7e39-4ad5-999a-4cebabac9619",
+ "7688b6ef-5255-5962-d008-fff894223582",
+ "c837649c-ce43-f272-9138-e72cc3152070",
+ "6208ef0f-7750-c111-548c-f90b6ea1d0d0",
+ "3e1e967e-9b79-3e90-8f8e-ae83c74dba9b",
+ "39fa9ec1-90ee-e7b6-f4df-f1100d6343e1",
+ "d029fa3a-95e1-74a1-9934-857f535eb942",
+ "81b8a03f-97e8-787c-53fe-1a86bda042b6",
+ "da4ea2a5-506f-2693-eae1-90d9360a1f31",
+ "a68b412c-4282-555f-1554-6cf6e1fc4289",
+ "108c995b-953c-8a35-5611-03e2014cf828",
+ "3ada92f2-8b4c-eda3-8562-ebf047c6ff05",
+ "49d180ec-f561-3281-9571-bf39d9b7b342",
+ "a21855da-08cb-102d-1d21-7c53dc5824a3",
+ "c75cb66a-e28d-8ebc-6ede-d002c28a8ba0",
+ "ff5a1ae0-12af-a5d4-c889-c50ad427aaf5",
+ "7f2253d7-e228-b22a-08bd-a1f09c516f6f",
+ "87226162-0421-7edd-b39e-7df969e0698a",
+ "96061e92-f58e-4bdc-dee7-3df36183fe3a",
+ "eb624dbe-56eb-6620-ae62-080c10a273ca",
+ "f369cb89-fc62-7e66-8987-007d121ed1ea",
+ "f74efabe-f12e-a619-e30b-79bddef89cff",
+ "a88a7902-cb4e-f697-ba0b-6759c50e8c10",
+ "349c4120-1b62-db85-1192-665c504b350f",
+ "98a3ab7c-340e-8a03-3e7b-37b6ef942875",
+ "48449a14-a4ff-7d79-bb7a-1b6f3d488eba",
+ "5316ca1c-5ddc-a8e6-cecc-fce58f3b8540",
+ "a46e3763-2fa6-ca51-a13f-e39a567b3c23",
+ "bbb965ab-0c80-d653-8cf2-184babad2a56",
+ "44c8031c-b036-a735-0d8b-9b8603af662a",
+ "b4944c6f-f08d-c6f4-3da2-e9c824669b7d",
+ "434c9b5a-e514-646b-bd91-b50032ca579e",
+ "bdd2d3af-3a5a-1213-497d-4f1f7bfcda89",
+ "8b940be7-fb78-aaa6-b656-7dd7a3987996",
+ "cd70bea0-23f7-52a0-564a-bb6ed08d42c1",
+ "69f59c27-3b6e-669a-c32a-6dd5e1b2cb63",
+ "1da51b8d-8ff9-8f6a-48f8-0ae79fe3ca6c",
+ "82416496-09f8-8ccd-2a0a-5b233a07a538",
+ "6e400187-1c0c-f27c-7634-ef1dc478408f",
+ "e3d6c4d4-599e-0088-2384-ca981ee287ed",
+ "ad48ff99-415b-2f00-7dc3-5b7eb553fd1e",
+ "7b1a278f-5abe-8e9d-a907-fc9c29dfd432",
+ "d6d824ab-ba4a-fde8-1129-c71dea75b810",
+ "29db0c67-82db-d500-0559-ef4d9e953e30",
+ "8c1f1046-219d-dd21-6a02-3f792356ddf1"
+ ]
+ },
+ "backup_receipts": [
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 1,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 2,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 2,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:40.003731+00:00",
+ "pending": true,
+ "requested_sequence": 3,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 2,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:40.003731+00:00",
+ "pending": true,
+ "requested_sequence": 4,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 2,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:40.003731+00:00",
+ "pending": true,
+ "requested_sequence": 5,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 2,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:40.003731+00:00",
+ "pending": true,
+ "requested_sequence": 6,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 7,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 8,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 9,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 10,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 11,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 12,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 13,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 14,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 15,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 16,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 17,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 18,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 19,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 6,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:42.758327+00:00",
+ "pending": true,
+ "requested_sequence": 20,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 21,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 22,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 23,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 24,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 25,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 26,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 27,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 28,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 29,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 20,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:47.971642+00:00",
+ "pending": true,
+ "requested_sequence": 30,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:52.972148+00:00",
+ "pending": true,
+ "requested_sequence": 31,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:05:52.972148+00:00",
+ "pending": true,
+ "requested_sequence": 32,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_first10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:06:13.433464+00:00",
+ "pending": true,
+ "requested_sequence": 33,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_second10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:06:13.433464+00:00",
+ "pending": true,
+ "requested_sequence": 34,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ }
+ ],
+ "preparation_index_seconds": 0,
+ "lookup_mode": "persistent_native_sql_no_post_seed_tag_cache_preparation",
+ "bundle_definitions": {
+ "tag10_then10_filter20_http_total": [
+ "first10_revision_preflight",
+ "first10_save",
+ "second10_revision_preflight",
+ "second10_save",
+ "filter20"
+ ],
+ "cell_tag_then_cell_filter_http_total": [
+ "cell_save",
+ "filter_cell100"
+ ],
+ "first10_selected_action_http_total": [
+ "first10_revision_preflight",
+ "first10_save"
+ ],
+ "second10_selected_action_http_total": [
+ "second10_revision_preflight",
+ "second10_save"
+ ]
+ },
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:06:18.387002+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "seconds": 5.976898541965056
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "seconds": 4.200603792036418
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "seconds": 5.332792375003919
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "seconds": 4.116656041005626
+ }
+ ]
+ },
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY,by_tag",
+ "key": "PRIMARY",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 613093,
+ "filtered": 100.0,
+ "Extra": "Using index; Using filesort"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "PRIMARY",
+ "table": "",
+ "partitions": null,
+ "type": "ALL",
+ "possible_keys": null,
+ "key": null,
+ "key_len": null,
+ "ref": null,
+ "rows": 183927,
+ "filtered": 100.0,
+ "Extra": null
+ },
+ {
+ "id": 1,
+ "select_type": "PRIMARY",
+ "table": "a",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "111",
+ "ref": "const,func",
+ "rows": 1,
+ "filtered": 5.0,
+ "Extra": "Using where"
+ },
+ {
+ "id": 2,
+ "select_type": "DERIVED",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY,by_tag",
+ "key": "PRIMARY",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 613093,
+ "filtered": 30.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ }
+ ],
+ "filter20_cpu_profile": {
+ "top_functions": [
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 313,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.4170000000000006e-06,
+ "cumulative_seconds": 0.374428792
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 159,
+ "function": "request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.958e-06,
+ "cumulative_seconds": 0.374386333
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1163,
+ "function": "get",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.375000000000001e-06,
+ "cumulative_seconds": 0.373476542
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/testing.py",
+ "line": 204,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.4083e-05,
+ "cumulative_seconds": 0.373470167
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1062,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.793e-06,
+ "cumulative_seconds": 0.37316512500000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 984,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0249000000000001e-05,
+ "cumulative_seconds": 0.37314945800000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1230,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.2624000000000001e-05,
+ "cumulative_seconds": 0.3726995
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1529,
+ "function": "__call__",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.334000000000001e-06,
+ "cumulative_seconds": 0.37266475000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1479,
+ "function": "wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.5376e-05,
+ "cumulative_seconds": 0.37265841600000005
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 904,
+ "function": "full_dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.126e-06,
+ "cumulative_seconds": 0.37206475
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 879,
+ "function": "dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00010283200000000001,
+ "cumulative_seconds": 0.37178904100000004
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 716,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.000158837,
+ "cumulative_seconds": 0.371678542
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 27,
+ "calls": 38,
+ "self_seconds": 3.0666e-05,
+ "cumulative_seconds": 0.185190878
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 249,
+ "function": "native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.0250000000000003e-05,
+ "cumulative_seconds": 0.185127292
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 268,
+ "function": "_native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.6667e-05,
+ "cumulative_seconds": 0.16585575000000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 311,
+ "function": "native_context",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 7.0245e-05,
+ "cumulative_seconds": 0.16577804200000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 207,
+ "function": "_metadata_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.017342542000000002,
+ "cumulative_seconds": 0.15863058300000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 22,
+ "calls": 4058,
+ "self_seconds": 0.001319663,
+ "cumulative_seconds": 0.14160387700000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 212,
+ "function": "",
+ "primitive_calls": 18,
+ "calls": 18,
+ "self_seconds": 2.7706000000000002e-05,
+ "cumulative_seconds": 0.141277457
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 748,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.0010381280000000001,
+ "cumulative_seconds": 0.138147084
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 778,
+ "function": "_epoch_page_identities",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.008510140000000001,
+ "cumulative_seconds": 0.137031375
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 55,
+ "function": "_epoch_query_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.102637528,
+ "cumulative_seconds": 0.130549292
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 132,
+ "function": "__enter__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 2.876e-06,
+ "cumulative_seconds": 0.10969495800000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 261,
+ "function": "filter_epoch_ids",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.023571786,
+ "cumulative_seconds": 0.105705084
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 141,
+ "function": "__exit__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 5.416e-06,
+ "cumulative_seconds": 0.07543862500000001
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 8,
+ "function": "matching",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.047434952,
+ "cumulative_seconds": 0.0730085
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 295,
+ "function": "for_epochs",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00010728200000000001,
+ "cumulative_seconds": 0.035757167
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 304,
+ "function": "traced_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000255777,
+ "cumulative_seconds": 0.033613458
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 412,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000147334,
+ "cumulative_seconds": 0.032831003000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 402,
+ "function": "_execute_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000145164,
+ "cumulative_seconds": 0.032476752000000005
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 150,
+ "function": "execute",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.016100000000001e-05,
+ "cumulative_seconds": 0.032024374
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 332,
+ "function": "_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.8291e-05,
+ "cumulative_seconds": 0.031495709000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 575,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 8.983e-05,
+ "cumulative_seconds": 0.030965087000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 535040,
+ "calls": 535040,
+ "self_seconds": 0.029391985000000002,
+ "cumulative_seconds": 0.029391985000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 851,
+ "function": "_read_query_result",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.4422e-05,
+ "cumulative_seconds": 0.029344170000000003
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1288,
+ "function": "read",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 8.8255e-05,
+ "cumulative_seconds": 0.029244248
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 128,
+ "function": "deepcopy",
+ "primitive_calls": 78,
+ "calls": 17170,
+ "self_seconds": 0.019775887000000002,
+ "cumulative_seconds": 0.026320795
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 201,
+ "function": "_deepcopy_list",
+ "primitive_calls": 62,
+ "calls": 102,
+ "self_seconds": 0.000301189,
+ "cumulative_seconds": 0.026185620000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 227,
+ "function": "_deepcopy_dict",
+ "primitive_calls": 1242,
+ "calls": 1284,
+ "self_seconds": 0.002786677,
+ "cumulative_seconds": 0.024226512000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 763,
+ "function": "_read_packet",
+ "primitive_calls": 765,
+ "calls": 765,
+ "self_seconds": 0.001309718,
+ "cumulative_seconds": 0.020606641000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 101,
+ "function": "_rows",
+ "primitive_calls": 46,
+ "calls": 46,
+ "self_seconds": 6.8374e-05,
+ "cumulative_seconds": 0.019305502000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 197,
+ "function": "response_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.5752e-05,
+ "cumulative_seconds": 0.019156126000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 233,
+ "function": "_attest_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 7.8791e-05,
+ "cumulative_seconds": 0.01911475
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 813,
+ "function": "_read_bytes",
+ "primitive_calls": 1530,
+ "calls": 1530,
+ "self_seconds": 0.0005736410000000001,
+ "cumulative_seconds": 0.017317151
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 1530,
+ "calls": 1530,
+ "self_seconds": 0.000242761,
+ "cumulative_seconds": 0.016664344
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/socket.py",
+ "line": 704,
+ "function": "readinto",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 0.00010028000000000001,
+ "cumulative_seconds": 0.016421583
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1299,
+ "function": "recv_into",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 8.8368e-05,
+ "cumulative_seconds": 0.016270255
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 869,
+ "function": "to_dicts",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 2.4788e-05,
+ "cumulative_seconds": 0.016210542
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1157,
+ "function": "read",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 4.7793e-05,
+ "cumulative_seconds": 0.016172626000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 0.016120626000000002,
+ "cumulative_seconds": 0.016120626000000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 34,
+ "function": "_exact_equal",
+ "primitive_calls": 22,
+ "calls": 14072,
+ "self_seconds": 0.0038657690000000002,
+ "cumulative_seconds": 0.014416209000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 1231,
+ "function": "cursor",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 2.4924e-05,
+ "cumulative_seconds": 0.014076628
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 845,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.012475625,
+ "cumulative_seconds": 0.012475625
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1370,
+ "function": "_read_result_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.1754e-05,
+ "cumulative_seconds": 0.012389460000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 42,
+ "function": "",
+ "primitive_calls": 6012,
+ "calls": 16026,
+ "self_seconds": 0.008119805,
+ "cumulative_seconds": 0.011268288000000001
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 134,
+ "function": "_active_ddl_guard",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 5.0499000000000006e-05,
+ "cumulative_seconds": 0.01104571
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 797,
+ "function": "",
+ "primitive_calls": 100001,
+ "calls": 100001,
+ "self_seconds": 0.009490819000000001,
+ "cumulative_seconds": 0.009490819000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1416,
+ "function": "_read_rowdata_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00038105500000000004,
+ "cumulative_seconds": 0.009312415000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 173,
+ "function": "_rows",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.3291e-05,
+ "cumulative_seconds": 0.008443
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/__init__.py",
+ "line": 138,
+ "function": "jsonify",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.332000000000001e-06,
+ "cumulative_seconds": 0.007500791000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 189,
+ "function": "response",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.6874e-05,
+ "cumulative_seconds": 0.0074843340000000005
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 61,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.5209e-05,
+ "cumulative_seconds": 0.0074175840000000005
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 100027,
+ "calls": 100027,
+ "self_seconds": 0.007347353,
+ "cumulative_seconds": 0.007347353
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1429,
+ "function": "_read_row_from_packet",
+ "primitive_calls": 428,
+ "calls": 428,
+ "self_seconds": 0.0009690370000000001,
+ "cumulative_seconds": 0.007011843
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 21,
+ "calls": 21,
+ "self_seconds": 0.0059070400000000006,
+ "cumulative_seconds": 0.0059070400000000006
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 62,
+ "function": "browser_safe",
+ "primitive_calls": 1,
+ "calls": 9641,
+ "self_seconds": 0.0031328690000000004,
+ "cumulative_seconds": 0.0056478330000000005
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 66,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1363,
+ "self_seconds": 0.0014191730000000001,
+ "cumulative_seconds": 0.005645458000000001
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 2,
+ "calls": 102,
+ "self_seconds": 0.00021272200000000001,
+ "cumulative_seconds": 0.005634167000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 185,
+ "function": "_metadata",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 8.4875e-05,
+ "cumulative_seconds": 0.005598292
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 205717,
+ "calls": 205719,
+ "self_seconds": 0.005549391000000001,
+ "cumulative_seconds": 0.005549765000000001
+ },
+ {
+ "file": "python/workspace_explorer.py",
+ "line": 60,
+ "function": "protocol_binding_header",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 2.2667e-05,
+ "cumulative_seconds": 0.004394084
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/converters.py",
+ "line": 166,
+ "function": "convert_datetime",
+ "primitive_calls": 144,
+ "calls": 144,
+ "self_seconds": 0.003984993,
+ "cumulative_seconds": 0.004224045
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 100008,
+ "calls": 100008,
+ "self_seconds": 0.0041956020000000005,
+ "cumulative_seconds": 0.0041956020000000005
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 38,
+ "function": "walk",
+ "primitive_calls": 1,
+ "calls": 2,
+ "self_seconds": 0.001548792,
+ "cumulative_seconds": 0.0037413750000000003
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 1.0292e-05,
+ "cumulative_seconds": 0.003726042
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 44,
+ "function": "",
+ "primitive_calls": 2040,
+ "calls": 2040,
+ "self_seconds": 0.000484992,
+ "cumulative_seconds": 0.003606039
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 125,
+ "function": "_observer_status",
+ "primitive_calls": 8,
+ "calls": 8,
+ "self_seconds": 3.1464e-05,
+ "cumulative_seconds": 0.003434084
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 106471,
+ "calls": 106471,
+ "self_seconds": 0.003273446,
+ "cumulative_seconds": 0.003273446
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 107,
+ "function": "_ddl",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 3.8205000000000004e-05,
+ "cumulative_seconds": 0.003112208
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1448,
+ "function": "_get_descriptions",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00037153500000000004,
+ "cumulative_seconds": 0.002979207
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 115,
+ "function": "_triggers",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.791e-06,
+ "cumulative_seconds": 0.0028592910000000003
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 101,
+ "function": "selected",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.7252e-05,
+ "cumulative_seconds": 0.002837
+ },
+ {
+ "file": "python/workspace_curation.py",
+ "line": 170,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.5624e-05,
+ "cumulative_seconds": 0.0026332910000000003
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 0.002589833,
+ "cumulative_seconds": 0.002589833
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 163,
+ "function": "read_length_coded_string",
+ "primitive_calls": 2712,
+ "calls": 2712,
+ "self_seconds": 0.0008559580000000001,
+ "cumulative_seconds": 0.0025305230000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 898,
+ "function": "",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 5.4031000000000005e-05,
+ "cumulative_seconds": 0.002105
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 183,
+ "function": "dumps",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 4.0372000000000006e-05,
+ "cumulative_seconds": 0.0020357910000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 183,
+ "function": "encode",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 2.8629000000000003e-05,
+ "cumulative_seconds": 0.0019757100000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 205,
+ "function": "iterencode",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 0.0019436660000000001,
+ "cumulative_seconds": 0.0019436660000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 899,
+ "function": "",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 0.00035072,
+ "cumulative_seconds": 0.0017950870000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 166,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.917e-06,
+ "cumulative_seconds": 0.0017245420000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 195,
+ "function": "_chips",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 3.8292000000000005e-05,
+ "cumulative_seconds": 0.0017142510000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 232,
+ "function": "__init__",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 9.221e-05,
+ "cumulative_seconds": 0.0016251290000000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 236,
+ "function": "source_scope",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 6.7618e-05,
+ "cumulative_seconds": 0.0015764160000000002
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 188,
+ "function": "_validated_tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 6.0371e-05,
+ "cumulative_seconds": 0.001572087
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 869,
+ "function": "_execute_command",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00045425,
+ "cumulative_seconds": 0.001518337
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 236,
+ "function": "_parse_field_descriptor",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 0.00035320400000000003,
+ "cumulative_seconds": 0.001511043
+ },
+ {
+ "file": "python/workspace_datastores.py",
+ "line": 192,
+ "function": "source_states",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.4749000000000003e-05,
+ "cumulative_seconds": 0.0014340000000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 282,
+ "function": "__and__",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 1.2125e-05,
+ "cumulative_seconds": 0.001374666
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 178,
+ "function": "restrict",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 0.000107409,
+ "cumulative_seconds": 0.001362541
+ }
+ ],
+ "native_sql_calls": [
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.001239582954440266
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003942079492844641
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00023616600083187222
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004265829920768738
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0003481670282781124
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003493750118650496
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0004118339857086539
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00013762497110292315
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00021129200467839837
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00018354097846895456
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005306670209392905
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0013821249594911933
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004562920075841248
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.0001866249949671328
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0003940830356441438
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002472499618306756
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00034708401653915644
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00029858300695195794
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00013683299766853452
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00021649996051564813
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00045012502232566476
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00026879197685047984
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00020479195518419147
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00313895900035277
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0008001670357771218
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.0004587919684126973
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "seconds": 0.0008829580037854612
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`epoch_uuid`,`included`,`tags`,`review_state`,`revision`,`metadata_fingerprint` FROM `recording_workspace`.`curation` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))AND( ( (`epoch_uuid`='?') OR (",
+ "seconds": 0.001604916003998369
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`dataset_uuid`,`created_at`,`artifact_sha256` FROM `recording_workspace`.`dataset_revision` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0007313330424949527
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.005572959023993462
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.0006252499879337847
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.000411542016081512
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00019879097817465663
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00023737503215670586
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00033245899248868227
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0007911670254543424
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004128749715164304
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.0002832919708453119
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005226670182310045
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002677920274436474
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003719590022228658
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0004917919868603349
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00024750002194195986
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00031995802419260144
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00021849997574463487
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005769999697804451
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0014615000109188259
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0005247920053079724
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00029312499100342393
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005268339882604778
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00032354099676012993
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004479580093175173
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0004295419785194099
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00016670901095494628
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0002632910036481917
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00046158296754583716
+ }
+ ],
+ "limits": "Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap."
+ },
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1070",
+ "ref": "const,const,const",
+ "rows": 20,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ }
+ ],
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "2aa92db9-6472-4c39-9292-5794937593db",
+ "reused": true,
+ "phase": "complete",
+ "seconds": 0.068206
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "2aa92db9-6472-4c39-9292-5794937593db",
+ "reused": true,
+ "phase": "complete",
+ "seconds": 0.114146
+ },
+ "seconds": 0.1826643330277875,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "workflow_peak_rss_before_durable_oracle_bytes": 941637632,
+ "durable_snapshot_verify_seconds": 8.10934654099401,
+ "loaded_product_modules": {
+ "workspace_tree": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree.py",
+ "workspace_predicates": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_predicates.py",
+ "workspace_cache_lifecycle": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_cache_lifecycle.py",
+ "workspace_metadata_objects": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_metadata_objects.py",
+ "workspace_disk_index": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py",
+ "workspace_catalog_identity": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_catalog_identity.py",
+ "recording_workspace": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/recording_workspace.py",
+ "workspace_recipes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recipes.py",
+ "workspace_tree_code": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree_code.py",
+ "workspace_service": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py",
+ "workspace_tree_pages": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree_pages.py",
+ "workspace_projects": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_projects.py",
+ "workspace_mysql_profile": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mysql_profile.py",
+ "workspace_native_mysql": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_native_mysql.py",
+ "workspace_mysql_runtime": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mysql_runtime.py",
+ "workspace_storage": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_storage.py",
+ "workspace_diff": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_diff.py",
+ "workspace_protocol_identity": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_protocol_identity.py",
+ "workspace_import_check": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_import_check.py",
+ "workspace_audit": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_audit.py",
+ "workspace_api": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_api.py",
+ "workspace_state_snapshot": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_snapshot.py",
+ "workspace_author_preferences": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_author_preferences.py",
+ "workspace_curation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_curation.py",
+ "workspace_annotations": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotations.py",
+ "workspace_shared_tag_index": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_shared_tag_index.py",
+ "workspace_paths": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_paths.py",
+ "workspace_desktop_paths": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_desktop_paths.py",
+ "workspace_startup_registry": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_startup_registry.py",
+ "workspace_explorer": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_explorer.py",
+ "workspace_datastores": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_datastores.py",
+ "workspace_suggestions": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_suggestions.py",
+ "workspace_tag_predicates": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tag_predicates.py",
+ "workspace_export_folder": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_export_folder.py",
+ "workspace_import_progress": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_import_progress.py",
+ "workspace_project_preferences": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_project_preferences.py",
+ "workspace_protocol_state": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_protocol_state.py",
+ "workspace_state_generation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_generation.py",
+ "workspace_project_servers": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_project_servers.py",
+ "workspace_launcher": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_launcher.py",
+ "workspace_app_routes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_app_routes.py",
+ "workspace_folder_browser": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_folder_browser.py",
+ "workspace_h5_inbox": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_h5_inbox.py",
+ "workspace_tag_exchange": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tag_exchange.py",
+ "workspace_external_tags": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_external_tags.py",
+ "workspace_qc": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_qc.py",
+ "workspace_search": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_search.py",
+ "workspace_matlab_masks": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab_masks.py",
+ "workspace_matlab_routes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab_routes.py",
+ "workspace_mask_refresh": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mask_refresh.py",
+ "workspace_matching_epochs": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matching_epochs.py",
+ "workspace_candidate_exports": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_candidate_exports.py",
+ "workspace_backup_scheduler": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_backup_scheduler.py",
+ "workspace_recovery_store": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recovery_store.py",
+ "workspace_recovery_generation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recovery_generation.py",
+ "workspace_annotation_preparation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotation_preparation.py",
+ "workspace_native_tag_lookup": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_native_tag_lookup.py",
+ "workspace_lifecycle": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_lifecycle.py",
+ "workspace_native_tag_filter": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_native_tag_filter.py"
+ },
+ "owned_native_runtime_stopped": true,
+ "source_inventory_after": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "17c37104693d46b416064ab05027c54added8d26ddd2ba6b7de202911ce5d592",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "98e5ddf9f5ea388f8697f2c5ea4e176aaf860450e78e2993c0f576ac164e11b5",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "eb16d29e4e6656db8d47f6812cfa2a44c882a8624701ad5b590e85acfacf1388",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7afc6c297770800fb90e148ffeb4ad3d5c24c8e84f5a788094f3246f59f97782",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6286df8eb8ea15f0c2c558a9a42c291167277cb5654ec4dd132ad739e96733b1",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "bac9888aef2eac472e3278902f9285ed5651b0164fa1a76a665ab881c6927ee4",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "66a6a04c107053b3c0492e5ccd2e6f6e10b8479c107c0b58d5b5fd712800312c",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "9f198eb78c0ccc44dfdd4aebd44c0dd890482d6020b0164c5acce4955e46f5e6",
+ "python/workspace_native_tag_lookup.py": "d549e8084af074ab484759a2851fdacde7e632c334f55e2d65caee368e04ffd5",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "35a10441d0ba59e43c59ad564879b2b73eccc29cd19a2558e3eaf8db1a9f5782",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "1917ae5b36553048027a19e1b68f99677a754dae1501d1e52be1cd3faaa2b4a5",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "source_unchanged": false,
+ "harness_unchanged": true,
+ "seconds": 212.61365483299596,
+ "finished_at": "2026-09-30T15:06:27.252955+00:00",
+ "peak_rss_bytes": 941637632,
+ "passed": false
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-500.json b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-500.json
new file mode 100644
index 0000000..f1bd2e1
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-500.json
@@ -0,0 +1,3307 @@
+{
+ "scope": "Persistent native tag lookup: tag-ten, tag-another-ten, filter-twenty and cell/epoch hierarchy.\n\nEvery project/database is disposable. Acquisition metadata is synthetic; actual\nMySQL, triggers, Flask routes, derived indexes and each version's save hooks\nare used. Native SQL commits are checked immediately; any background recovery\nmirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude\noracle work, and do not model browser concurrency/rendering. Sequential bundle\ntimes are sums of their separately measured requests. Profiles are separate.\n",
+ "epochs": 500,
+ "profiles": 3,
+ "tags_per_annotation": 5,
+ "protocols": 2,
+ "samples": 5,
+ "source_root": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI",
+ "source_inventory_before": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "17c37104693d46b416064ab05027c54added8d26ddd2ba6b7de202911ce5d592",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "98e5ddf9f5ea388f8697f2c5ea4e176aaf860450e78e2993c0f576ac164e11b5",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "4c586415a445d806ffe8d64d74f4d53d6d453be6fdcb2b4812da49f69a4525a2",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "22de09a82f03fb9f7b88252c004fa81f25885d7480e0d4d3ce0b7aa9570fd872",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7afc6c297770800fb90e148ffeb4ad3d5c24c8e84f5a788094f3246f59f97782",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6286df8eb8ea15f0c2c558a9a42c291167277cb5654ec4dd132ad739e96733b1",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "6587dcb57e94489175dad26bbbfaa9142170f7eb1b947fe7b3becf40707e11cf",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "66a6a04c107053b3c0492e5ccd2e6f6e10b8479c107c0b58d5b5fd712800312c",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "9f198eb78c0ccc44dfdd4aebd44c0dd890482d6020b0164c5acce4955e46f5e6",
+ "python/workspace_native_tag_lookup.py": "d549e8084af074ab484759a2851fdacde7e632c334f55e2d65caee368e04ffd5",
+ "python/workspace_paths.py": "a75c94ce4c2a5863fb2bf5d79c6abaf4304fb7aa90726d89d80a9e69114d5710",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "3f5ec51667618b9e0a215f6371dd22a9e1502afd54166da0efb9b677045d4216",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "35a10441d0ba59e43c59ad564879b2b73eccc29cd19a2558e3eaf8db1a9f5782",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "1917ae5b36553048027a19e1b68f99677a754dae1501d1e52be1cd3faaa2b4a5",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "fixture_module": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py",
+ "harness_sha256": "a2b5e70861826936fdb4c9d6a5c76efd7645486ad5ce05e1482e3e6aab694fde",
+ "started_at": "2026-09-30T15:00:24.760152+00:00",
+ "release_ready": false,
+ "operations": {
+ "first10_revision_preflight": {
+ "samples_seconds": [
+ 0.006811458966694772,
+ 0.00572104100137949,
+ 0.005855125025846064,
+ 0.005982916976790875,
+ 0.0059248749748803675
+ ],
+ "p50_seconds": 0.0059248749748803675,
+ "p95_seconds": 0.006811458966694772,
+ "p99_seconds": 0.006811458966694772,
+ "maximum_seconds": 0.006811458966694772,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15362,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15362,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ }
+ ]
+ },
+ "first10_save": {
+ "samples_seconds": [
+ 0.0397009999724105,
+ 0.015526416013017297,
+ 0.015385875012725592,
+ 0.01620404195273295,
+ 0.014777208969462663
+ ],
+ "p50_seconds": 0.015526416013017297,
+ "p95_seconds": 0.0397009999724105,
+ "p99_seconds": 0.0397009999724105,
+ "maximum_seconds": 0.0397009999724105,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 18598,
+ "Questions": 36
+ },
+ {
+ "Bytes_sent": 12452,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12423,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ }
+ ]
+ },
+ "second10_revision_preflight": {
+ "samples_seconds": [
+ 0.005753292003646493,
+ 0.0059770410298369825,
+ 0.005863624974153936,
+ 0.005840791971422732,
+ 0.006592250021640211
+ ],
+ "p50_seconds": 0.005863624974153936,
+ "p95_seconds": 0.006592250021640211,
+ "p99_seconds": 0.006592250021640211,
+ "maximum_seconds": 0.006592250021640211,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15380,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15380,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ }
+ ]
+ },
+ "second10_save": {
+ "samples_seconds": [
+ 0.015676334034651518,
+ 0.015652582980692387,
+ 0.014553832996170968,
+ 0.017022708023432642,
+ 0.01785816700430587
+ ],
+ "p50_seconds": 0.015676334034651518,
+ "p95_seconds": 0.01785816700430587,
+ "p99_seconds": 0.01785816700430587,
+ "maximum_seconds": 0.01785816700430587,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12435,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter20": {
+ "samples_seconds": [
+ 0.03799974999856204,
+ 0.021650042035616934,
+ 0.0209364999900572,
+ 0.021898082981351763,
+ 0.022155124985147268
+ ],
+ "p50_seconds": 0.021898082981351763,
+ "p95_seconds": 0.03799974999856204,
+ "p99_seconds": 0.03799974999856204,
+ "maximum_seconds": 0.03799974999856204,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 156835,
+ "Questions": 83
+ },
+ {
+ "Bytes_sent": 134733,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134734,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134734,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134734,
+ "Questions": 57
+ }
+ ]
+ },
+ "cell_save": {
+ "samples_seconds": [
+ 0.007789916999172419,
+ 0.006697749951854348,
+ 0.007052166038192809,
+ 0.007307749998290092,
+ 0.007273291994351894
+ ],
+ "p50_seconds": 0.007273291994351894,
+ "p95_seconds": 0.007789916999172419,
+ "p99_seconds": 0.007789916999172419,
+ "maximum_seconds": 0.007789916999172419,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4165,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_cell100": {
+ "samples_seconds": [
+ 0.04186395899159834,
+ 0.03950862505007535,
+ 0.040653792035300285,
+ 0.039671292004641145,
+ 0.03873795800609514
+ ],
+ "p50_seconds": 0.039671292004641145,
+ "p95_seconds": 0.04186395899159834,
+ "p99_seconds": 0.04186395899159834,
+ "maximum_seconds": 0.04186395899159834,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 168042,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168042,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168043,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168043,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168043,
+ "Questions": 57
+ }
+ ]
+ },
+ "filter_cell_and_epoch10": {
+ "samples_seconds": [
+ 0.01839087496045977,
+ 0.018143708002753556,
+ 0.022742124972864985,
+ 0.01809895900078118,
+ 0.01865949999773875
+ ],
+ "p50_seconds": 0.01839087496045977,
+ "p95_seconds": 0.022742124972864985,
+ "p99_seconds": 0.022742124972864985,
+ "maximum_seconds": 0.022742124972864985,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 125358,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125358,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125359,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125359,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125359,
+ "Questions": 58
+ }
+ ]
+ },
+ "remove_first10_revision_preflight": {
+ "samples_seconds": [
+ 0.005876458017155528,
+ 0.005922999989707023,
+ 0.006230500002857298,
+ 0.0060287920059636235,
+ 0.006431999965570867
+ ],
+ "p50_seconds": 0.0060287920059636235,
+ "p95_seconds": 0.006431999965570867,
+ "p99_seconds": 0.006431999965570867,
+ "maximum_seconds": 0.006431999965570867,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15799,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15799,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_first10_save": {
+ "samples_seconds": [
+ 0.016493040951900184,
+ 0.0152747500105761,
+ 0.018004917015787214,
+ 0.015299541992135346,
+ 0.018318833026569337
+ ],
+ "p50_seconds": 0.016493040951900184,
+ "p95_seconds": 0.018318833026569337,
+ "p99_seconds": 0.018318833026569337,
+ "maximum_seconds": 0.018318833026569337,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12452,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12452,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12423,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_hierarchy_after_epoch_remove": {
+ "samples_seconds": [
+ 0.01246662496123463,
+ 0.01156399998581037,
+ 0.01460758299799636,
+ 0.012225457991007715,
+ 0.012653290992602706
+ ],
+ "p50_seconds": 0.01246662496123463,
+ "p95_seconds": 0.01460758299799636,
+ "p99_seconds": 0.01460758299799636,
+ "maximum_seconds": 0.01460758299799636,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112890,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112890,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112891,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112891,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112891,
+ "Questions": 55
+ }
+ ]
+ },
+ "filter_remaining10": {
+ "samples_seconds": [
+ 0.01550741697428748,
+ 0.017109708045609295,
+ 0.018612417043186724,
+ 0.017108583997469395,
+ 0.017651750007644296
+ ],
+ "p50_seconds": 0.017109708045609295,
+ "p95_seconds": 0.018612417043186724,
+ "p99_seconds": 0.018612417043186724,
+ "maximum_seconds": 0.018612417043186724,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 124593,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124593,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124594,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124594,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124594,
+ "Questions": 57
+ }
+ ]
+ },
+ "remove_second10_revision_preflight": {
+ "samples_seconds": [
+ 0.006006916984915733,
+ 0.006016999948769808,
+ 0.0059361670282669365,
+ 0.005967749981209636,
+ 0.00590987503528595
+ ],
+ "p50_seconds": 0.005967749981209636,
+ "p95_seconds": 0.006016999948769808,
+ "p99_seconds": 0.006016999948769808,
+ "maximum_seconds": 0.006016999948769808,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_second10_save": {
+ "samples_seconds": [
+ 0.015905459003988653,
+ 0.015918832970783114,
+ 0.01652000000467524,
+ 0.01596254203468561,
+ 0.015179125010035932
+ ],
+ "p50_seconds": 0.015918832970783114,
+ "p95_seconds": 0.01652000000467524,
+ "p99_seconds": 0.01652000000467524,
+ "maximum_seconds": 0.01652000000467524,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12435,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_after_all_epoch_removals": {
+ "samples_seconds": [
+ 0.011529624985996634,
+ 0.011944708006922156,
+ 0.01212529098847881,
+ 0.012091375014279038,
+ 0.012476667005103081
+ ],
+ "p50_seconds": 0.012091375014279038,
+ "p95_seconds": 0.012476667005103081,
+ "p99_seconds": 0.012476667005103081,
+ "maximum_seconds": 0.012476667005103081,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112133,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112133,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ }
+ ]
+ },
+ "remove_cell_save": {
+ "samples_seconds": [
+ 0.007468916999641806,
+ 0.006469584011938423,
+ 0.007360292016528547,
+ 0.00706408399855718,
+ 0.007187874987721443
+ ],
+ "p50_seconds": 0.007187874987721443,
+ "p95_seconds": 0.007468916999641806,
+ "p99_seconds": 0.007468916999641806,
+ "maximum_seconds": 0.007468916999641806,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4165,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_after_cell_removal": {
+ "samples_seconds": [
+ 0.012379000021610409,
+ 0.013237042003311217,
+ 0.012182208010926843,
+ 0.011354041984304786,
+ 0.011573958967346698
+ ],
+ "p50_seconds": 0.012182208010926843,
+ "p95_seconds": 0.013237042003311217,
+ "p99_seconds": 0.013237042003311217,
+ "maximum_seconds": 0.013237042003311217,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112133,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112133,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ }
+ ]
+ },
+ "tag10_then10_filter20_http_total": {
+ "samples_seconds": [
+ 0.10594183497596532,
+ 0.06452712306054309,
+ 0.06259495799895376,
+ 0.06694854190573096,
+ 0.06730762595543638
+ ],
+ "p50_seconds": 0.06694854190573096,
+ "p95_seconds": 0.10594183497596532,
+ "p99_seconds": 0.10594183497596532,
+ "maximum_seconds": 0.10594183497596532,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "samples_seconds": [
+ 0.04965387599077076,
+ 0.0462063750019297,
+ 0.04770595807349309,
+ 0.04697904200293124,
+ 0.046011250000447035
+ ],
+ "p50_seconds": 0.04697904200293124,
+ "p95_seconds": 0.04965387599077076,
+ "p99_seconds": 0.04965387599077076,
+ "maximum_seconds": 0.04965387599077076,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "first10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.04651245893910527,
+ 0.021247457014396787,
+ 0.021241000038571656,
+ 0.022186958929523826,
+ 0.02070208394434303
+ ],
+ "p50_seconds": 0.021247457014396787,
+ "p95_seconds": 0.04651245893910527,
+ "p99_seconds": 0.04651245893910527,
+ "maximum_seconds": 0.04651245893910527,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "second10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.02142962603829801,
+ 0.02162962401052937,
+ 0.020417457970324904,
+ 0.022863499994855374,
+ 0.02445041702594608
+ ],
+ "p50_seconds": 0.02162962401052937,
+ "p95_seconds": 0.02445041702594608,
+ "p99_seconds": 0.02445041702594608,
+ "maximum_seconds": 0.02445041702594608,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "autocomplete_cold_diagnostic": {
+ "samples_seconds": [
+ 0.025664957996923476
+ ],
+ "p50_seconds": 0.025664957996923476,
+ "p95_seconds": 0.025664957996923476,
+ "p99_seconds": 0.025664957996923476,
+ "maximum_seconds": 0.025664957996923476,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 214891,
+ "Questions": 89
+ }
+ ]
+ },
+ "autocomplete_warm_diagnostic": {
+ "samples_seconds": [
+ 0.02846150001278147
+ ],
+ "p50_seconds": 0.02846150001278147,
+ "p95_seconds": 0.02846150001278147,
+ "p99_seconds": 0.02846150001278147,
+ "maximum_seconds": 0.02846150001278147,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 214891,
+ "Questions": 89
+ }
+ ]
+ },
+ "same_server_native_reuse_filter20": {
+ "samples_seconds": [
+ 0.019670958979986608
+ ],
+ "p50_seconds": 0.019670958979986608,
+ "p95_seconds": 0.019670958979986608,
+ "p99_seconds": 0.019670958979986608,
+ "maximum_seconds": 0.019670958979986608,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 134735,
+ "Questions": 57
+ }
+ ]
+ }
+ },
+ "checks": [
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 167
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "page_cells": true,
+ "target_specification": "tag10_then10_filter20_and_cell_epoch_hierarchy",
+ "baseline_harness_sha256": "272fe050c8762ed4ea45bd013e236e8723d8fe46f5e7e293c58e2edf1fd52ddc",
+ "mysql_version": "8.4.2",
+ "seed_seconds": 0.41018587502185255,
+ "seed_checkpoint_seconds": 0.17138437496032566,
+ "native_lookup_startup_preparation": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "7c07d8c5-6f30-473f-90be-19146e25d0c8",
+ "reused": false,
+ "phase": "complete",
+ "seconds": 0.165026
+ },
+ "selected_epoch_uuids": {
+ "first10": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5"
+ ],
+ "second10": [
+ "ad573668-6512-6e55-649e-cb23ae1d4888",
+ "16dc368a-89b4-28b2-4854-84313ba67a39",
+ "37834f2f-2576-2f23-e1f7-4a531cbe445d",
+ "454f63ac-30c8-3229-97ef-025edff6abd2",
+ "5ef6fdf3-2513-aa7c-d11f-72beccf132b9",
+ "1253e937-3e78-1b75-0026-6caa55150e08",
+ "482d9673-cfee-5de3-91f9-7fde4d1c84f9",
+ "3346f2bb-f6c3-4bd2-dbe2-8bd1bb657d0e",
+ "9537f32e-c759-9e1a-e953-af6c9f929fe7",
+ "0fd42b3f-73c4-48b3-4940-b339f87d07ad"
+ ],
+ "cell": "0604cd31-38fe-ed20-2ef2-93e062da2f47",
+ "cell_children": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5",
+ "4a44dc15-3642-04a8-0fe8-0e9039455cc1",
+ "4fc82b26-aecb-47d2-868c-4efbe3581732",
+ "6b51d431-df5d-7f14-1cbe-ceccf79edf3d",
+ "3fdba35f-04dc-8c46-2986-c992bcf87554",
+ "8527a891-e224-1369-50ff-32ca212b45bc",
+ "e629fa65-98d7-3276-8f7c-726b4b621285",
+ "b17ef6d1-9c7a-5b1e-e83b-907c595526dc",
+ "4523540f-1504-cd17-100c-4835e85b7eef",
+ "4ec9599f-c203-d176-a301-536c2e091a19",
+ "9400f1b2-1cb5-27d7-fa3d-3eabba93557a",
+ "f5ca38f7-48a1-d6ea-f726-b8a42fb575c3",
+ "6f4b6612-125f-b3a0-daec-d2799dfd6c9c",
+ "785f3ec7-eb32-f30b-90cd-0fcf3657d388",
+ "535fa30d-7e25-dd8a-49f1-536779734ec8",
+ "c2356069-e9d1-e79c-a924-378153cfbbfb",
+ "b7a56873-cd77-1f2c-446d-369b649430b6",
+ "5f9c4ab0-8cac-7457-e911-1a30e4664920",
+ "670671cd-9740-4156-226e-507973f2ab83",
+ "59e19706-d51d-39f6-6711-c2653cd7eb12",
+ "35135aaa-6cc2-3891-b40c-b3f378c53a17",
+ "624b60c5-8c9d-8bfb-6ff1-886c2fd605d2",
+ "eb1e33e8-a81b-697b-7585-5af6bfcdbcbf",
+ "e29c9c18-0c62-79b0-b02a-bd6a1801c7c0",
+ "c6f3ac57-944a-5314-90cd-39902d0f7777",
+ "86e50149-6586-6131-2a9e-0b35558d84f6",
+ "9f14025a-f006-5b30-e47e-23ebb3b491d3",
+ "76a50887-d8f1-c2e9-3017-55428990ad81",
+ "7a61b537-01be-fdae-0eee-ffaecc73f14e",
+ "aea92132-c4cb-eb26-3e6a-c2bf6c183b5d",
+ "0b918943-df09-62bc-7a18-24c0555a3893",
+ "d59eced1-ded0-7f84-c145-592f65bdf854",
+ "3d914f93-48c9-cc0f-f8a7-9716700b9fcd",
+ "73475cb4-0a56-8e8d-a8a0-45ced110137e",
+ "44cb730c-4204-80a0-477b-505ae68af508",
+ "71ee45a3-c0db-9a98-65f7-313dd3372cf6",
+ "811786ad-1ae7-4adf-dd20-dd0372abaaeb",
+ "25fc0e70-96fc-6537-1820-2dc30b0c580b",
+ "31489056-e091-6d59-fe3a-dd79e63f095a",
+ "98010bd9-270f-9b10-0b62-14a21754fd33",
+ "0e17daca-5f3e-175f-448b-acace3bc0da4",
+ "1a656259-0ef1-9d10-45d0-6c4055742d38",
+ "031b4af5-197e-c30a-926f-48cf40e11a7d",
+ "41cfc0d1-f2d1-27b0-4555-b7246d84019b",
+ "2858dcd1-057d-3eae-7f7d-5f782167e24b",
+ "2fca346d-b656-1871-02ce-806ac732e06a",
+ "02d20bbd-7e39-4ad5-999a-4cebabac9619",
+ "7688b6ef-5255-5962-d008-fff894223582",
+ "c837649c-ce43-f272-9138-e72cc3152070",
+ "6208ef0f-7750-c111-548c-f90b6ea1d0d0",
+ "3e1e967e-9b79-3e90-8f8e-ae83c74dba9b",
+ "39fa9ec1-90ee-e7b6-f4df-f1100d6343e1",
+ "d029fa3a-95e1-74a1-9934-857f535eb942",
+ "81b8a03f-97e8-787c-53fe-1a86bda042b6",
+ "da4ea2a5-506f-2693-eae1-90d9360a1f31",
+ "a68b412c-4282-555f-1554-6cf6e1fc4289",
+ "108c995b-953c-8a35-5611-03e2014cf828",
+ "3ada92f2-8b4c-eda3-8562-ebf047c6ff05",
+ "49d180ec-f561-3281-9571-bf39d9b7b342",
+ "a21855da-08cb-102d-1d21-7c53dc5824a3",
+ "c75cb66a-e28d-8ebc-6ede-d002c28a8ba0",
+ "ff5a1ae0-12af-a5d4-c889-c50ad427aaf5",
+ "7f2253d7-e228-b22a-08bd-a1f09c516f6f",
+ "87226162-0421-7edd-b39e-7df969e0698a",
+ "96061e92-f58e-4bdc-dee7-3df36183fe3a",
+ "eb624dbe-56eb-6620-ae62-080c10a273ca",
+ "f369cb89-fc62-7e66-8987-007d121ed1ea",
+ "f74efabe-f12e-a619-e30b-79bddef89cff",
+ "a88a7902-cb4e-f697-ba0b-6759c50e8c10",
+ "349c4120-1b62-db85-1192-665c504b350f",
+ "98a3ab7c-340e-8a03-3e7b-37b6ef942875",
+ "48449a14-a4ff-7d79-bb7a-1b6f3d488eba",
+ "5316ca1c-5ddc-a8e6-cecc-fce58f3b8540",
+ "a46e3763-2fa6-ca51-a13f-e39a567b3c23",
+ "bbb965ab-0c80-d653-8cf2-184babad2a56",
+ "44c8031c-b036-a735-0d8b-9b8603af662a",
+ "b4944c6f-f08d-c6f4-3da2-e9c824669b7d",
+ "434c9b5a-e514-646b-bd91-b50032ca579e",
+ "bdd2d3af-3a5a-1213-497d-4f1f7bfcda89",
+ "8b940be7-fb78-aaa6-b656-7dd7a3987996",
+ "cd70bea0-23f7-52a0-564a-bb6ed08d42c1",
+ "69f59c27-3b6e-669a-c32a-6dd5e1b2cb63",
+ "1da51b8d-8ff9-8f6a-48f8-0ae79fe3ca6c",
+ "82416496-09f8-8ccd-2a0a-5b233a07a538",
+ "6e400187-1c0c-f27c-7634-ef1dc478408f",
+ "e3d6c4d4-599e-0088-2384-ca981ee287ed",
+ "ad48ff99-415b-2f00-7dc3-5b7eb553fd1e",
+ "7b1a278f-5abe-8e9d-a907-fc9c29dfd432",
+ "d6d824ab-ba4a-fde8-1129-c71dea75b810",
+ "29db0c67-82db-d500-0559-ef4d9e953e30",
+ "8c1f1046-219d-dd21-6a02-3f792356ddf1"
+ ]
+ },
+ "backup_receipts": [
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 1,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 2,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 3,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 4,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 5,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 6,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 7,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 8,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 9,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 10,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 11,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 12,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 13,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 14,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 15,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 16,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 17,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 18,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 19,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 20,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 21,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 22,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 23,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 24,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 25,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 26,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 27,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 28,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 29,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 30,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:00:32.685142+00:00",
+ "pending": true,
+ "requested_sequence": 31,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:00:32.685142+00:00",
+ "pending": true,
+ "requested_sequence": 32,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_first10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:00:32.948785+00:00",
+ "pending": true,
+ "requested_sequence": 33,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_second10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:00:32.948785+00:00",
+ "pending": true,
+ "requested_sequence": 34,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ }
+ ],
+ "preparation_index_seconds": 0,
+ "lookup_mode": "persistent_native_sql_no_post_seed_tag_cache_preparation",
+ "bundle_definitions": {
+ "tag10_then10_filter20_http_total": [
+ "first10_revision_preflight",
+ "first10_save",
+ "second10_revision_preflight",
+ "second10_save",
+ "filter20"
+ ],
+ "cell_tag_then_cell_filter_http_total": [
+ "cell_save",
+ "filter_cell100"
+ ],
+ "first10_selected_action_http_total": [
+ "first10_revision_preflight",
+ "first10_save"
+ ],
+ "second10_selected_action_http_total": [
+ "second10_revision_preflight",
+ "second10_save"
+ ]
+ },
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:00:33.647722+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "seconds": 0.006383416010066867
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "seconds": 0.0028258750098757446
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "seconds": 0.007205624948255718
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "seconds": 0.0027852500206790864
+ }
+ ]
+ },
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY,by_tag",
+ "key": "PRIMARY",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 3787,
+ "filtered": 100.0,
+ "Extra": "Using index; Using filesort"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "PRIMARY",
+ "table": "",
+ "partitions": null,
+ "type": "ALL",
+ "possible_keys": null,
+ "key": null,
+ "key_len": null,
+ "ref": null,
+ "rows": 542,
+ "filtered": 100.0,
+ "Extra": null
+ },
+ {
+ "id": 1,
+ "select_type": "PRIMARY",
+ "table": "a",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "111",
+ "ref": "const,func",
+ "rows": 1,
+ "filtered": 5.0,
+ "Extra": "Using where"
+ },
+ {
+ "id": 2,
+ "select_type": "DERIVED",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY,by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 542,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ }
+ ],
+ "filter20_cpu_profile": {
+ "top_functions": [
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 313,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.541e-06,
+ "cumulative_seconds": 0.054274083
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 159,
+ "function": "request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.79e-06,
+ "cumulative_seconds": 0.054249583000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1163,
+ "function": "get",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.459000000000001e-06,
+ "cumulative_seconds": 0.053411834000000005
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/testing.py",
+ "line": 204,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.2751e-05,
+ "cumulative_seconds": 0.053406375000000006
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1062,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.2500000000000006e-06,
+ "cumulative_seconds": 0.053143458000000005
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 984,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.958e-06,
+ "cumulative_seconds": 0.05312962500000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1230,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.8780000000000006e-06,
+ "cumulative_seconds": 0.052947917000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1529,
+ "function": "__call__",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.500000000000001e-06,
+ "cumulative_seconds": 0.052924375
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1479,
+ "function": "wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0209e-05,
+ "cumulative_seconds": 0.052918875000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 904,
+ "function": "full_dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.624e-06,
+ "cumulative_seconds": 0.052687500000000005
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 879,
+ "function": "dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.2209e-05,
+ "cumulative_seconds": 0.052494375
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 716,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.2328e-05,
+ "cumulative_seconds": 0.05241875
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 295,
+ "function": "for_epochs",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.590400000000001e-05,
+ "cumulative_seconds": 0.021142541
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 304,
+ "function": "traced_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000196102,
+ "cumulative_seconds": 0.020249043
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 412,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 9.642e-05,
+ "cumulative_seconds": 0.019690752000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 402,
+ "function": "_execute_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00010849500000000001,
+ "cumulative_seconds": 0.019441834
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 27,
+ "calls": 38,
+ "self_seconds": 2.7003000000000002e-05,
+ "cumulative_seconds": 0.019405418
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 249,
+ "function": "native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.0292e-05,
+ "cumulative_seconds": 0.019342208
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 150,
+ "function": "execute",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 5.5366000000000005e-05,
+ "cumulative_seconds": 0.019082372
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 332,
+ "function": "_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.423400000000001e-05,
+ "cumulative_seconds": 0.018632707000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 575,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.283100000000001e-05,
+ "cumulative_seconds": 0.018174499
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 851,
+ "function": "_read_query_result",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 5.4294000000000006e-05,
+ "cumulative_seconds": 0.017274794
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1288,
+ "function": "read",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.753400000000001e-05,
+ "cumulative_seconds": 0.017198458
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 128,
+ "function": "deepcopy",
+ "primitive_calls": 78,
+ "calls": 17170,
+ "self_seconds": 0.00932983,
+ "cumulative_seconds": 0.015814497
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 201,
+ "function": "_deepcopy_list",
+ "primitive_calls": 62,
+ "calls": 102,
+ "self_seconds": 0.000296734,
+ "cumulative_seconds": 0.015689001
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 101,
+ "function": "_rows",
+ "primitive_calls": 46,
+ "calls": 46,
+ "self_seconds": 5.2170000000000004e-05,
+ "cumulative_seconds": 0.015632377
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 197,
+ "function": "response_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.5e-05,
+ "cumulative_seconds": 0.015488125
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 233,
+ "function": "_attest_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 5.4624000000000004e-05,
+ "cumulative_seconds": 0.015459541
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 227,
+ "function": "_deepcopy_dict",
+ "primitive_calls": 1242,
+ "calls": 1284,
+ "self_seconds": 0.0027896590000000003,
+ "cumulative_seconds": 0.013735265000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 763,
+ "function": "_read_packet",
+ "primitive_calls": 765,
+ "calls": 765,
+ "self_seconds": 0.0011524550000000001,
+ "cumulative_seconds": 0.012796017000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 813,
+ "function": "_read_bytes",
+ "primitive_calls": 1530,
+ "calls": 1530,
+ "self_seconds": 0.000545364,
+ "cumulative_seconds": 0.009765910000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 141,
+ "function": "__exit__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 3.5430000000000002e-06,
+ "cumulative_seconds": 0.00970025
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 132,
+ "function": "__enter__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 2.5410000000000003e-06,
+ "cumulative_seconds": 0.009647083
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 134,
+ "function": "_active_ddl_guard",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 3.2710000000000004e-05,
+ "cumulative_seconds": 0.009180917
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 1530,
+ "calls": 1530,
+ "self_seconds": 0.000207604,
+ "cumulative_seconds": 0.009142557
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/socket.py",
+ "line": 704,
+ "function": "readinto",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 6.491400000000001e-05,
+ "cumulative_seconds": 0.008934953
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1299,
+ "function": "recv_into",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 7.657700000000001e-05,
+ "cumulative_seconds": 0.008833749
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1157,
+ "function": "read",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 4.5587e-05,
+ "cumulative_seconds": 0.008747209
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 0.008697289,
+ "cumulative_seconds": 0.008697289
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1370,
+ "function": "_read_result_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 5.8163000000000005e-05,
+ "cumulative_seconds": 0.007926465
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/__init__.py",
+ "line": 138,
+ "function": "jsonify",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.6240000000000004e-06,
+ "cumulative_seconds": 0.007166041000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 189,
+ "function": "response",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.0916e-05,
+ "cumulative_seconds": 0.007155000000000001
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 61,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.6960000000000005e-05,
+ "cumulative_seconds": 0.007076917
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 869,
+ "function": "to_dicts",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 1.8752000000000003e-05,
+ "cumulative_seconds": 0.006889543000000001
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 62,
+ "function": "browser_safe",
+ "primitive_calls": 1,
+ "calls": 9641,
+ "self_seconds": 0.003157097,
+ "cumulative_seconds": 0.0056334160000000005
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 66,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1363,
+ "self_seconds": 0.001445494,
+ "cumulative_seconds": 0.005631625
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 2,
+ "calls": 102,
+ "self_seconds": 0.000189636,
+ "cumulative_seconds": 0.005620333
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1416,
+ "function": "_read_rowdata_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000339208,
+ "cumulative_seconds": 0.005085877
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 1231,
+ "function": "cursor",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 1.4335e-05,
+ "cumulative_seconds": 0.00477875
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 173,
+ "function": "_rows",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 9.921e-06,
+ "cumulative_seconds": 0.004233417000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 268,
+ "function": "_native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.8083000000000002e-05,
+ "cumulative_seconds": 0.003803166
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 311,
+ "function": "native_context",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.0876e-05,
+ "cumulative_seconds": 0.0037510420000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1429,
+ "function": "_read_row_from_packet",
+ "primitive_calls": 428,
+ "calls": 428,
+ "self_seconds": 0.000901201,
+ "cumulative_seconds": 0.0030138200000000004
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 125,
+ "function": "_observer_status",
+ "primitive_calls": 8,
+ "calls": 8,
+ "self_seconds": 2.6455e-05,
+ "cumulative_seconds": 0.002907666
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1448,
+ "function": "_get_descriptions",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00030576800000000004,
+ "cumulative_seconds": 0.0027617970000000003
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 163,
+ "function": "read_length_coded_string",
+ "primitive_calls": 2712,
+ "calls": 2712,
+ "self_seconds": 0.000823981,
+ "cumulative_seconds": 0.0024374170000000003
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 115,
+ "function": "_triggers",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 9.59e-07,
+ "cumulative_seconds": 0.002420209
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 107,
+ "function": "_ddl",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 2.6868e-05,
+ "cumulative_seconds": 0.002107042
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 898,
+ "function": "",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 5.0951e-05,
+ "cumulative_seconds": 0.002088458
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 185,
+ "function": "_metadata",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.8792000000000002e-05,
+ "cumulative_seconds": 0.00182825
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 195,
+ "function": "_chips",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 3.8617e-05,
+ "cumulative_seconds": 0.00182292
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 899,
+ "function": "",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 0.00034619500000000004,
+ "cumulative_seconds": 0.0017873770000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 101,
+ "function": "selected",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.121000000000001e-06,
+ "cumulative_seconds": 0.001738416
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 188,
+ "function": "_validated_tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 5.9242000000000006e-05,
+ "cumulative_seconds": 0.0016865060000000002
+ },
+ {
+ "file": "python/workspace_curation.py",
+ "line": 170,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.332e-06,
+ "cumulative_seconds": 0.00164575
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 183,
+ "function": "dumps",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 2.1914e-05,
+ "cumulative_seconds": 0.001609041
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 183,
+ "function": "encode",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 1.8209e-05,
+ "cumulative_seconds": 0.0015800430000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 205,
+ "function": "iterencode",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 0.001558708,
+ "cumulative_seconds": 0.001558708
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 232,
+ "function": "__init__",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 8.5381e-05,
+ "cumulative_seconds": 0.0015486360000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 37540,
+ "calls": 37540,
+ "self_seconds": 0.00153531,
+ "cumulative_seconds": 0.00153531
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 748,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.1166e-05,
+ "cumulative_seconds": 0.001464709
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 236,
+ "function": "_parse_field_descriptor",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 0.000335937,
+ "cumulative_seconds": 0.00144242
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 778,
+ "function": "_epoch_page_identities",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.5844e-05,
+ "cumulative_seconds": 0.0014215
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 166,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.125e-06,
+ "cumulative_seconds": 0.001406541
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 282,
+ "function": "__and__",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 1.0668e-05,
+ "cumulative_seconds": 0.001164167
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 178,
+ "function": "restrict",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 7.737400000000001e-05,
+ "cumulative_seconds": 0.001153499
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 223,
+ "function": "attributes",
+ "primitive_calls": 1006,
+ "calls": 1006,
+ "self_seconds": 0.000372064,
+ "cumulative_seconds": 0.001152573
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 34,
+ "function": "text",
+ "primitive_calls": 416,
+ "calls": 416,
+ "self_seconds": 0.00034434200000000003,
+ "cumulative_seconds": 0.0011120140000000001
+ },
+ {
+ "file": "python/workspace_explorer.py",
+ "line": 60,
+ "function": "protocol_binding_header",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 1.2746000000000001e-05,
+ "cumulative_seconds": 0.00109525
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 299,
+ "function": "loads",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 5.6668e-05,
+ "cumulative_seconds": 0.001059706
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 347,
+ "function": "token",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 1.0581e-05,
+ "cumulative_seconds": 0.0010067070000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 61,
+ "function": "read",
+ "primitive_calls": 2712,
+ "calls": 2712,
+ "self_seconds": 0.000882678,
+ "cumulative_seconds": 0.000998633
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 40,
+ "function": "tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 6.6506e-05,
+ "cumulative_seconds": 0.00099105
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 22,
+ "calls": 78,
+ "self_seconds": 5.3205e-05,
+ "cumulative_seconds": 0.0009847080000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 359,
+ "function": "_fresh_token",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.4166000000000001e-05,
+ "cumulative_seconds": 0.000982375
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 285,
+ "function": "__getitem__",
+ "primitive_calls": 745,
+ "calls": 745,
+ "self_seconds": 0.00014096100000000002,
+ "cumulative_seconds": 0.0009730740000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/decoder.py",
+ "line": 332,
+ "function": "decode",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 8.482300000000001e-05,
+ "cumulative_seconds": 0.0009629560000000001
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 311,
+ "function": "_scope",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 9.795e-06,
+ "cumulative_seconds": 0.0009457930000000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 236,
+ "function": "source_scope",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.8468000000000003e-05,
+ "cumulative_seconds": 0.0009385830000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 20369,
+ "calls": 20369,
+ "self_seconds": 0.0009083400000000001,
+ "cumulative_seconds": 0.000934426
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 42,
+ "function": "",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 5.7363000000000006e-05,
+ "cumulative_seconds": 0.0009112090000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 261,
+ "function": "filter_epoch_ids",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.372e-05,
+ "cumulative_seconds": 0.000866833
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/condition.py",
+ "line": 278,
+ "function": "make_condition",
+ "primitive_calls": 14,
+ "calls": 56,
+ "self_seconds": 0.00023307000000000002,
+ "cumulative_seconds": 0.000858958
+ },
+ {
+ "file": "python/workspace_datastores.py",
+ "line": 192,
+ "function": "source_states",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 6.79e-06,
+ "cumulative_seconds": 0.000854916
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/wrappers/response.py",
+ "line": 601,
+ "function": "get_json",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.1260000000000002e-06,
+ "cumulative_seconds": 0.0008338340000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 456,
+ "calls": 456,
+ "self_seconds": 0.00023371400000000002,
+ "cumulative_seconds": 0.000833748
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/decoder.py",
+ "line": 343,
+ "function": "raw_decode",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 0.000832791,
+ "cumulative_seconds": 0.000832791
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 181,
+ "function": "loads",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.5e-06,
+ "cumulative_seconds": 0.0008173330000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 869,
+ "function": "_execute_command",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00016949400000000001,
+ "cumulative_seconds": 0.000815087
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 207,
+ "function": "_metadata_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.8793e-05,
+ "cumulative_seconds": 0.000744291
+ }
+ ],
+ "native_sql_calls": [
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.000690957996994257
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00035266700433567166
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00018516700947657228
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00040933297714218497
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00020754203433170915
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00032491603633388877
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003042080206796527
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00013237498933449388
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0002060839906334877
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00017895898781716824
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00044425000669434667
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0012032919912599027
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00032829202245920897
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00018783402629196644
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00037687504664063454
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00020004104590043426
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003575420123524964
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003186250105500221
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014462496619671583
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0002282499917782843
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00041574996430426836
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0002098330296576023
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.000166917045135051
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00022633298067376018
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00029020803049206734
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.000230583013035357
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "seconds": 0.00038166699232533574
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`epoch_uuid`,`included`,`tags`,`review_state`,`revision`,`metadata_fingerprint` FROM `recording_workspace`.`curation` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))AND( ( (`epoch_uuid`='?') OR (",
+ "seconds": 0.0007193329511210322
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`dataset_uuid`,`created_at`,`artifact_sha256` FROM `recording_workspace`.`dataset_revision` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00022025004727765918
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.0015987499500624835
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.00046212499728426337
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0003293330082669854
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00019670795882120728
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00023145898012444377
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00031979201594367623
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0005031659966334701
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.000378583965357393
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00025212502805516124
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004041249630972743
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002279590116813779
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003369160112924874
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00033887499012053013
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00016108399722725153
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00022545800311490893
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00018754199845716357
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004633330390788615
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0012080839951522648
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003772909985855222
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00026299996534362435
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0003891250235028565
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00023641600273549557
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003325419966131449
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0002959170378744602
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014779198681935668
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00024716596817597747
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003652920131571591
+ }
+ ],
+ "limits": "Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap."
+ },
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1070",
+ "ref": "const,const,const",
+ "rows": 20,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ }
+ ],
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "7c07d8c5-6f30-473f-90be-19146e25d0c8",
+ "reused": true,
+ "phase": "complete",
+ "seconds": 0.064545
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "7c07d8c5-6f30-473f-90be-19146e25d0c8",
+ "reused": true,
+ "phase": "complete",
+ "seconds": 0.095174
+ },
+ "seconds": 0.1597440000041388,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "workflow_peak_rss_before_durable_oracle_bytes": 168656896,
+ "durable_snapshot_verify_seconds": 0.031988709000870585,
+ "loaded_product_modules": {
+ "workspace_tree": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree.py",
+ "workspace_predicates": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_predicates.py",
+ "workspace_cache_lifecycle": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_cache_lifecycle.py",
+ "workspace_metadata_objects": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_metadata_objects.py",
+ "workspace_disk_index": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py",
+ "workspace_catalog_identity": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_catalog_identity.py",
+ "recording_workspace": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/recording_workspace.py",
+ "workspace_recipes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recipes.py",
+ "workspace_tree_code": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree_code.py",
+ "workspace_service": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py",
+ "workspace_tree_pages": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree_pages.py",
+ "workspace_projects": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_projects.py",
+ "workspace_mysql_profile": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mysql_profile.py",
+ "workspace_native_mysql": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_native_mysql.py",
+ "workspace_mysql_runtime": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mysql_runtime.py",
+ "workspace_storage": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_storage.py",
+ "workspace_diff": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_diff.py",
+ "workspace_protocol_identity": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_protocol_identity.py",
+ "workspace_import_check": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_import_check.py",
+ "workspace_audit": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_audit.py",
+ "workspace_api": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_api.py",
+ "workspace_state_snapshot": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_snapshot.py",
+ "workspace_author_preferences": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_author_preferences.py",
+ "workspace_curation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_curation.py",
+ "workspace_annotations": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotations.py",
+ "workspace_shared_tag_index": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_shared_tag_index.py",
+ "workspace_paths": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_paths.py",
+ "workspace_desktop_paths": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_desktop_paths.py",
+ "workspace_startup_registry": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_startup_registry.py",
+ "workspace_explorer": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_explorer.py",
+ "workspace_datastores": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_datastores.py",
+ "workspace_suggestions": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_suggestions.py",
+ "workspace_tag_predicates": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tag_predicates.py",
+ "workspace_export_folder": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_export_folder.py",
+ "workspace_import_progress": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_import_progress.py",
+ "workspace_project_preferences": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_project_preferences.py",
+ "workspace_protocol_state": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_protocol_state.py",
+ "workspace_state_generation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_generation.py",
+ "workspace_project_servers": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_project_servers.py",
+ "workspace_launcher": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_launcher.py",
+ "workspace_app_routes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_app_routes.py",
+ "workspace_folder_browser": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_folder_browser.py",
+ "workspace_h5_inbox": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_h5_inbox.py",
+ "workspace_tag_exchange": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tag_exchange.py",
+ "workspace_external_tags": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_external_tags.py",
+ "workspace_qc": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_qc.py",
+ "workspace_search": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_search.py",
+ "workspace_matlab_masks": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab_masks.py",
+ "workspace_matlab_routes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab_routes.py",
+ "workspace_mask_refresh": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mask_refresh.py",
+ "workspace_matching_epochs": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matching_epochs.py",
+ "workspace_candidate_exports": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_candidate_exports.py",
+ "workspace_backup_scheduler": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_backup_scheduler.py",
+ "workspace_recovery_store": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recovery_store.py",
+ "workspace_recovery_generation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recovery_generation.py",
+ "workspace_annotation_preparation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotation_preparation.py",
+ "workspace_native_tag_lookup": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_native_tag_lookup.py",
+ "workspace_lifecycle": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_lifecycle.py",
+ "workspace_native_tag_filter": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_native_tag_filter.py"
+ },
+ "owned_native_runtime_stopped": true,
+ "source_inventory_after": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "17c37104693d46b416064ab05027c54added8d26ddd2ba6b7de202911ce5d592",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "98e5ddf9f5ea388f8697f2c5ea4e176aaf860450e78e2993c0f576ac164e11b5",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "4c586415a445d806ffe8d64d74f4d53d6d453be6fdcb2b4812da49f69a4525a2",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "22de09a82f03fb9f7b88252c004fa81f25885d7480e0d4d3ce0b7aa9570fd872",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7afc6c297770800fb90e148ffeb4ad3d5c24c8e84f5a788094f3246f59f97782",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6286df8eb8ea15f0c2c558a9a42c291167277cb5654ec4dd132ad739e96733b1",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "6587dcb57e94489175dad26bbbfaa9142170f7eb1b947fe7b3becf40707e11cf",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "66a6a04c107053b3c0492e5ccd2e6f6e10b8479c107c0b58d5b5fd712800312c",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "9f198eb78c0ccc44dfdd4aebd44c0dd890482d6020b0164c5acce4955e46f5e6",
+ "python/workspace_native_tag_lookup.py": "d549e8084af074ab484759a2851fdacde7e632c334f55e2d65caee368e04ffd5",
+ "python/workspace_paths.py": "a75c94ce4c2a5863fb2bf5d79c6abaf4304fb7aa90726d89d80a9e69114d5710",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "3f5ec51667618b9e0a215f6371dd22a9e1502afd54166da0efb9b677045d4216",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "35a10441d0ba59e43c59ad564879b2b73eccc29cd19a2558e3eaf8db1a9f5782",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "1917ae5b36553048027a19e1b68f99677a754dae1501d1e52be1cd3faaa2b4a5",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "source_unchanged": true,
+ "harness_unchanged": true,
+ "seconds": 9.550093834055588,
+ "finished_at": "2026-09-30T15:00:34.310755+00:00",
+ "peak_rss_bytes": 168656896,
+ "passed": true
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-results.json b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-results.json
new file mode 100644
index 0000000..f770404
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-results.json
@@ -0,0 +1,632 @@
+{
+ "scope": "Native SQL lookup versus prior SQLite membership/vocabulary cache, same scientist tag workflow; isolated synthetic fixture; HTTP test client timings, not browser paint.",
+ "passed": false,
+ "workflow_correctness_checks_passed": true,
+ "source_and_harness_unchanged_across_both_native_runs": false,
+ "samples_per_workflow_operation": 5,
+ "source_inventory_sha256": "40bc48eab49a28794aae0e50858e5476c2332325720798e9ffc3f128b6994c25",
+ "native_harness_sha256": "a2b5e70861826936fdb4c9d6a5c76efd7645486ad5ce05e1482e3e6aab694fde",
+ "scales": {
+ "500": {
+ "native_receipt": "native-tag-sequence-pre-autocompletefix-500.json",
+ "receipt_passed": true,
+ "source_unchanged": true,
+ "changed_during_run": {},
+ "prior_receipt": "tag-sequence-after-500.json",
+ "comparison_ms": {
+ "first10_save": {
+ "prior_p50": 15.186292002908885,
+ "native_p50": 15.526416013017297,
+ "native_first_sample": 39.7009999724105,
+ "native_maximum": 39.7009999724105
+ },
+ "second10_save": {
+ "prior_p50": 13.435167027637362,
+ "native_p50": 15.676334034651518,
+ "native_first_sample": 15.676334034651518,
+ "native_maximum": 17.85816700430587
+ },
+ "filter20": {
+ "prior_p50": 31.089458032511175,
+ "native_p50": 21.898082981351763,
+ "native_first_sample": 37.99974999856204,
+ "native_maximum": 37.99974999856204
+ },
+ "tag10_then10_filter20_http_total": {
+ "prior_p50": 73.59912293031812,
+ "native_p50": 66.94854190573096,
+ "native_first_sample": 105.94183497596532,
+ "native_maximum": 105.94183497596532
+ },
+ "cell_save": {
+ "prior_p50": 6.7298339563421905,
+ "native_p50": 7.273291994351894,
+ "native_first_sample": 7.789916999172419,
+ "native_maximum": 7.789916999172419
+ },
+ "filter_cell100": {
+ "prior_p50": 41.47870797896758,
+ "native_p50": 39.671292004641145,
+ "native_first_sample": 41.86395899159834,
+ "native_maximum": 41.86395899159834
+ },
+ "filter_cell_and_epoch10": {
+ "prior_p50": 17.395791015587747,
+ "native_p50": 18.39087496045977,
+ "native_first_sample": 18.39087496045977,
+ "native_maximum": 22.742124972864985
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "prior_p50": 48.06662496412173,
+ "native_p50": 46.97904200293124,
+ "native_first_sample": 49.65387599077076,
+ "native_maximum": 49.65387599077076
+ }
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "checks": [
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 167
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1070",
+ "ref": "const,const,const",
+ "rows": 20,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ }
+ ],
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY,by_tag",
+ "key": "PRIMARY",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 3787,
+ "filtered": 100.0,
+ "Extra": "Using index; Using filesort"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "PRIMARY",
+ "table": "",
+ "partitions": null,
+ "type": "ALL",
+ "possible_keys": null,
+ "key": null,
+ "key_len": null,
+ "ref": null,
+ "rows": 542,
+ "filtered": 100.0,
+ "Extra": null
+ },
+ {
+ "id": 1,
+ "select_type": "PRIMARY",
+ "table": "a",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "111",
+ "ref": "const,func",
+ "rows": 1,
+ "filtered": 5.0,
+ "Extra": "Using where"
+ },
+ {
+ "id": 2,
+ "select_type": "DERIVED",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY,by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 542,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ }
+ ],
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "seconds": 0.006383416010066867
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "seconds": 0.0028258750098757446
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "seconds": 0.007205624948255718
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "seconds": 0.0027852500206790864
+ }
+ ]
+ },
+ "autocomplete_diagnostic_ms": {
+ "autocomplete_cold_diagnostic": 25.664957996923476,
+ "autocomplete_warm_diagnostic": 28.46150001278147
+ },
+ "initial_empty_native_setup": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "7c07d8c5-6f30-473f-90be-19146e25d0c8",
+ "reused": false,
+ "phase": "complete",
+ "seconds": 0.165026
+ },
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "7c07d8c5-6f30-473f-90be-19146e25d0c8",
+ "reused": true,
+ "phase": "complete",
+ "seconds": 0.064545
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "7c07d8c5-6f30-473f-90be-19146e25d0c8",
+ "reused": true,
+ "phase": "complete",
+ "seconds": 0.095174
+ },
+ "seconds": 0.1597440000041388,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "same_server_native_reuse_filter_ms": 19.670958979986608,
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:00:33.647722+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "mysql_version": "8.4.2",
+ "runtime_seconds": 9.550093834055588,
+ "seed_seconds": 0.41018587502185255,
+ "seed_checkpoint_seconds": 0.17138437496032566,
+ "owned_native_runtime_stopped": true
+ },
+ "100000": {
+ "native_receipt": "native-tag-sequence-pre-autocompletefix-100000.json",
+ "receipt_passed": false,
+ "source_unchanged": false,
+ "changed_during_run": {
+ "python/tests/test_workspace_path_api.py": {
+ "before": "4c586415a445d806ffe8d64d74f4d53d6d453be6fdcb2b4812da49f69a4525a2",
+ "after": "eb16d29e4e6656db8d47f6812cfa2a44c882a8624701ad5b590e85acfacf1388"
+ },
+ "python/workspace_api.py": {
+ "before": "6587dcb57e94489175dad26bbbfaa9142170f7eb1b947fe7b3becf40707e11cf",
+ "after": "bac9888aef2eac472e3278902f9285ed5651b0164fa1a76a665ab881c6927ee4"
+ },
+ "python/workspace_paths.py": {
+ "before": "a75c94ce4c2a5863fb2bf5d79c6abaf4304fb7aa90726d89d80a9e69114d5710",
+ "after": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab"
+ },
+ "python/workspace_projects.py": {
+ "before": "3f5ec51667618b9e0a215f6371dd22a9e1502afd54166da0efb9b677045d4216",
+ "after": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce"
+ }
+ },
+ "prior_receipt": "tag-sequence-after-100000.json",
+ "comparison_ms": {
+ "first10_save": {
+ "prior_p50": 17.622290994040668,
+ "native_p50": 20.763959037140012,
+ "native_first_sample": 44.37404201598838,
+ "native_maximum": 44.37404201598838
+ },
+ "second10_save": {
+ "prior_p50": 16.18437498109415,
+ "native_p50": 24.33695795480162,
+ "native_first_sample": 27.478916046675295,
+ "native_maximum": 27.478916046675295
+ },
+ "filter20": {
+ "prior_p50": 119.21920796157792,
+ "native_p50": 124.48295799549669,
+ "native_first_sample": 984.3111250083894,
+ "native_maximum": 984.3111250083894
+ },
+ "tag10_then10_filter20_http_total": {
+ "prior_p50": 165.9542090492323,
+ "native_p50": 217.5220000790432,
+ "native_first_sample": 1074.6557911043055,
+ "native_maximum": 1074.6557911043055
+ },
+ "cell_save": {
+ "prior_p50": 7.809958013240248,
+ "native_p50": 8.591292018536478,
+ "native_first_sample": 8.944916015025228,
+ "native_maximum": 8.944916015025228
+ },
+ "filter_cell100": {
+ "prior_p50": 129.82887495309114,
+ "native_p50": 156.82324999943376,
+ "native_first_sample": 156.82324999943376,
+ "native_maximum": 162.14675002265722
+ },
+ "filter_cell_and_epoch10": {
+ "prior_p50": 106.19708400918171,
+ "native_p50": 124.58279100246727,
+ "native_first_sample": 116.38466699514538,
+ "native_maximum": 130.77333301771432
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "prior_p50": 137.83216592855752,
+ "native_p50": 165.76816601445898,
+ "native_first_sample": 165.76816601445898,
+ "native_maximum": 171.01029201876372
+ }
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "checks": [
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 33334
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 66666
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 66667
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1070",
+ "ref": "const,const,const",
+ "rows": 20,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ }
+ ],
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY,by_tag",
+ "key": "PRIMARY",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 613093,
+ "filtered": 100.0,
+ "Extra": "Using index; Using filesort"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "PRIMARY",
+ "table": "",
+ "partitions": null,
+ "type": "ALL",
+ "possible_keys": null,
+ "key": null,
+ "key_len": null,
+ "ref": null,
+ "rows": 183927,
+ "filtered": 100.0,
+ "Extra": null
+ },
+ {
+ "id": 1,
+ "select_type": "PRIMARY",
+ "table": "a",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "111",
+ "ref": "const,func",
+ "rows": 1,
+ "filtered": 5.0,
+ "Extra": "Using where"
+ },
+ {
+ "id": 2,
+ "select_type": "DERIVED",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "PRIMARY,by_tag",
+ "key": "PRIMARY",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 613093,
+ "filtered": 30.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ }
+ ],
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "seconds": 5.976898541965056
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "seconds": 4.200603792036418
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s GROUP BY tag",
+ "seconds": 5.332792375003919
+ },
+ {
+ "sql": "SELECT picked.tag,a.profile_uuid,a.author_name FROM (SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY tag,profile_uuid) picked JOIN `recording_workspace`.`shared_annotation` a ON a.project_uuid=%s AND a.profile_uuid=picked.profile_uuid AND a.target_kind=SUBSTRING_INDEX(picked.position,'/',1) AND a.target_uuid=SUBSTRING_INDEX(picked.position,'/',-1)",
+ "seconds": 4.116656041005626
+ }
+ ]
+ },
+ "autocomplete_diagnostic_ms": {
+ "autocomplete_cold_diagnostic": 10202.606874983758,
+ "autocomplete_warm_diagnostic": 9474.194333015475
+ },
+ "initial_empty_native_setup": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "2aa92db9-6472-4c39-9292-5794937593db",
+ "reused": false,
+ "phase": "complete",
+ "seconds": 0.173335
+ },
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "2aa92db9-6472-4c39-9292-5794937593db",
+ "reused": true,
+ "phase": "complete",
+ "seconds": 0.068206
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {},
+ "project_uuid": "2aa92db9-6472-4c39-9292-5794937593db",
+ "reused": true,
+ "phase": "complete",
+ "seconds": 0.114146
+ },
+ "seconds": 0.1826643330277875,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "same_server_native_reuse_filter_ms": 143.8881250214763,
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:06:18.387002+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "mysql_version": "8.4.2",
+ "runtime_seconds": 212.61365483299596,
+ "seed_seconds": 115.08456225000555,
+ "seed_checkpoint_seconds": 14.813979834027123,
+ "owned_native_runtime_stopped": true
+ }
+ }
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-results.md b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-results.md
new file mode 100644
index 0000000..1874396
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-autocompletefix-results.md
@@ -0,0 +1,51 @@
+# Native SQL tag workflow validation
+
+The 500-epoch native SQL run passes. At 100,000 epochs all workflow correctness checks pass, but the overall receipt is invalidated by source changes during execution; its timings are provisional. The 100k run also exposes slow autocomplete (9–10 seconds), so these results do not support declaring the performance work complete. Neither run calls the legacy SQLite tag membership or refresh path.
+
+Each fixture has three profiles, five seeded tags per annotation, cell annotations, and two independent protocol curation scopes. The timed sequence reads revisions and durably tags 10 epochs, reads revisions and tags another 10 in another cell, then immediately filters the exact 20. Five complete cycles were measured.
+
+| Operation (median milliseconds) | 500 prior | 500 native | 100k prior | 100k native (provisional) |
+|---|---:|---:|---:|---:|
+| First 10 epochs: durable POST | 15.2 | 15.5 | 17.6 | 20.8 |
+| Next 10 epochs: durable POST | 13.4 | 15.7 | 16.2 | 24.3 |
+| First filter after the two saves: exactly 20 epochs | 31.1 | 21.9 | 119.2 | 124.5 |
+| Two revision reads + two saves + filter | 73.6 | 66.9 | 166.0 | 217.5 |
+| One cell: durable POST | 6.7 | 7.3 | 7.8 | 8.6 |
+| Cell filter: exactly 100 inherited epochs | 41.5 | 39.7 | 129.8 | 156.8 |
+| Cell AND epoch filter: exactly 10 epochs | 17.4 | 18.4 | 106.2 | 124.6 |
+| Cell save + cell filter | 48.1 | 47.0 | 137.8 | 165.8 |
+
+The total uses paired per-cycle timings and includes both revision reads. POST timing ends only after canonical MySQL save acknowledgment. The separately scheduled recovery mirror is flushed and checked after the operations. These are Flask HTTP client timings on a disposable native MySQL database, not browser interaction or paint timings.
+
+The native run performs no preparatory tag filtering or SQLite tag checkpoint after seeding. Its very first filter request took 38.0 ms at 500 epochs and 984.3 ms at 100k; the first full sequences took 105.9 and 1074.7 ms. “First” means the first HTTP filter after seeding, not a cold MySQL/OS cache. The prior SQLite receipts prepared their tag caches before timing. Five samples do not establish tail-latency guarantees.
+
+## Index, persistence and correctness
+
+The actual 20-epoch filter uses the persistent `by_tag` covering index with `ref` access and an estimate of 20 lookup rows at both scales. Both runs distinguish `QC` from `qc` and NFC `é` from decomposed `é`, and verify AND, OR, NOT, inherited cell membership, direct-only epoch scope, exact author chips and revisions. Cell tagging changes one canonical cell record and creates no child annotation copies; both protocol curation scopes remain unchanged.
+
+The lookup is checkpointed and reopened as a fresh Python adapter in the same server, reusing the persistent SQL table. This is not a MySQL process restart. The reopened exact-20 filter takes 19.7/143.9 ms at 500/100k. Initial setup timing in the raw receipts is empty-database schema/trigger setup before seeding, not a populated-database migration benchmark.
+
+Both final backup receipts are `current`, with no pending/running work or error, and requested/completed sequence 34. The flushed recovery mirror matches all 63 tracked canonical records, including final removals. Both owned native servers stopped successfully.
+
+## Autocomplete diagnostic
+
+Two untimed-workflow diagnostic calls request `q=gate&limit=12`, checking exact counts and profile authors. Each is a single observation; “cold/warm” labels mean first/repeated endpoint calls after the timed workflow, not cold/warm storage.
+
+| Scale | First endpoint ms | Repeated endpoint ms | Aggregate SQL first/repeated ms | Author SQL first/repeated ms |
+|---|---:|---:|---:|---:|
+| 500 | 25.7 | 28.5 | 6.4 / 7.2 | 2.8 / 2.8 |
+| 100,000 | 10202.6 | 9474.2 | 5976.9 / 5332.8 | 4200.6 / 4116.7 |
+
+At 100k, autocomplete scans the project-wide PRIMARY prefix (613,093 estimated rows) and filesorts for `COUNT(DISTINCT target_kind,target_uuid)` grouped by tag. The author subquery also selects PRIMARY project-prefix access instead of `by_tag`; the canonical join uses only project/kind (key length 111). These SQL operations account for essentially the entire 9–10 second endpoint latency. This remains a material responsiveness issue; it is separate from the indexed point-tag query. The raw receipts preserve actual SQL, timings and EXPLAIN output.
+
+## Reproducible evidence
+
+- [500-epoch native receipt](native-tag-sequence-pre-autocompletefix-500.json) and [100,000-epoch native receipt](native-tag-sequence-pre-autocompletefix-100000.json).
+- [Prior 500-epoch receipt](tag-sequence-after-500.json) and [prior 100,000-epoch receipt](tag-sequence-after-100000.json).
+- [Native harness](benchmark_native_tag_sequence.py) and [machine-readable comparison](native-tag-sequence-pre-autocompletefix-results.json).
+
+The 500-epoch source and harness inventories are unchanged. The 100k harness is unchanged, but `workspace_api.py`, `workspace_paths.py`, `workspace_projects.py`, and `tests/test_workspace_path_api.py` changed during execution. `tests/test_workspace_root_boundaries.py` also changed between scales. The raw receipt deliberately remains `passed: false`; its benchmark results must not be promoted to final unchanged-source evidence. Full before/after SHA-256 inventories and loaded-module paths are preserved.
+
+Native harness SHA-256: `a2b5e70861826936fdb4c9d6a5c76efd7645486ad5ce05e1482e3e6aab694fde`.
+
+These receipts validate this workflow and its stated checks; they do not certify a complete desktop release.
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-100000.json b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-100000.json
new file mode 100644
index 0000000..c48255f
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-100000.json
@@ -0,0 +1,3463 @@
+{
+ "scope": "Persistent native tag lookup: tag-ten, tag-another-ten, filter-twenty and cell/epoch hierarchy.\n\nEvery project/database is disposable. Acquisition metadata is synthetic; actual\nMySQL, triggers, Flask routes, derived indexes and each version's save hooks\nare used. Native SQL commits are checked immediately; any background recovery\nmirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude\noracle work, and do not model browser concurrency/rendering. Sequential bundle\ntimes are sums of their separately measured requests. Profiles are separate.\n",
+ "epochs": 100000,
+ "profiles": 3,
+ "tags_per_annotation": 5,
+ "protocols": 2,
+ "samples": 5,
+ "source_root": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9",
+ "source_inventory_before": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_concurrent_requests.py": "f074a762536ab5bb52abe4ea56faada41babb1cd765512acb3caba18dd503648",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "17c37104693d46b416064ab05027c54added8d26ddd2ba6b7de202911ce5d592",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_event_paging.py": "e79cd1de3bd7469818e255cc01f087a608c11e799a48e81e4ca0ac29ff7a578e",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "3f4b346a59e1b78aeea51b1d1c2812582eed009b4cb2cc36831830e0d62601ca",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "f852f8af8b9795c63772800c3ca3cb7203fa0a1b81e0ef562ef848c1e31a06b0",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7fe035916c152aad2df6f41cc6f5c55e8d30cb4168f53c4ab1aa9e171bdd7cfc",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6d89f8a735c34e6ec3f7f3db291e5d21c1bd8e64a60e7b12563e82dc18eb9f24",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "8efc55e5fb6079072014c616223872464c0451c6d85bfcb2c61693f9baaed1ff",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "66a6a04c107053b3c0492e5ccd2e6f6e10b8479c107c0b58d5b5fd712800312c",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "a43e66ff47aaec64d0a862277b7751901775ab7f193f58c0106437b9fab1331b",
+ "python/workspace_native_tag_lookup.py": "d549e8084af074ab484759a2851fdacde7e632c334f55e2d65caee368e04ffd5",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "40ba54acf2c5e9983c2e2766179a3d365b30ad1cdc4955410571936f7dfb4954",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "1917ae5b36553048027a19e1b68f99677a754dae1501d1e52be1cd3faaa2b4a5",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "fixture_module": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py",
+ "harness_sha256": "468173271c08020e7e1a7ce8b0fcdfc59693be0f9e3699a8caec342b2e77affd",
+ "started_at": "2026-09-30T15:17:46.744505+00:00",
+ "release_ready": false,
+ "operations": {
+ "first10_revision_preflight": {
+ "samples_seconds": [
+ 0.01199199998518452,
+ 0.04156083398265764,
+ 0.007461917004548013,
+ 0.008337290957570076,
+ 0.006807041994761676
+ ],
+ "p50_seconds": 0.008337290957570076,
+ "p95_seconds": 0.04156083398265764,
+ "p99_seconds": 0.04156083398265764,
+ "maximum_seconds": 0.04156083398265764,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ }
+ ]
+ },
+ "first10_save": {
+ "samples_seconds": [
+ 0.05614608299219981,
+ 0.02782075002323836,
+ 0.02499641600297764,
+ 0.02187658299226314,
+ 0.01975841698003933
+ ],
+ "p50_seconds": 0.02499641600297764,
+ "p95_seconds": 0.05614608299219981,
+ "p99_seconds": 0.05614608299219981,
+ "maximum_seconds": 0.05614608299219981,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 18600,
+ "Questions": 36
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12424,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ }
+ ]
+ },
+ "second10_revision_preflight": {
+ "samples_seconds": [
+ 0.008524707984179258,
+ 0.006244334043003619,
+ 0.023840208014007658,
+ 0.005883166042622179,
+ 0.026213333010673523
+ ],
+ "p50_seconds": 0.008524707984179258,
+ "p95_seconds": 0.026213333010673523,
+ "p99_seconds": 0.026213333010673523,
+ "maximum_seconds": 0.026213333010673523,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ }
+ ]
+ },
+ "second10_save": {
+ "samples_seconds": [
+ 0.023883083020336926,
+ 0.02518754097400233,
+ 0.02145470801042393,
+ 0.026714917039498687,
+ 0.019584666995797306
+ ],
+ "p50_seconds": 0.023883083020336926,
+ "p95_seconds": 0.026714917039498687,
+ "p99_seconds": 0.026714917039498687,
+ "maximum_seconds": 0.026714917039498687,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12436,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 273985,
+ "Questions": 155
+ }
+ ]
+ },
+ "filter20": {
+ "samples_seconds": [
+ 0.22716829099226743,
+ 0.12525804102187976,
+ 0.1252272499841638,
+ 0.13001949997851625,
+ 0.12601441703736782
+ ],
+ "p50_seconds": 0.12601441703736782,
+ "p95_seconds": 0.22716829099226743,
+ "p99_seconds": 0.22716829099226743,
+ "maximum_seconds": 0.22716829099226743,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 141284,
+ "Questions": 64
+ },
+ {
+ "Bytes_sent": 134745,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134745,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134745,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134745,
+ "Questions": 57
+ }
+ ]
+ },
+ "cell_save": {
+ "samples_seconds": [
+ 0.010468583030160517,
+ 0.010496834001969546,
+ 0.008183291996829212,
+ 0.008193165995180607,
+ 0.008758000039961189
+ ],
+ "p50_seconds": 0.008758000039961189,
+ "p95_seconds": 0.010496834001969546,
+ "p99_seconds": 0.010496834001969546,
+ "maximum_seconds": 0.010496834001969546,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4166,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_cell100": {
+ "samples_seconds": [
+ 0.15443866699934006,
+ 0.16119712503859773,
+ 0.1600392499822192,
+ 0.1468521250062622,
+ 0.16326100000878796
+ ],
+ "p50_seconds": 0.1600392499822192,
+ "p95_seconds": 0.16326100000878796,
+ "p99_seconds": 0.16326100000878796,
+ "maximum_seconds": 0.16326100000878796,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168054,
+ "Questions": 57
+ }
+ ]
+ },
+ "filter_cell_and_epoch10": {
+ "samples_seconds": [
+ 0.12339312495896593,
+ 0.13643654203042388,
+ 0.12478045799070969,
+ 0.12551662500482053,
+ 0.12105583405354992
+ ],
+ "p50_seconds": 0.12478045799070969,
+ "p95_seconds": 0.13643654203042388,
+ "p99_seconds": 0.13643654203042388,
+ "maximum_seconds": 0.13643654203042388,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125370,
+ "Questions": 58
+ }
+ ]
+ },
+ "remove_first10_revision_preflight": {
+ "samples_seconds": [
+ 0.006872707977890968,
+ 0.006685125001240522,
+ 0.006732042005751282,
+ 0.006497750000562519,
+ 0.006534791027661413
+ ],
+ "p50_seconds": 0.006685125001240522,
+ "p95_seconds": 0.006872707977890968,
+ "p99_seconds": 0.006872707977890968,
+ "maximum_seconds": 0.006872707977890968,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_first10_save": {
+ "samples_seconds": [
+ 0.022132374986540526,
+ 0.024739916960243136,
+ 0.021291458979249,
+ 0.019143666955642402,
+ 0.02145708299940452
+ ],
+ "p50_seconds": 0.02145708299940452,
+ "p95_seconds": 0.024739916960243136,
+ "p99_seconds": 0.024739916960243136,
+ "maximum_seconds": 0.024739916960243136,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12424,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_hierarchy_after_epoch_remove": {
+ "samples_seconds": [
+ 0.11069879098795354,
+ 0.10966495797038078,
+ 0.10294529102975503,
+ 0.13921783299883828,
+ 0.10848212498240173
+ ],
+ "p50_seconds": 0.10966495797038078,
+ "p95_seconds": 0.13921783299883828,
+ "p99_seconds": 0.13921783299883828,
+ "maximum_seconds": 0.13921783299883828,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112902,
+ "Questions": 55
+ }
+ ]
+ },
+ "filter_remaining10": {
+ "samples_seconds": [
+ 0.11796779197175056,
+ 0.11772724997717887,
+ 0.1194367500138469,
+ 0.12223120802082121,
+ 0.15665787504985929
+ ],
+ "p50_seconds": 0.1194367500138469,
+ "p95_seconds": 0.15665787504985929,
+ "p99_seconds": 0.15665787504985929,
+ "maximum_seconds": 0.15665787504985929,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124605,
+ "Questions": 57
+ }
+ ]
+ },
+ "remove_second10_revision_preflight": {
+ "samples_seconds": [
+ 0.01008412498049438,
+ 0.010636958002578467,
+ 0.00979362498037517,
+ 0.00988920801319182,
+ 0.010092334006913006
+ ],
+ "p50_seconds": 0.01008412498049438,
+ "p95_seconds": 0.010636958002578467,
+ "p99_seconds": 0.010636958002578467,
+ "maximum_seconds": 0.010636958002578467,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_second10_save": {
+ "samples_seconds": [
+ 0.023356417019385844,
+ 0.021738334035035223,
+ 0.018369458965025842,
+ 0.020638792018871754,
+ 0.019545999995898455
+ ],
+ "p50_seconds": 0.020638792018871754,
+ "p95_seconds": 0.023356417019385844,
+ "p99_seconds": 0.023356417019385844,
+ "maximum_seconds": 0.023356417019385844,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12436,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_after_all_epoch_removals": {
+ "samples_seconds": [
+ 0.10815216699847952,
+ 0.10508224996738136,
+ 0.1046494580223225,
+ 0.10758879204513505,
+ 0.1083835830213502
+ ],
+ "p50_seconds": 0.10758879204513505,
+ "p95_seconds": 0.1083835830213502,
+ "p99_seconds": 0.1083835830213502,
+ "maximum_seconds": 0.1083835830213502,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ }
+ ]
+ },
+ "remove_cell_save": {
+ "samples_seconds": [
+ 0.009546625020448118,
+ 0.008671040995977819,
+ 0.008816290996037424,
+ 0.008307332987897098,
+ 0.009664583019912243
+ ],
+ "p50_seconds": 0.008816290996037424,
+ "p95_seconds": 0.009664583019912243,
+ "p99_seconds": 0.009664583019912243,
+ "maximum_seconds": 0.009664583019912243,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4166,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_after_cell_removal": {
+ "samples_seconds": [
+ 0.1054909999947995,
+ 0.10503399994922802,
+ 0.10871112497989088,
+ 0.10642904101405293,
+ 0.10895304195582867
+ ],
+ "p50_seconds": 0.10642904101405293,
+ "p95_seconds": 0.10895304195582867,
+ "p99_seconds": 0.10895304195582867,
+ "maximum_seconds": 0.10895304195582867,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112145,
+ "Questions": 54
+ }
+ ]
+ },
+ "tag10_then10_filter20_http_total": {
+ "samples_seconds": [
+ 0.32771416497416794,
+ 0.22607150004478171,
+ 0.20298049901612103,
+ 0.19283145701047033,
+ 0.19837787601863965
+ ],
+ "p50_seconds": 0.20298049901612103,
+ "p95_seconds": 0.32771416497416794,
+ "p99_seconds": 0.32771416497416794,
+ "maximum_seconds": 0.32771416497416794,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "samples_seconds": [
+ 0.16490725002950057,
+ 0.17169395904056728,
+ 0.1682225419790484,
+ 0.15504529100144282,
+ 0.17201900004874915
+ ],
+ "p50_seconds": 0.1682225419790484,
+ "p95_seconds": 0.17201900004874915,
+ "p99_seconds": 0.17201900004874915,
+ "maximum_seconds": 0.17201900004874915,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "first10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.06813808297738433,
+ 0.069381584005896,
+ 0.03245833300752565,
+ 0.030213873949833214,
+ 0.026565458974801004
+ ],
+ "p50_seconds": 0.03245833300752565,
+ "p95_seconds": 0.069381584005896,
+ "p99_seconds": 0.069381584005896,
+ "maximum_seconds": 0.069381584005896,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "second10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.032407791004516184,
+ 0.03143187501700595,
+ 0.045294916024431586,
+ 0.032598083082120866,
+ 0.04579800000647083
+ ],
+ "p50_seconds": 0.032598083082120866,
+ "p95_seconds": 0.04579800000647083,
+ "p99_seconds": 0.04579800000647083,
+ "maximum_seconds": 0.04579800000647083,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "autocomplete_cold_diagnostic": {
+ "samples_seconds": [
+ 3.740355083020404
+ ],
+ "p50_seconds": 3.740355083020404,
+ "p95_seconds": 3.740355083020404,
+ "p99_seconds": 3.740355083020404,
+ "maximum_seconds": 3.740355083020404,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 215741,
+ "Questions": 91
+ }
+ ]
+ },
+ "autocomplete_warm_diagnostic": {
+ "samples_seconds": [
+ 3.255884749989491
+ ],
+ "p50_seconds": 3.255884749989491,
+ "p95_seconds": 3.255884749989491,
+ "p99_seconds": 3.255884749989491,
+ "maximum_seconds": 3.255884749989491,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 215741,
+ "Questions": 91
+ }
+ ]
+ },
+ "autocomplete_empty_diagnostic": {
+ "samples_seconds": [
+ 11.217491208051797
+ ],
+ "p50_seconds": 11.217491208051797,
+ "p95_seconds": 11.217491208051797,
+ "p99_seconds": 11.217491208051797,
+ "maximum_seconds": 11.217491208051797,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 219196,
+ "Questions": 91
+ }
+ ]
+ },
+ "same_server_native_reuse_filter20": {
+ "samples_seconds": [
+ 0.13854400004493073
+ ],
+ "p50_seconds": 0.13854400004493073,
+ "p95_seconds": 0.13854400004493073,
+ "p99_seconds": 0.13854400004493073,
+ "maximum_seconds": 0.13854400004493073,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 134746,
+ "Questions": 57
+ }
+ ]
+ }
+ },
+ "checks": [
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 33334
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 66666
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 66667
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "page_cells": true,
+ "target_specification": "tag10_then10_filter20_and_cell_epoch_hierarchy",
+ "baseline_harness_sha256": "272fe050c8762ed4ea45bd013e236e8723d8fe46f5e7e293c58e2edf1fd52ddc",
+ "mysql_version": "8.4.2",
+ "seed_seconds": 127.78472875000443,
+ "seed_checkpoint_seconds": 13.33677566703409,
+ "native_lookup_startup_preparation": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 1.488408
+ }
+ },
+ "project_uuid": "3a791e27-101f-4a82-a7e8-f8be38297694",
+ "reused": false,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 1.672049
+ },
+ "selected_epoch_uuids": {
+ "first10": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5"
+ ],
+ "second10": [
+ "ad573668-6512-6e55-649e-cb23ae1d4888",
+ "16dc368a-89b4-28b2-4854-84313ba67a39",
+ "37834f2f-2576-2f23-e1f7-4a531cbe445d",
+ "454f63ac-30c8-3229-97ef-025edff6abd2",
+ "5ef6fdf3-2513-aa7c-d11f-72beccf132b9",
+ "1253e937-3e78-1b75-0026-6caa55150e08",
+ "482d9673-cfee-5de3-91f9-7fde4d1c84f9",
+ "3346f2bb-f6c3-4bd2-dbe2-8bd1bb657d0e",
+ "9537f32e-c759-9e1a-e953-af6c9f929fe7",
+ "0fd42b3f-73c4-48b3-4940-b339f87d07ad"
+ ],
+ "cell": "3bb78535-cc95-55ff-19fe-3556aaa41c78",
+ "cell_children": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5",
+ "4a44dc15-3642-04a8-0fe8-0e9039455cc1",
+ "4fc82b26-aecb-47d2-868c-4efbe3581732",
+ "6b51d431-df5d-7f14-1cbe-ceccf79edf3d",
+ "3fdba35f-04dc-8c46-2986-c992bcf87554",
+ "8527a891-e224-1369-50ff-32ca212b45bc",
+ "e629fa65-98d7-3276-8f7c-726b4b621285",
+ "b17ef6d1-9c7a-5b1e-e83b-907c595526dc",
+ "4523540f-1504-cd17-100c-4835e85b7eef",
+ "4ec9599f-c203-d176-a301-536c2e091a19",
+ "9400f1b2-1cb5-27d7-fa3d-3eabba93557a",
+ "f5ca38f7-48a1-d6ea-f726-b8a42fb575c3",
+ "6f4b6612-125f-b3a0-daec-d2799dfd6c9c",
+ "785f3ec7-eb32-f30b-90cd-0fcf3657d388",
+ "535fa30d-7e25-dd8a-49f1-536779734ec8",
+ "c2356069-e9d1-e79c-a924-378153cfbbfb",
+ "b7a56873-cd77-1f2c-446d-369b649430b6",
+ "5f9c4ab0-8cac-7457-e911-1a30e4664920",
+ "670671cd-9740-4156-226e-507973f2ab83",
+ "59e19706-d51d-39f6-6711-c2653cd7eb12",
+ "35135aaa-6cc2-3891-b40c-b3f378c53a17",
+ "624b60c5-8c9d-8bfb-6ff1-886c2fd605d2",
+ "eb1e33e8-a81b-697b-7585-5af6bfcdbcbf",
+ "e29c9c18-0c62-79b0-b02a-bd6a1801c7c0",
+ "c6f3ac57-944a-5314-90cd-39902d0f7777",
+ "86e50149-6586-6131-2a9e-0b35558d84f6",
+ "9f14025a-f006-5b30-e47e-23ebb3b491d3",
+ "76a50887-d8f1-c2e9-3017-55428990ad81",
+ "7a61b537-01be-fdae-0eee-ffaecc73f14e",
+ "aea92132-c4cb-eb26-3e6a-c2bf6c183b5d",
+ "0b918943-df09-62bc-7a18-24c0555a3893",
+ "d59eced1-ded0-7f84-c145-592f65bdf854",
+ "3d914f93-48c9-cc0f-f8a7-9716700b9fcd",
+ "73475cb4-0a56-8e8d-a8a0-45ced110137e",
+ "44cb730c-4204-80a0-477b-505ae68af508",
+ "71ee45a3-c0db-9a98-65f7-313dd3372cf6",
+ "811786ad-1ae7-4adf-dd20-dd0372abaaeb",
+ "25fc0e70-96fc-6537-1820-2dc30b0c580b",
+ "31489056-e091-6d59-fe3a-dd79e63f095a",
+ "98010bd9-270f-9b10-0b62-14a21754fd33",
+ "0e17daca-5f3e-175f-448b-acace3bc0da4",
+ "1a656259-0ef1-9d10-45d0-6c4055742d38",
+ "031b4af5-197e-c30a-926f-48cf40e11a7d",
+ "41cfc0d1-f2d1-27b0-4555-b7246d84019b",
+ "2858dcd1-057d-3eae-7f7d-5f782167e24b",
+ "2fca346d-b656-1871-02ce-806ac732e06a",
+ "02d20bbd-7e39-4ad5-999a-4cebabac9619",
+ "7688b6ef-5255-5962-d008-fff894223582",
+ "c837649c-ce43-f272-9138-e72cc3152070",
+ "6208ef0f-7750-c111-548c-f90b6ea1d0d0",
+ "3e1e967e-9b79-3e90-8f8e-ae83c74dba9b",
+ "39fa9ec1-90ee-e7b6-f4df-f1100d6343e1",
+ "d029fa3a-95e1-74a1-9934-857f535eb942",
+ "81b8a03f-97e8-787c-53fe-1a86bda042b6",
+ "da4ea2a5-506f-2693-eae1-90d9360a1f31",
+ "a68b412c-4282-555f-1554-6cf6e1fc4289",
+ "108c995b-953c-8a35-5611-03e2014cf828",
+ "3ada92f2-8b4c-eda3-8562-ebf047c6ff05",
+ "49d180ec-f561-3281-9571-bf39d9b7b342",
+ "a21855da-08cb-102d-1d21-7c53dc5824a3",
+ "c75cb66a-e28d-8ebc-6ede-d002c28a8ba0",
+ "ff5a1ae0-12af-a5d4-c889-c50ad427aaf5",
+ "7f2253d7-e228-b22a-08bd-a1f09c516f6f",
+ "87226162-0421-7edd-b39e-7df969e0698a",
+ "96061e92-f58e-4bdc-dee7-3df36183fe3a",
+ "eb624dbe-56eb-6620-ae62-080c10a273ca",
+ "f369cb89-fc62-7e66-8987-007d121ed1ea",
+ "f74efabe-f12e-a619-e30b-79bddef89cff",
+ "a88a7902-cb4e-f697-ba0b-6759c50e8c10",
+ "349c4120-1b62-db85-1192-665c504b350f",
+ "98a3ab7c-340e-8a03-3e7b-37b6ef942875",
+ "48449a14-a4ff-7d79-bb7a-1b6f3d488eba",
+ "5316ca1c-5ddc-a8e6-cecc-fce58f3b8540",
+ "a46e3763-2fa6-ca51-a13f-e39a567b3c23",
+ "bbb965ab-0c80-d653-8cf2-184babad2a56",
+ "44c8031c-b036-a735-0d8b-9b8603af662a",
+ "b4944c6f-f08d-c6f4-3da2-e9c824669b7d",
+ "434c9b5a-e514-646b-bd91-b50032ca579e",
+ "bdd2d3af-3a5a-1213-497d-4f1f7bfcda89",
+ "8b940be7-fb78-aaa6-b656-7dd7a3987996",
+ "cd70bea0-23f7-52a0-564a-bb6ed08d42c1",
+ "69f59c27-3b6e-669a-c32a-6dd5e1b2cb63",
+ "1da51b8d-8ff9-8f6a-48f8-0ae79fe3ca6c",
+ "82416496-09f8-8ccd-2a0a-5b233a07a538",
+ "6e400187-1c0c-f27c-7634-ef1dc478408f",
+ "e3d6c4d4-599e-0088-2384-ca981ee287ed",
+ "ad48ff99-415b-2f00-7dc3-5b7eb553fd1e",
+ "7b1a278f-5abe-8e9d-a907-fc9c29dfd432",
+ "d6d824ab-ba4a-fde8-1129-c71dea75b810",
+ "29db0c67-82db-d500-0559-ef4d9e953e30",
+ "8c1f1046-219d-dd21-6a02-3f792356ddf1"
+ ]
+ },
+ "backup_receipts": [
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 1,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 2,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 3,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 4,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 5,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 6,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 7,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 8,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 9,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 10,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 11,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 12,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 13,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 14,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 15,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 16,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 17,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 18,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 19,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 20,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 21,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 22,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 23,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 24,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 25,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 12,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:48.778388+00:00",
+ "pending": true,
+ "requested_sequence": 26,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 26,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:54.339162+00:00",
+ "pending": true,
+ "requested_sequence": 27,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 26,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:54.339162+00:00",
+ "pending": true,
+ "requested_sequence": 28,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 26,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:54.339162+00:00",
+ "pending": true,
+ "requested_sequence": 29,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 26,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:54.339162+00:00",
+ "pending": true,
+ "requested_sequence": 30,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:56.802369+00:00",
+ "pending": true,
+ "requested_sequence": 31,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:20:56.802369+00:00",
+ "pending": true,
+ "requested_sequence": 32,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_first10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:21:15.996011+00:00",
+ "pending": true,
+ "requested_sequence": 33,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_second10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:21:15.996011+00:00",
+ "pending": true,
+ "requested_sequence": 34,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ }
+ ],
+ "preparation_index_seconds": 0,
+ "lookup_mode": "persistent_native_sql_no_post_seed_tag_cache_preparation",
+ "bundle_definitions": {
+ "tag10_then10_filter20_http_total": [
+ "first10_revision_preflight",
+ "first10_save",
+ "second10_revision_preflight",
+ "second10_save",
+ "filter20"
+ ],
+ "cell_tag_then_cell_filter_http_total": [
+ "cell_save",
+ "filter_cell100"
+ ],
+ "first10_selected_action_http_total": [
+ "first10_revision_preflight",
+ "first10_save"
+ ],
+ "second10_selected_action_http_total": [
+ "second10_revision_preflight",
+ "second10_save"
+ ]
+ },
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:21:20.833735+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 3.526624999998603
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.08437079098075628
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.10236137500032783
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0004254169762134552
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 3.0357274590060115
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.09343941701808944
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.1018385419738479
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0004039999912492931
+ }
+ ],
+ "autocomplete_empty_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 2.991644333000295
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 4.095707458036486
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 4.103170584014151
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0005604579928331077
+ }
+ ]
+ },
+ "autocomplete_requests": {
+ "autocomplete_cold_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_warm_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_empty_diagnostic": {
+ "q": "",
+ "limit": 12,
+ "total": 41
+ }
+ },
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 827077,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 204390,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 204390,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1108",
+ "ref": null,
+ "rows": 2951014,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index for group-by (scanning)"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "index",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1108",
+ "ref": null,
+ "rows": 1654154,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 12,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ }
+ ],
+ "filter20_cpu_profile": {
+ "top_functions": [
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 331,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.6042000000000004e-05,
+ "cumulative_seconds": 0.338109625
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 159,
+ "function": "request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.333e-06,
+ "cumulative_seconds": 0.3380305
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1163,
+ "function": "get",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.6660000000000005e-06,
+ "cumulative_seconds": 0.337179375
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/testing.py",
+ "line": 204,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.3209e-05,
+ "cumulative_seconds": 0.33717370900000004
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1062,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.164000000000001e-06,
+ "cumulative_seconds": 0.33688270800000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 984,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0458e-05,
+ "cumulative_seconds": 0.336865792
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1230,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.958e-06,
+ "cumulative_seconds": 0.336662834
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1529,
+ "function": "__call__",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.625e-06,
+ "cumulative_seconds": 0.33663787500000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1479,
+ "function": "wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.2791000000000003e-05,
+ "cumulative_seconds": 0.33663125000000005
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 904,
+ "function": "full_dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.124e-06,
+ "cumulative_seconds": 0.33631266600000004
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 879,
+ "function": "dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00011029200000000001,
+ "cumulative_seconds": 0.33607037500000003
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 716,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00012362900000000002,
+ "cumulative_seconds": 0.335955334
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 27,
+ "calls": 38,
+ "self_seconds": 3.4711e-05,
+ "cumulative_seconds": 0.153796711
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 249,
+ "function": "native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.4042e-05,
+ "cumulative_seconds": 0.153727834
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 268,
+ "function": "_native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.3998e-05,
+ "cumulative_seconds": 0.136067916
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 311,
+ "function": "native_context",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 5.1705e-05,
+ "cumulative_seconds": 0.135981292
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 207,
+ "function": "_metadata_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.009798792,
+ "cumulative_seconds": 0.13164345800000002
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 748,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.0008182500000000001,
+ "cumulative_seconds": 0.12887279100000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 778,
+ "function": "_epoch_page_identities",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.007877548,
+ "cumulative_seconds": 0.12800725000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 22,
+ "calls": 4058,
+ "self_seconds": 0.0012614710000000001,
+ "cumulative_seconds": 0.12213162500000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 212,
+ "function": "",
+ "primitive_calls": 18,
+ "calls": 18,
+ "self_seconds": 2.7666000000000002e-05,
+ "cumulative_seconds": 0.121835333
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 55,
+ "function": "_epoch_query_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.09261957500000001,
+ "cumulative_seconds": 0.115141374
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 261,
+ "function": "filter_epoch_ids",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.019374498,
+ "cumulative_seconds": 0.098126458
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 132,
+ "function": "__enter__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 2.376e-06,
+ "cumulative_seconds": 0.080341042
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 141,
+ "function": "__exit__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 8.042e-06,
+ "cumulative_seconds": 0.073394875
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 8,
+ "function": "matching",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.045571507000000004,
+ "cumulative_seconds": 0.068809667
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 322,
+ "function": "traced_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000242801,
+ "cumulative_seconds": 0.035812709000000005
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 412,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000141084,
+ "cumulative_seconds": 0.035118955
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 402,
+ "function": "_execute_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00012974700000000002,
+ "cumulative_seconds": 0.034778042
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 150,
+ "function": "execute",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.379500000000001e-05,
+ "cumulative_seconds": 0.034351083000000004
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 332,
+ "function": "_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.8946e-05,
+ "cumulative_seconds": 0.033850826
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 575,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 9.021300000000001e-05,
+ "cumulative_seconds": 0.033317961
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 851,
+ "function": "_read_query_result",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.0452e-05,
+ "cumulative_seconds": 0.031795709000000005
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1288,
+ "function": "read",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 8.0127e-05,
+ "cumulative_seconds": 0.031705006
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 295,
+ "function": "for_epochs",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.000124575,
+ "cumulative_seconds": 0.026587167000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 535040,
+ "calls": 535040,
+ "self_seconds": 0.0250952,
+ "cumulative_seconds": 0.0250952
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 763,
+ "function": "_read_packet",
+ "primitive_calls": 765,
+ "calls": 765,
+ "self_seconds": 0.0012484330000000002,
+ "cumulative_seconds": 0.022685774000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 813,
+ "function": "_read_bytes",
+ "primitive_calls": 1530,
+ "calls": 1530,
+ "self_seconds": 0.000573005,
+ "cumulative_seconds": 0.019514024
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 869,
+ "function": "to_dicts",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 2.8583e-05,
+ "cumulative_seconds": 0.019235625000000003
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 1530,
+ "calls": 1530,
+ "self_seconds": 0.00022469000000000002,
+ "cumulative_seconds": 0.018863232
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/socket.py",
+ "line": 704,
+ "function": "readinto",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 8.7658e-05,
+ "cumulative_seconds": 0.018638542
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1299,
+ "function": "recv_into",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 8.548900000000001e-05,
+ "cumulative_seconds": 0.018509461
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1157,
+ "function": "read",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 4.6499e-05,
+ "cumulative_seconds": 0.018414708000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 0.018363831,
+ "cumulative_seconds": 0.018363831
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 101,
+ "function": "_rows",
+ "primitive_calls": 46,
+ "calls": 46,
+ "self_seconds": 6.0753000000000006e-05,
+ "cumulative_seconds": 0.017847586000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 197,
+ "function": "response_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.8416e-05,
+ "cumulative_seconds": 0.0175995
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 233,
+ "function": "_attest_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 6.2878e-05,
+ "cumulative_seconds": 0.017564833000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/__init__.py",
+ "line": 138,
+ "function": "jsonify",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0375000000000001e-05,
+ "cumulative_seconds": 0.017006875
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 189,
+ "function": "response",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.7917e-05,
+ "cumulative_seconds": 0.016991125000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 1231,
+ "function": "cursor",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 2.1545e-05,
+ "cumulative_seconds": 0.016985083
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 61,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.4667000000000004e-05,
+ "cumulative_seconds": 0.016911167
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 128,
+ "function": "deepcopy",
+ "primitive_calls": 78,
+ "calls": 17170,
+ "self_seconds": 0.009457589,
+ "cumulative_seconds": 0.016118456
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 201,
+ "function": "_deepcopy_list",
+ "primitive_calls": 62,
+ "calls": 102,
+ "self_seconds": 0.000297197,
+ "cumulative_seconds": 0.015986214000000002
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 62,
+ "function": "browser_safe",
+ "primitive_calls": 1,
+ "calls": 9641,
+ "self_seconds": 0.003125787,
+ "cumulative_seconds": 0.015383334
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 66,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1363,
+ "self_seconds": 0.011160961,
+ "cumulative_seconds": 0.015381166000000002
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 2,
+ "calls": 102,
+ "self_seconds": 0.000217979,
+ "cumulative_seconds": 0.015366791000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 227,
+ "function": "_deepcopy_dict",
+ "primitive_calls": 1242,
+ "calls": 1284,
+ "self_seconds": 0.002889378,
+ "cumulative_seconds": 0.014004211
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1370,
+ "function": "_read_result_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.5784e-05,
+ "cumulative_seconds": 0.012644917
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 845,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.011560791,
+ "cumulative_seconds": 0.011560791
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 34,
+ "function": "_exact_equal",
+ "primitive_calls": 22,
+ "calls": 14072,
+ "self_seconds": 0.0036541620000000003,
+ "cumulative_seconds": 0.010249251000000001
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 134,
+ "function": "_active_ddl_guard",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 3.4085e-05,
+ "cumulative_seconds": 0.009855583000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1416,
+ "function": "_read_rowdata_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00036018300000000003,
+ "cumulative_seconds": 0.009693584
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 797,
+ "function": "",
+ "primitive_calls": 100001,
+ "calls": 100001,
+ "self_seconds": 0.009644538000000001,
+ "cumulative_seconds": 0.009644538000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 173,
+ "function": "_rows",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.5997e-05,
+ "cumulative_seconds": 0.009355959
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 101,
+ "function": "selected",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.4914000000000002e-05,
+ "cumulative_seconds": 0.0076009580000000005
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1429,
+ "function": "_read_row_from_packet",
+ "primitive_calls": 428,
+ "calls": 428,
+ "self_seconds": 0.000937469,
+ "cumulative_seconds": 0.007531751000000001
+ },
+ {
+ "file": "python/workspace_curation.py",
+ "line": 170,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.334e-06,
+ "cumulative_seconds": 0.007453250000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 42,
+ "function": "",
+ "primitive_calls": 6012,
+ "calls": 16026,
+ "self_seconds": 0.004263462,
+ "cumulative_seconds": 0.007246141
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 100027,
+ "calls": 100027,
+ "self_seconds": 0.005727994,
+ "cumulative_seconds": 0.005727994
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 205717,
+ "calls": 205719,
+ "self_seconds": 0.005396692000000001,
+ "cumulative_seconds": 0.005397149
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 21,
+ "calls": 21,
+ "self_seconds": 0.004913249000000001,
+ "cumulative_seconds": 0.004913249000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/converters.py",
+ "line": 166,
+ "function": "convert_datetime",
+ "primitive_calls": 144,
+ "calls": 144,
+ "self_seconds": 0.004578714,
+ "cumulative_seconds": 0.0048093780000000004
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 100008,
+ "calls": 100008,
+ "self_seconds": 0.004321989,
+ "cumulative_seconds": 0.004321989
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 38,
+ "function": "walk",
+ "primitive_calls": 1,
+ "calls": 2,
+ "self_seconds": 0.001046209,
+ "cumulative_seconds": 0.00395225
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 9.625e-06,
+ "cumulative_seconds": 0.0035925830000000003
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 44,
+ "function": "",
+ "primitive_calls": 2040,
+ "calls": 2040,
+ "self_seconds": 0.000445773,
+ "cumulative_seconds": 0.0034728270000000004
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 107,
+ "function": "_ddl",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 3.1046e-05,
+ "cumulative_seconds": 0.0033254160000000003
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 125,
+ "function": "_observer_status",
+ "primitive_calls": 8,
+ "calls": 8,
+ "self_seconds": 3.1999e-05,
+ "cumulative_seconds": 0.003297042
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 106471,
+ "calls": 106471,
+ "self_seconds": 0.003258059,
+ "cumulative_seconds": 0.003258059
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1448,
+ "function": "_get_descriptions",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000333447,
+ "cumulative_seconds": 0.0028627550000000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 185,
+ "function": "_metadata",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.4043000000000005e-05,
+ "cumulative_seconds": 0.002859917
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 0.002604916,
+ "cumulative_seconds": 0.002604916
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 163,
+ "function": "read_length_coded_string",
+ "primitive_calls": 2712,
+ "calls": 2712,
+ "self_seconds": 0.000841616,
+ "cumulative_seconds": 0.002495883
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 115,
+ "function": "_triggers",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.333e-06,
+ "cumulative_seconds": 0.0024841250000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 898,
+ "function": "",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 6.1905e-05,
+ "cumulative_seconds": 0.002217292
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 899,
+ "function": "",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 0.000381109,
+ "cumulative_seconds": 0.001893382
+ },
+ {
+ "file": "python/workspace_explorer.py",
+ "line": 60,
+ "function": "protocol_binding_header",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 2.4292e-05,
+ "cumulative_seconds": 0.001865792
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 195,
+ "function": "_chips",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 4.2914e-05,
+ "cumulative_seconds": 0.001833713
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 183,
+ "function": "dumps",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 3.6248e-05,
+ "cumulative_seconds": 0.0017533330000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 183,
+ "function": "encode",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 2.9416e-05,
+ "cumulative_seconds": 0.0017009590000000002
+ },
+ {
+ "file": "python/workspace_native_tag_filter.py",
+ "line": 25,
+ "function": "contains",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.124000000000001e-06,
+ "cumulative_seconds": 0.0016989580000000002
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 188,
+ "function": "_validated_tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 6.559e-05,
+ "cumulative_seconds": 0.001677466
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 205,
+ "function": "iterencode",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 0.001668041,
+ "cumulative_seconds": 0.001668041
+ },
+ {
+ "file": "python/workspace_native_tag_lookup.py",
+ "line": 251,
+ "function": "targets",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0126000000000001e-05,
+ "cumulative_seconds": 0.0016652090000000002
+ },
+ {
+ "file": "python/workspace_native_tag_lookup.py",
+ "line": 93,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.207e-06,
+ "cumulative_seconds": 0.001634208
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 232,
+ "function": "__init__",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 8.9801e-05,
+ "cumulative_seconds": 0.0015738780000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 166,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.040000000000001e-06,
+ "cumulative_seconds": 0.0014831660000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 236,
+ "function": "_parse_field_descriptor",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 0.000327473,
+ "cumulative_seconds": 0.001460918
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 236,
+ "function": "source_scope",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.0004000000000005e-05,
+ "cumulative_seconds": 0.001446042
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 869,
+ "function": "_execute_command",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000467089,
+ "cumulative_seconds": 0.001418542
+ }
+ ],
+ "native_sql_calls": [
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0011297910241410136
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004975830088369548
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00027316599152982235
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00042937498074024916
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002436659997329116
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003595830057747662
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.000341000035405159
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00013645802391692996
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00022445799550041556
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00017604202730581164
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00047745800111442804
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.001228584034834057
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003522090264596045
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00017862499225884676
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00038754200795665383
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00020529201719909906
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003262920072302222
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0004046669928357005
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.0001313749817200005
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00022350001381710172
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0005449579912237823
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00022733403602615
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00019225000869482756
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.0006037919665686786
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0006285419804044068
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00047687499318271875
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "seconds": 0.0016202910337597132
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`epoch_uuid`,`included`,`tags`,`review_state`,`revision`,`metadata_fingerprint` FROM `recording_workspace`.`curation` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))AND( ( (`epoch_uuid`='?') OR (",
+ "seconds": 0.006371416035108268
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`dataset_uuid`,`created_at`,`artifact_sha256` FROM `recording_workspace`.`dataset_revision` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0006397080142050982
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.006264208001084626
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.0007439159671775997
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00044420798076316714
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00020970799960196018
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00025345798349007964
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0004218749818392098
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0008576669497415423
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0005532079958356917
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00023662502644583583
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00042770797153934836
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00023695797426626086
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003593750298023224
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00039854203350842
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00016325002070516348
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00025037501472979784
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00019079202320426702
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005356249748729169
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.001245874969754368
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00038291700184345245
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00020591699285432696
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0003889590152539313
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00021708401618525386
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.000338125042617321
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0002839590306393802
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.0001384170027449727
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00024216703604906797
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0006391250062733889
+ }
+ ],
+ "limits": "Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap."
+ },
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1070",
+ "ref": "const,const,const",
+ "rows": 20,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ }
+ ],
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.068396
+ }
+ },
+ "project_uuid": "3a791e27-101f-4a82-a7e8-f8be38297694",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.151797
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.052229
+ }
+ },
+ "project_uuid": "3a791e27-101f-4a82-a7e8-f8be38297694",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.165251
+ },
+ "seconds": 0.31707962503423914,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "workflow_peak_rss_before_durable_oracle_bytes": 953991168,
+ "durable_snapshot_verify_seconds": 8.305504375020973,
+ "loaded_product_modules": {
+ "workspace_tree": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tree.py",
+ "workspace_predicates": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_predicates.py",
+ "workspace_cache_lifecycle": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_cache_lifecycle.py",
+ "workspace_metadata_objects": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_metadata_objects.py",
+ "workspace_disk_index": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_disk_index.py",
+ "workspace_catalog_identity": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_catalog_identity.py",
+ "recording_workspace": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/recording_workspace.py",
+ "workspace_recipes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_recipes.py",
+ "workspace_tree_code": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tree_code.py",
+ "workspace_service": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_service.py",
+ "workspace_tree_pages": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tree_pages.py",
+ "workspace_projects": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_projects.py",
+ "workspace_mysql_profile": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_mysql_profile.py",
+ "workspace_native_mysql": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_native_mysql.py",
+ "workspace_mysql_runtime": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_mysql_runtime.py",
+ "workspace_storage": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_storage.py",
+ "workspace_diff": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_diff.py",
+ "workspace_protocol_identity": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_protocol_identity.py",
+ "workspace_import_check": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_import_check.py",
+ "workspace_audit": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_audit.py",
+ "workspace_api": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_api.py",
+ "workspace_state_snapshot": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_state_snapshot.py",
+ "workspace_author_preferences": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_author_preferences.py",
+ "workspace_curation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_curation.py",
+ "workspace_annotations": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_annotations.py",
+ "workspace_shared_tag_index": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_shared_tag_index.py",
+ "workspace_paths": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_paths.py",
+ "workspace_desktop_paths": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_desktop_paths.py",
+ "workspace_startup_registry": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_startup_registry.py",
+ "workspace_explorer": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_explorer.py",
+ "workspace_datastores": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_datastores.py",
+ "workspace_suggestions": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_suggestions.py",
+ "workspace_tag_predicates": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tag_predicates.py",
+ "workspace_export_folder": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_export_folder.py",
+ "workspace_import_progress": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_import_progress.py",
+ "workspace_project_preferences": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_project_preferences.py",
+ "workspace_protocol_state": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_protocol_state.py",
+ "workspace_state_generation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_state_generation.py",
+ "workspace_project_servers": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_project_servers.py",
+ "workspace_launcher": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_launcher.py",
+ "workspace_app_routes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_app_routes.py",
+ "workspace_folder_browser": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_folder_browser.py",
+ "workspace_h5_inbox": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_h5_inbox.py",
+ "workspace_tag_exchange": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tag_exchange.py",
+ "workspace_external_tags": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_external_tags.py",
+ "workspace_qc": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_qc.py",
+ "workspace_search": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_search.py",
+ "workspace_matlab_masks": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_matlab_masks.py",
+ "workspace_matlab_routes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_matlab_routes.py",
+ "workspace_mask_refresh": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_mask_refresh.py",
+ "workspace_matching_epochs": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_matching_epochs.py",
+ "workspace_candidate_exports": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_candidate_exports.py",
+ "workspace_backup_scheduler": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_backup_scheduler.py",
+ "workspace_recovery_store": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_recovery_store.py",
+ "workspace_recovery_generation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_recovery_generation.py",
+ "workspace_annotation_preparation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_annotation_preparation.py",
+ "workspace_native_tag_lookup": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_native_tag_lookup.py",
+ "workspace_lifecycle": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_lifecycle.py",
+ "workspace_native_tag_filter": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_native_tag_filter.py"
+ },
+ "owned_native_runtime_stopped": true,
+ "source_inventory_after": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_concurrent_requests.py": "f074a762536ab5bb52abe4ea56faada41babb1cd765512acb3caba18dd503648",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "17c37104693d46b416064ab05027c54added8d26ddd2ba6b7de202911ce5d592",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_event_paging.py": "e79cd1de3bd7469818e255cc01f087a608c11e799a48e81e4ca0ac29ff7a578e",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "3f4b346a59e1b78aeea51b1d1c2812582eed009b4cb2cc36831830e0d62601ca",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "f852f8af8b9795c63772800c3ca3cb7203fa0a1b81e0ef562ef848c1e31a06b0",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7fe035916c152aad2df6f41cc6f5c55e8d30cb4168f53c4ab1aa9e171bdd7cfc",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6d89f8a735c34e6ec3f7f3db291e5d21c1bd8e64a60e7b12563e82dc18eb9f24",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "8efc55e5fb6079072014c616223872464c0451c6d85bfcb2c61693f9baaed1ff",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "66a6a04c107053b3c0492e5ccd2e6f6e10b8479c107c0b58d5b5fd712800312c",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "a43e66ff47aaec64d0a862277b7751901775ab7f193f58c0106437b9fab1331b",
+ "python/workspace_native_tag_lookup.py": "d549e8084af074ab484759a2851fdacde7e632c334f55e2d65caee368e04ffd5",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "40ba54acf2c5e9983c2e2766179a3d365b30ad1cdc4955410571936f7dfb4954",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "1917ae5b36553048027a19e1b68f99677a754dae1501d1e52be1cd3faaa2b4a5",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "source_unchanged": true,
+ "harness_unchanged": true,
+ "seconds": 223.9138767500408,
+ "finished_at": "2026-09-30T15:21:30.662101+00:00",
+ "peak_rss_bytes": 953991168,
+ "passed": true
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-500.json b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-500.json
new file mode 100644
index 0000000..1ac3681
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-500.json
@@ -0,0 +1,3463 @@
+{
+ "scope": "Persistent native tag lookup: tag-ten, tag-another-ten, filter-twenty and cell/epoch hierarchy.\n\nEvery project/database is disposable. Acquisition metadata is synthetic; actual\nMySQL, triggers, Flask routes, derived indexes and each version's save hooks\nare used. Native SQL commits are checked immediately; any background recovery\nmirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude\noracle work, and do not model browser concurrency/rendering. Sequential bundle\ntimes are sums of their separately measured requests. Profiles are separate.\n",
+ "epochs": 500,
+ "profiles": 3,
+ "tags_per_annotation": 5,
+ "protocols": 2,
+ "samples": 5,
+ "source_root": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9",
+ "source_inventory_before": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_concurrent_requests.py": "f074a762536ab5bb52abe4ea56faada41babb1cd765512acb3caba18dd503648",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "17c37104693d46b416064ab05027c54added8d26ddd2ba6b7de202911ce5d592",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_event_paging.py": "e79cd1de3bd7469818e255cc01f087a608c11e799a48e81e4ca0ac29ff7a578e",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "3f4b346a59e1b78aeea51b1d1c2812582eed009b4cb2cc36831830e0d62601ca",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "f852f8af8b9795c63772800c3ca3cb7203fa0a1b81e0ef562ef848c1e31a06b0",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7fe035916c152aad2df6f41cc6f5c55e8d30cb4168f53c4ab1aa9e171bdd7cfc",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6d89f8a735c34e6ec3f7f3db291e5d21c1bd8e64a60e7b12563e82dc18eb9f24",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "8efc55e5fb6079072014c616223872464c0451c6d85bfcb2c61693f9baaed1ff",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "66a6a04c107053b3c0492e5ccd2e6f6e10b8479c107c0b58d5b5fd712800312c",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "a43e66ff47aaec64d0a862277b7751901775ab7f193f58c0106437b9fab1331b",
+ "python/workspace_native_tag_lookup.py": "d549e8084af074ab484759a2851fdacde7e632c334f55e2d65caee368e04ffd5",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "40ba54acf2c5e9983c2e2766179a3d365b30ad1cdc4955410571936f7dfb4954",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "1917ae5b36553048027a19e1b68f99677a754dae1501d1e52be1cd3faaa2b4a5",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "fixture_module": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py",
+ "harness_sha256": "468173271c08020e7e1a7ce8b0fcdfc59693be0f9e3699a8caec342b2e77affd",
+ "started_at": "2026-09-30T15:17:16.330782+00:00",
+ "release_ready": false,
+ "operations": {
+ "first10_revision_preflight": {
+ "samples_seconds": [
+ 0.006863583053927869,
+ 0.005767875001765788,
+ 0.005951334023848176,
+ 0.006857750006020069,
+ 0.006089958013035357
+ ],
+ "p50_seconds": 0.006089958013035357,
+ "p95_seconds": 0.006863583053927869,
+ "p99_seconds": 0.006863583053927869,
+ "maximum_seconds": 0.006863583053927869,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15362,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15362,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ }
+ ]
+ },
+ "first10_save": {
+ "samples_seconds": [
+ 0.03397225000662729,
+ 0.01669733296148479,
+ 0.01590783294523135,
+ 0.01774270902387798,
+ 0.018389292003121227
+ ],
+ "p50_seconds": 0.01774270902387798,
+ "p95_seconds": 0.03397225000662729,
+ "p99_seconds": 0.03397225000662729,
+ "maximum_seconds": 0.03397225000662729,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 18598,
+ "Questions": 36
+ },
+ {
+ "Bytes_sent": 12452,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12423,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ }
+ ]
+ },
+ "second10_revision_preflight": {
+ "samples_seconds": [
+ 0.006374125019647181,
+ 0.005576791998464614,
+ 0.006221582996658981,
+ 0.0060125000309199095,
+ 0.005592082976363599
+ ],
+ "p50_seconds": 0.0060125000309199095,
+ "p95_seconds": 0.006374125019647181,
+ "p99_seconds": 0.006374125019647181,
+ "maximum_seconds": 0.006374125019647181,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15380,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15380,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ }
+ ]
+ },
+ "second10_save": {
+ "samples_seconds": [
+ 0.01842504198430106,
+ 0.016915374959353358,
+ 0.01750191702740267,
+ 0.016256875009275973,
+ 0.01602291699964553
+ ],
+ "p50_seconds": 0.016915374959353358,
+ "p95_seconds": 0.01842504198430106,
+ "p99_seconds": 0.01842504198430106,
+ "maximum_seconds": 0.01842504198430106,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12435,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter20": {
+ "samples_seconds": [
+ 0.02724245801800862,
+ 0.022826333006378263,
+ 0.03516616701381281,
+ 0.021462041011545807,
+ 0.023176041955593973
+ ],
+ "p50_seconds": 0.023176041955593973,
+ "p95_seconds": 0.03516616701381281,
+ "p99_seconds": 0.03516616701381281,
+ "maximum_seconds": 0.03516616701381281,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 141272,
+ "Questions": 64
+ },
+ {
+ "Bytes_sent": 134733,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134734,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134734,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 134734,
+ "Questions": 57
+ }
+ ]
+ },
+ "cell_save": {
+ "samples_seconds": [
+ 0.007502541993744671,
+ 0.00725866702850908,
+ 0.018350000027567148,
+ 0.007054875022731721,
+ 0.007763291010633111
+ ],
+ "p50_seconds": 0.007502541993744671,
+ "p95_seconds": 0.018350000027567148,
+ "p99_seconds": 0.018350000027567148,
+ "maximum_seconds": 0.018350000027567148,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4165,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_cell100": {
+ "samples_seconds": [
+ 0.03877783298958093,
+ 0.03749154199613258,
+ 0.04011900001205504,
+ 0.038218542002141476,
+ 0.03963516699150205
+ ],
+ "p50_seconds": 0.03877783298958093,
+ "p95_seconds": 0.04011900001205504,
+ "p99_seconds": 0.04011900001205504,
+ "maximum_seconds": 0.04011900001205504,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 168042,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168042,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168043,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168043,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 168043,
+ "Questions": 57
+ }
+ ]
+ },
+ "filter_cell_and_epoch10": {
+ "samples_seconds": [
+ 0.01800766703672707,
+ 0.018951375037431717,
+ 0.020278000039979815,
+ 0.018302250013221055,
+ 0.01821737503632903
+ ],
+ "p50_seconds": 0.018302250013221055,
+ "p95_seconds": 0.020278000039979815,
+ "p99_seconds": 0.020278000039979815,
+ "maximum_seconds": 0.020278000039979815,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 125358,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125358,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125359,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125359,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 125359,
+ "Questions": 58
+ }
+ ]
+ },
+ "remove_first10_revision_preflight": {
+ "samples_seconds": [
+ 0.0059079170459881425,
+ 0.00627808301942423,
+ 0.006188458995893598,
+ 0.005992332997266203,
+ 0.007050833024550229
+ ],
+ "p50_seconds": 0.006188458995893598,
+ "p95_seconds": 0.007050833024550229,
+ "p99_seconds": 0.007050833024550229,
+ "maximum_seconds": 0.007050833024550229,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15799,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15799,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_first10_save": {
+ "samples_seconds": [
+ 0.016256083035841584,
+ 0.01609495800221339,
+ 0.016188249981496483,
+ 0.015846625028643757,
+ 0.018834499991498888
+ ],
+ "p50_seconds": 0.016188249981496483,
+ "p95_seconds": 0.018834499991498888,
+ "p99_seconds": 0.018834499991498888,
+ "maximum_seconds": 0.018834499991498888,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12452,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12452,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12423,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_hierarchy_after_epoch_remove": {
+ "samples_seconds": [
+ 0.011758790991734713,
+ 0.012778042000718415,
+ 0.012820375035516918,
+ 0.01242733298568055,
+ 0.011840666003990918
+ ],
+ "p50_seconds": 0.01242733298568055,
+ "p95_seconds": 0.012820375035516918,
+ "p99_seconds": 0.012820375035516918,
+ "maximum_seconds": 0.012820375035516918,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112890,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112890,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112891,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112891,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 112891,
+ "Questions": 55
+ }
+ ]
+ },
+ "filter_remaining10": {
+ "samples_seconds": [
+ 0.0174046250176616,
+ 0.017987709026783705,
+ 0.01727400004165247,
+ 0.01689045800594613,
+ 0.017530124983750284
+ ],
+ "p50_seconds": 0.0174046250176616,
+ "p95_seconds": 0.017987709026783705,
+ "p99_seconds": 0.017987709026783705,
+ "maximum_seconds": 0.017987709026783705,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 124593,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124593,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124594,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124594,
+ "Questions": 57
+ },
+ {
+ "Bytes_sent": 124594,
+ "Questions": 57
+ }
+ ]
+ },
+ "remove_second10_revision_preflight": {
+ "samples_seconds": [
+ 0.005977375025395304,
+ 0.005894292029552162,
+ 0.006003624992445111,
+ 0.005952916049864143,
+ 0.006602292007301003
+ ],
+ "p50_seconds": 0.005977375025395304,
+ "p95_seconds": 0.006602292007301003,
+ "p99_seconds": 0.006602292007301003,
+ "maximum_seconds": 0.006602292007301003,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_second10_save": {
+ "samples_seconds": [
+ 0.016056958003900945,
+ 0.015534125035628676,
+ 0.015537166967988014,
+ 0.015851082978770137,
+ 0.01648387499153614
+ ],
+ "p50_seconds": 0.015851082978770137,
+ "p95_seconds": 0.01648387499153614,
+ "p99_seconds": 0.01648387499153614,
+ "maximum_seconds": 0.01648387499153614,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12435,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_after_all_epoch_removals": {
+ "samples_seconds": [
+ 0.011692999978549778,
+ 0.011391708976589143,
+ 0.011859584017656744,
+ 0.011452833947259933,
+ 0.012298792018555105
+ ],
+ "p50_seconds": 0.011692999978549778,
+ "p95_seconds": 0.012298792018555105,
+ "p99_seconds": 0.012298792018555105,
+ "maximum_seconds": 0.012298792018555105,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112133,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112133,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ }
+ ]
+ },
+ "remove_cell_save": {
+ "samples_seconds": [
+ 0.0071121660294011235,
+ 0.007018374977633357,
+ 0.007366625010035932,
+ 0.007096500019542873,
+ 0.007313000038266182
+ ],
+ "p50_seconds": 0.0071121660294011235,
+ "p95_seconds": 0.007366625010035932,
+ "p99_seconds": 0.007366625010035932,
+ "maximum_seconds": 0.007366625010035932,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4165,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_after_cell_removal": {
+ "samples_seconds": [
+ 0.011266125016845763,
+ 0.01227129198377952,
+ 0.013780042005237192,
+ 0.011646541010122746,
+ 0.011481249995995313
+ ],
+ "p50_seconds": 0.011646541010122746,
+ "p95_seconds": 0.013780042005237192,
+ "p99_seconds": 0.013780042005237192,
+ "maximum_seconds": 0.013780042005237192,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 112133,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ },
+ {
+ "Bytes_sent": 112134,
+ "Questions": 54
+ }
+ ]
+ },
+ "tag10_then10_filter20_http_total": {
+ "samples_seconds": [
+ 0.09287745808251202,
+ 0.06778370792744681,
+ 0.08074883400695398,
+ 0.06833187508163974,
+ 0.06927029194775969
+ ],
+ "p50_seconds": 0.06927029194775969,
+ "p95_seconds": 0.09287745808251202,
+ "p99_seconds": 0.09287745808251202,
+ "maximum_seconds": 0.09287745808251202,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "samples_seconds": [
+ 0.0462803749833256,
+ 0.04475020902464166,
+ 0.05846900003962219,
+ 0.0452734170248732,
+ 0.04739845800213516
+ ],
+ "p50_seconds": 0.0462803749833256,
+ "p95_seconds": 0.05846900003962219,
+ "p99_seconds": 0.05846900003962219,
+ "maximum_seconds": 0.05846900003962219,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "first10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.04083583306055516,
+ 0.022465207963250577,
+ 0.021859166969079524,
+ 0.024600459029898047,
+ 0.024479250016156584
+ ],
+ "p50_seconds": 0.024479250016156584,
+ "p95_seconds": 0.04083583306055516,
+ "p99_seconds": 0.04083583306055516,
+ "maximum_seconds": 0.04083583306055516,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "second10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.02479916700394824,
+ 0.02249216695781797,
+ 0.02372350002406165,
+ 0.022269375040195882,
+ 0.02161499997600913
+ ],
+ "p50_seconds": 0.02249216695781797,
+ "p95_seconds": 0.02479916700394824,
+ "p99_seconds": 0.02479916700394824,
+ "maximum_seconds": 0.02479916700394824,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "autocomplete_cold_diagnostic": {
+ "samples_seconds": [
+ 0.023283249989617616
+ ],
+ "p50_seconds": 0.023283249989617616,
+ "p95_seconds": 0.023283249989617616,
+ "p99_seconds": 0.023283249989617616,
+ "maximum_seconds": 0.023283249989617616,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 215729,
+ "Questions": 91
+ }
+ ]
+ },
+ "autocomplete_warm_diagnostic": {
+ "samples_seconds": [
+ 0.029437625024002045
+ ],
+ "p50_seconds": 0.029437625024002045,
+ "p95_seconds": 0.029437625024002045,
+ "p99_seconds": 0.029437625024002045,
+ "maximum_seconds": 0.029437625024002045,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 215729,
+ "Questions": 91
+ }
+ ]
+ },
+ "autocomplete_empty_diagnostic": {
+ "samples_seconds": [
+ 0.03325066698016599
+ ],
+ "p50_seconds": 0.03325066698016599,
+ "p95_seconds": 0.03325066698016599,
+ "p99_seconds": 0.03325066698016599,
+ "maximum_seconds": 0.03325066698016599,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 218807,
+ "Questions": 91
+ }
+ ]
+ },
+ "same_server_native_reuse_filter20": {
+ "samples_seconds": [
+ 0.020739166997373104
+ ],
+ "p50_seconds": 0.020739166997373104,
+ "p95_seconds": 0.020739166997373104,
+ "p99_seconds": 0.020739166997373104,
+ "maximum_seconds": 0.020739166997373104,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 134735,
+ "Questions": 57
+ }
+ ]
+ }
+ },
+ "checks": [
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 167
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "page_cells": true,
+ "target_specification": "tag10_then10_filter20_and_cell_epoch_hierarchy",
+ "baseline_harness_sha256": "272fe050c8762ed4ea45bd013e236e8723d8fe46f5e7e293c58e2edf1fd52ddc",
+ "mysql_version": "8.4.2",
+ "seed_seconds": 0.4073622500291094,
+ "seed_checkpoint_seconds": 0.18052658397937194,
+ "native_lookup_startup_preparation": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.054246
+ }
+ },
+ "project_uuid": "e9bffb2b-d4b9-42e6-add4-e9fef1532cf7",
+ "reused": false,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.233545
+ },
+ "selected_epoch_uuids": {
+ "first10": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5"
+ ],
+ "second10": [
+ "ad573668-6512-6e55-649e-cb23ae1d4888",
+ "16dc368a-89b4-28b2-4854-84313ba67a39",
+ "37834f2f-2576-2f23-e1f7-4a531cbe445d",
+ "454f63ac-30c8-3229-97ef-025edff6abd2",
+ "5ef6fdf3-2513-aa7c-d11f-72beccf132b9",
+ "1253e937-3e78-1b75-0026-6caa55150e08",
+ "482d9673-cfee-5de3-91f9-7fde4d1c84f9",
+ "3346f2bb-f6c3-4bd2-dbe2-8bd1bb657d0e",
+ "9537f32e-c759-9e1a-e953-af6c9f929fe7",
+ "0fd42b3f-73c4-48b3-4940-b339f87d07ad"
+ ],
+ "cell": "0604cd31-38fe-ed20-2ef2-93e062da2f47",
+ "cell_children": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5",
+ "4a44dc15-3642-04a8-0fe8-0e9039455cc1",
+ "4fc82b26-aecb-47d2-868c-4efbe3581732",
+ "6b51d431-df5d-7f14-1cbe-ceccf79edf3d",
+ "3fdba35f-04dc-8c46-2986-c992bcf87554",
+ "8527a891-e224-1369-50ff-32ca212b45bc",
+ "e629fa65-98d7-3276-8f7c-726b4b621285",
+ "b17ef6d1-9c7a-5b1e-e83b-907c595526dc",
+ "4523540f-1504-cd17-100c-4835e85b7eef",
+ "4ec9599f-c203-d176-a301-536c2e091a19",
+ "9400f1b2-1cb5-27d7-fa3d-3eabba93557a",
+ "f5ca38f7-48a1-d6ea-f726-b8a42fb575c3",
+ "6f4b6612-125f-b3a0-daec-d2799dfd6c9c",
+ "785f3ec7-eb32-f30b-90cd-0fcf3657d388",
+ "535fa30d-7e25-dd8a-49f1-536779734ec8",
+ "c2356069-e9d1-e79c-a924-378153cfbbfb",
+ "b7a56873-cd77-1f2c-446d-369b649430b6",
+ "5f9c4ab0-8cac-7457-e911-1a30e4664920",
+ "670671cd-9740-4156-226e-507973f2ab83",
+ "59e19706-d51d-39f6-6711-c2653cd7eb12",
+ "35135aaa-6cc2-3891-b40c-b3f378c53a17",
+ "624b60c5-8c9d-8bfb-6ff1-886c2fd605d2",
+ "eb1e33e8-a81b-697b-7585-5af6bfcdbcbf",
+ "e29c9c18-0c62-79b0-b02a-bd6a1801c7c0",
+ "c6f3ac57-944a-5314-90cd-39902d0f7777",
+ "86e50149-6586-6131-2a9e-0b35558d84f6",
+ "9f14025a-f006-5b30-e47e-23ebb3b491d3",
+ "76a50887-d8f1-c2e9-3017-55428990ad81",
+ "7a61b537-01be-fdae-0eee-ffaecc73f14e",
+ "aea92132-c4cb-eb26-3e6a-c2bf6c183b5d",
+ "0b918943-df09-62bc-7a18-24c0555a3893",
+ "d59eced1-ded0-7f84-c145-592f65bdf854",
+ "3d914f93-48c9-cc0f-f8a7-9716700b9fcd",
+ "73475cb4-0a56-8e8d-a8a0-45ced110137e",
+ "44cb730c-4204-80a0-477b-505ae68af508",
+ "71ee45a3-c0db-9a98-65f7-313dd3372cf6",
+ "811786ad-1ae7-4adf-dd20-dd0372abaaeb",
+ "25fc0e70-96fc-6537-1820-2dc30b0c580b",
+ "31489056-e091-6d59-fe3a-dd79e63f095a",
+ "98010bd9-270f-9b10-0b62-14a21754fd33",
+ "0e17daca-5f3e-175f-448b-acace3bc0da4",
+ "1a656259-0ef1-9d10-45d0-6c4055742d38",
+ "031b4af5-197e-c30a-926f-48cf40e11a7d",
+ "41cfc0d1-f2d1-27b0-4555-b7246d84019b",
+ "2858dcd1-057d-3eae-7f7d-5f782167e24b",
+ "2fca346d-b656-1871-02ce-806ac732e06a",
+ "02d20bbd-7e39-4ad5-999a-4cebabac9619",
+ "7688b6ef-5255-5962-d008-fff894223582",
+ "c837649c-ce43-f272-9138-e72cc3152070",
+ "6208ef0f-7750-c111-548c-f90b6ea1d0d0",
+ "3e1e967e-9b79-3e90-8f8e-ae83c74dba9b",
+ "39fa9ec1-90ee-e7b6-f4df-f1100d6343e1",
+ "d029fa3a-95e1-74a1-9934-857f535eb942",
+ "81b8a03f-97e8-787c-53fe-1a86bda042b6",
+ "da4ea2a5-506f-2693-eae1-90d9360a1f31",
+ "a68b412c-4282-555f-1554-6cf6e1fc4289",
+ "108c995b-953c-8a35-5611-03e2014cf828",
+ "3ada92f2-8b4c-eda3-8562-ebf047c6ff05",
+ "49d180ec-f561-3281-9571-bf39d9b7b342",
+ "a21855da-08cb-102d-1d21-7c53dc5824a3",
+ "c75cb66a-e28d-8ebc-6ede-d002c28a8ba0",
+ "ff5a1ae0-12af-a5d4-c889-c50ad427aaf5",
+ "7f2253d7-e228-b22a-08bd-a1f09c516f6f",
+ "87226162-0421-7edd-b39e-7df969e0698a",
+ "96061e92-f58e-4bdc-dee7-3df36183fe3a",
+ "eb624dbe-56eb-6620-ae62-080c10a273ca",
+ "f369cb89-fc62-7e66-8987-007d121ed1ea",
+ "f74efabe-f12e-a619-e30b-79bddef89cff",
+ "a88a7902-cb4e-f697-ba0b-6759c50e8c10",
+ "349c4120-1b62-db85-1192-665c504b350f",
+ "98a3ab7c-340e-8a03-3e7b-37b6ef942875",
+ "48449a14-a4ff-7d79-bb7a-1b6f3d488eba",
+ "5316ca1c-5ddc-a8e6-cecc-fce58f3b8540",
+ "a46e3763-2fa6-ca51-a13f-e39a567b3c23",
+ "bbb965ab-0c80-d653-8cf2-184babad2a56",
+ "44c8031c-b036-a735-0d8b-9b8603af662a",
+ "b4944c6f-f08d-c6f4-3da2-e9c824669b7d",
+ "434c9b5a-e514-646b-bd91-b50032ca579e",
+ "bdd2d3af-3a5a-1213-497d-4f1f7bfcda89",
+ "8b940be7-fb78-aaa6-b656-7dd7a3987996",
+ "cd70bea0-23f7-52a0-564a-bb6ed08d42c1",
+ "69f59c27-3b6e-669a-c32a-6dd5e1b2cb63",
+ "1da51b8d-8ff9-8f6a-48f8-0ae79fe3ca6c",
+ "82416496-09f8-8ccd-2a0a-5b233a07a538",
+ "6e400187-1c0c-f27c-7634-ef1dc478408f",
+ "e3d6c4d4-599e-0088-2384-ca981ee287ed",
+ "ad48ff99-415b-2f00-7dc3-5b7eb553fd1e",
+ "7b1a278f-5abe-8e9d-a907-fc9c29dfd432",
+ "d6d824ab-ba4a-fde8-1129-c71dea75b810",
+ "29db0c67-82db-d500-0559-ef4d9e953e30",
+ "8c1f1046-219d-dd21-6a02-3f792356ddf1"
+ ]
+ },
+ "backup_receipts": [
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 1,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 2,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 3,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 4,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 5,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 6,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 7,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 8,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 9,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 10,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 11,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 12,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 13,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 14,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 15,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 16,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 17,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 18,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 19,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 20,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 21,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 22,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 23,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 24,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 25,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 26,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 27,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 28,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 29,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 30,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:17:24.169576+00:00",
+ "pending": true,
+ "requested_sequence": 31,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T15:17:24.169576+00:00",
+ "pending": true,
+ "requested_sequence": 32,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_first10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:17:24.486314+00:00",
+ "pending": true,
+ "requested_sequence": 33,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_second10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T15:17:24.486314+00:00",
+ "pending": true,
+ "requested_sequence": 34,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ }
+ ],
+ "preparation_index_seconds": 0,
+ "lookup_mode": "persistent_native_sql_no_post_seed_tag_cache_preparation",
+ "bundle_definitions": {
+ "tag10_then10_filter20_http_total": [
+ "first10_revision_preflight",
+ "first10_save",
+ "second10_revision_preflight",
+ "second10_save",
+ "filter20"
+ ],
+ "cell_tag_then_cell_filter_http_total": [
+ "cell_save",
+ "filter_cell100"
+ ],
+ "first10_selected_action_http_total": [
+ "first10_revision_preflight",
+ "first10_save"
+ ],
+ "second10_selected_action_http_total": [
+ "second10_revision_preflight",
+ "second10_save"
+ ]
+ },
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:17:25.252632+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 0.0019153340253978968
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.0005384999676607549
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.0007603340200148523
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0002502499846741557
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 0.0018970409873872995
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.00045908300671726465
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.0006526659708470106
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00018674996681511402
+ }
+ ],
+ "autocomplete_empty_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 0.001917750050779432
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.005181541957426816
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.005963417002931237
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0002708330284804106
+ }
+ ]
+ },
+ "autocomplete_requests": {
+ "autocomplete_cold_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_warm_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_empty_diagnostic": {
+ "q": "",
+ "limit": 12,
+ "total": 41
+ }
+ },
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 3787,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 542,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 542,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1108",
+ "ref": null,
+ "rows": 9769,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index for group-by (scanning)"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "index",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1108",
+ "ref": null,
+ "rows": 7575,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 9,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ }
+ ],
+ "filter20_cpu_profile": {
+ "top_functions": [
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 331,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.833000000000001e-06,
+ "cumulative_seconds": 0.055883583
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 159,
+ "function": "request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.125e-06,
+ "cumulative_seconds": 0.055855125000000005
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1163,
+ "function": "get",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.916000000000001e-06,
+ "cumulative_seconds": 0.055037958000000005
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/testing.py",
+ "line": 204,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.5292000000000002e-05,
+ "cumulative_seconds": 0.055032042
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1062,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.916e-06,
+ "cumulative_seconds": 0.054734000000000005
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 984,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0544000000000001e-05,
+ "cumulative_seconds": 0.054719959000000006
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1230,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.206e-06,
+ "cumulative_seconds": 0.054527291000000006
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1529,
+ "function": "__call__",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.25e-06,
+ "cumulative_seconds": 0.054501125000000004
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1479,
+ "function": "wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0334000000000001e-05,
+ "cumulative_seconds": 0.054494875000000005
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 904,
+ "function": "full_dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.959000000000001e-06,
+ "cumulative_seconds": 0.05426191700000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 879,
+ "function": "dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.316600000000001e-05,
+ "cumulative_seconds": 0.05404975
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 716,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.011800000000001e-05,
+ "cumulative_seconds": 0.053952625000000004
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 295,
+ "function": "for_epochs",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.000110742,
+ "cumulative_seconds": 0.021559708
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py",
+ "line": 322,
+ "function": "traced_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00020169600000000002,
+ "cumulative_seconds": 0.021302794
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 412,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 9.6835e-05,
+ "cumulative_seconds": 0.020735294
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 402,
+ "function": "_execute_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000112442,
+ "cumulative_seconds": 0.020493535
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 27,
+ "calls": 38,
+ "self_seconds": 2.8749000000000002e-05,
+ "cumulative_seconds": 0.020399084
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 249,
+ "function": "native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.0751000000000001e-05,
+ "cumulative_seconds": 0.020334834
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 150,
+ "function": "execute",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 5.8716000000000005e-05,
+ "cumulative_seconds": 0.020121799000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 332,
+ "function": "_query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 6.766500000000001e-05,
+ "cumulative_seconds": 0.019671499000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 575,
+ "function": "query",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.2996e-05,
+ "cumulative_seconds": 0.019196248000000003
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 851,
+ "function": "_read_query_result",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 5.2428e-05,
+ "cumulative_seconds": 0.018222878
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1288,
+ "function": "read",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 7.029700000000001e-05,
+ "cumulative_seconds": 0.018144459
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 101,
+ "function": "_rows",
+ "primitive_calls": 46,
+ "calls": 46,
+ "self_seconds": 5.5586000000000006e-05,
+ "cumulative_seconds": 0.016481585
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 197,
+ "function": "response_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.6333e-05,
+ "cumulative_seconds": 0.016095541
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 233,
+ "function": "_attest_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 6.5583e-05,
+ "cumulative_seconds": 0.016065292000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 128,
+ "function": "deepcopy",
+ "primitive_calls": 78,
+ "calls": 17170,
+ "self_seconds": 0.009397953,
+ "cumulative_seconds": 0.016016376000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 201,
+ "function": "_deepcopy_list",
+ "primitive_calls": 62,
+ "calls": 102,
+ "self_seconds": 0.000313937,
+ "cumulative_seconds": 0.015887622
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 227,
+ "function": "_deepcopy_dict",
+ "primitive_calls": 1242,
+ "calls": 1284,
+ "self_seconds": 0.002823383,
+ "cumulative_seconds": 0.013861159000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 763,
+ "function": "_read_packet",
+ "primitive_calls": 765,
+ "calls": 765,
+ "self_seconds": 0.001183477,
+ "cumulative_seconds": 0.013647774000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 813,
+ "function": "_read_bytes",
+ "primitive_calls": 1530,
+ "calls": 1530,
+ "self_seconds": 0.00055718,
+ "cumulative_seconds": 0.010595909
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 132,
+ "function": "__enter__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 2.4160000000000002e-06,
+ "cumulative_seconds": 0.01029525
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 141,
+ "function": "__exit__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 5.961e-06,
+ "cumulative_seconds": 0.010047500000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 1530,
+ "calls": 1530,
+ "self_seconds": 0.000202833,
+ "cumulative_seconds": 0.009966704
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/socket.py",
+ "line": 704,
+ "function": "readinto",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 7.1325e-05,
+ "cumulative_seconds": 0.009763871
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1299,
+ "function": "recv_into",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 7.6537e-05,
+ "cumulative_seconds": 0.00965621
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1157,
+ "function": "read",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 4.4206e-05,
+ "cumulative_seconds": 0.009569875
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 67,
+ "calls": 67,
+ "self_seconds": 0.009520961000000001,
+ "cumulative_seconds": 0.009520961000000001
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 134,
+ "function": "_active_ddl_guard",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 3.4702e-05,
+ "cumulative_seconds": 0.009518833
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1370,
+ "function": "_read_result_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 5.7791e-05,
+ "cumulative_seconds": 0.008029705
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/__init__.py",
+ "line": 138,
+ "function": "jsonify",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.917e-06,
+ "cumulative_seconds": 0.007209750000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 189,
+ "function": "response",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.0667e-05,
+ "cumulative_seconds": 0.0071941670000000004
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 869,
+ "function": "to_dicts",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 2.0088e-05,
+ "cumulative_seconds": 0.007106127
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 61,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.9334e-05,
+ "cumulative_seconds": 0.007095667000000001
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 62,
+ "function": "browser_safe",
+ "primitive_calls": 1,
+ "calls": 9641,
+ "self_seconds": 0.003128445,
+ "cumulative_seconds": 0.005557
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 66,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1363,
+ "self_seconds": 0.001356685,
+ "cumulative_seconds": 0.0055550420000000005
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 2,
+ "calls": 102,
+ "self_seconds": 0.000207931,
+ "cumulative_seconds": 0.005543167000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1416,
+ "function": "_read_rowdata_packet",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.000348339,
+ "cumulative_seconds": 0.005187870000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 1231,
+ "function": "cursor",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 1.4085000000000002e-05,
+ "cumulative_seconds": 0.004998834000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 173,
+ "function": "_rows",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.2874e-05,
+ "cumulative_seconds": 0.004452666
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 268,
+ "function": "_native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.8374000000000003e-05,
+ "cumulative_seconds": 0.00418375
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 311,
+ "function": "native_context",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.9625e-05,
+ "cumulative_seconds": 0.0041300420000000004
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1429,
+ "function": "_read_row_from_packet",
+ "primitive_calls": 428,
+ "calls": 428,
+ "self_seconds": 0.000943448,
+ "cumulative_seconds": 0.00309484
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 125,
+ "function": "_observer_status",
+ "primitive_calls": 8,
+ "calls": 8,
+ "self_seconds": 3.0752e-05,
+ "cumulative_seconds": 0.002996167
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1448,
+ "function": "_get_descriptions",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00031073,
+ "cumulative_seconds": 0.0027641270000000003
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 115,
+ "function": "_triggers",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.0840000000000001e-06,
+ "cumulative_seconds": 0.00249225
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 163,
+ "function": "read_length_coded_string",
+ "primitive_calls": 2712,
+ "calls": 2712,
+ "self_seconds": 0.0008386870000000001,
+ "cumulative_seconds": 0.002463757
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 107,
+ "function": "_ddl",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 2.6045000000000002e-05,
+ "cumulative_seconds": 0.002096749
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 898,
+ "function": "",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 5.2901e-05,
+ "cumulative_seconds": 0.002083417
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 185,
+ "function": "_metadata",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.8873e-05,
+ "cumulative_seconds": 0.001887292
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 195,
+ "function": "_chips",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 3.8842000000000005e-05,
+ "cumulative_seconds": 0.0018064630000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 899,
+ "function": "",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 0.00036378800000000003,
+ "cumulative_seconds": 0.0017890520000000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 101,
+ "function": "selected",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0127000000000001e-05,
+ "cumulative_seconds": 0.0017238330000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 183,
+ "function": "dumps",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 2.5582e-05,
+ "cumulative_seconds": 0.0017133740000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 183,
+ "function": "encode",
+ "primitive_calls": 11,
+ "calls": 11,
+ "self_seconds": 2.1001e-05,
+ "cumulative_seconds": 0.0016796250000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 188,
+ "function": "_validated_tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 6.0735e-05,
+ "cumulative_seconds": 0.0016687540000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 205,
+ "function": "iterencode",
+ "primitive_calls": 9,
+ "calls": 9,
+ "self_seconds": 0.001655624,
+ "cumulative_seconds": 0.001655624
+ },
+ {
+ "file": "python/workspace_curation.py",
+ "line": 170,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.041e-06,
+ "cumulative_seconds": 0.0016269580000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 37540,
+ "calls": 37540,
+ "self_seconds": 0.001579066,
+ "cumulative_seconds": 0.001579066
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 232,
+ "function": "__init__",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 8.209100000000001e-05,
+ "cumulative_seconds": 0.0015381350000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 166,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.5840000000000005e-06,
+ "cumulative_seconds": 0.001499333
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 748,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.0418e-05,
+ "cumulative_seconds": 0.001481625
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 778,
+ "function": "_epoch_page_identities",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.3036000000000005e-05,
+ "cumulative_seconds": 0.0014375000000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 236,
+ "function": "_parse_field_descriptor",
+ "primitive_calls": 169,
+ "calls": 169,
+ "self_seconds": 0.000321222,
+ "cumulative_seconds": 0.001434004
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 347,
+ "function": "token",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 1.1835000000000001e-05,
+ "cumulative_seconds": 0.001308874
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 359,
+ "function": "_fresh_token",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.9957000000000002e-05,
+ "cumulative_seconds": 0.001283249
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 311,
+ "function": "_scope",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 1.0588000000000001e-05,
+ "cumulative_seconds": 0.0012372090000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 282,
+ "function": "__and__",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 1.0955000000000001e-05,
+ "cumulative_seconds": 0.001231415
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 178,
+ "function": "restrict",
+ "primitive_calls": 14,
+ "calls": 14,
+ "self_seconds": 8.0241e-05,
+ "cumulative_seconds": 0.00122046
+ },
+ {
+ "file": "python/workspace_explorer.py",
+ "line": 60,
+ "function": "protocol_binding_header",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 1.0375000000000001e-05,
+ "cumulative_seconds": 0.001140292
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 223,
+ "function": "attributes",
+ "primitive_calls": 1006,
+ "calls": 1006,
+ "self_seconds": 0.000370603,
+ "cumulative_seconds": 0.001131045
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 34,
+ "function": "text",
+ "primitive_calls": 416,
+ "calls": 416,
+ "self_seconds": 0.000344121,
+ "cumulative_seconds": 0.001093559
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 299,
+ "function": "loads",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 5.7661e-05,
+ "cumulative_seconds": 0.0010433340000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 61,
+ "function": "read",
+ "primitive_calls": 2712,
+ "calls": 2712,
+ "self_seconds": 0.000883326,
+ "cumulative_seconds": 0.001001099
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 22,
+ "calls": 78,
+ "self_seconds": 5.9162e-05,
+ "cumulative_seconds": 0.0009938730000000002
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 40,
+ "function": "tags",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 6.4268e-05,
+ "cumulative_seconds": 0.000976505
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 20369,
+ "calls": 20369,
+ "self_seconds": 0.000925852,
+ "cumulative_seconds": 0.000956848
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 236,
+ "function": "source_scope",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.9469000000000002e-05,
+ "cumulative_seconds": 0.000953541
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 285,
+ "function": "__getitem__",
+ "primitive_calls": 745,
+ "calls": 745,
+ "self_seconds": 0.000133812,
+ "cumulative_seconds": 0.0009465220000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/decoder.py",
+ "line": 332,
+ "function": "decode",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 8.2749e-05,
+ "cumulative_seconds": 0.0009448740000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/datajoint/condition.py",
+ "line": 278,
+ "function": "make_condition",
+ "primitive_calls": 14,
+ "calls": 56,
+ "self_seconds": 0.000252074,
+ "cumulative_seconds": 0.00090821
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 42,
+ "function": "",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 5.4155000000000006e-05,
+ "cumulative_seconds": 0.000898625
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 869,
+ "function": "_execute_command",
+ "primitive_calls": 56,
+ "calls": 56,
+ "self_seconds": 0.00017737200000000002,
+ "cumulative_seconds": 0.0008883750000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 261,
+ "function": "filter_epoch_ids",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.886e-05,
+ "cumulative_seconds": 0.0008803330000000001
+ },
+ {
+ "file": "python/workspace_datastores.py",
+ "line": 192,
+ "function": "source_states",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 7.208e-06,
+ "cumulative_seconds": 0.0008665830000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 456,
+ "calls": 456,
+ "self_seconds": 0.000230088,
+ "cumulative_seconds": 0.0008293720000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/decoder.py",
+ "line": 343,
+ "function": "raw_decode",
+ "primitive_calls": 89,
+ "calls": 89,
+ "self_seconds": 0.000817002,
+ "cumulative_seconds": 0.000817002
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/wrappers/response.py",
+ "line": 601,
+ "function": "get_json",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.9590000000000004e-06,
+ "cumulative_seconds": 0.000812959
+ },
+ {
+ "file": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/.rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 181,
+ "function": "loads",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.5830000000000001e-06,
+ "cumulative_seconds": 0.00079675
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 20016,
+ "calls": 20016,
+ "self_seconds": 0.000750718,
+ "cumulative_seconds": 0.000750718
+ }
+ ],
+ "native_sql_calls": [
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0007289999630302191
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00034683296689763665
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00028316699899733067
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0003928749938495457
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00027595803840085864
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003370419726707041
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003447500057518482
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.0001521250233054161
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00021983298938721418
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00019200000679120421
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0005740419728681445
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0012521659955382347
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003592920256778598
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00018645799718797207
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0003763329586945474
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002121249563060701
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00033579202136024833
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00029208301566541195
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00018783297855407
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00022912502754479647
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004045419627800584
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.000322542036883533
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00017604196909815073
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00023958302335813642
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0003084169584326446
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00023937498917803168
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "seconds": 0.00037987501127645373
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`epoch_uuid`,`included`,`tags`,`review_state`,`revision`,`metadata_fingerprint` FROM `recording_workspace`.`curation` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))AND( ( (`epoch_uuid`='?') OR (",
+ "seconds": 0.0007103749667294323
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`dataset_uuid`,`created_at`,`artifact_sha256` FROM `recording_workspace`.`dataset_revision` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0002502910210750997
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.001726499991491437
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.000497999950312078
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00047483399976044893
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0002162500168196857
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.0002269999822601676
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00030795898055657744
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004632920026779175
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00035358400782570243
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.0002062080311588943
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004330839728936553
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00023366697132587433
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004393329727463424
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00040429196087643504
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00015970796812325716
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.000274707970675081
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00019929197151213884
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.000484999967738986
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0012307920260354877
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003642079536803067
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.0001947919954545796
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.000389167049434036
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002192090032622218
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003327500307932496
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00029916601488366723
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014970795018598437
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0002160840085707605
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003625000244937837
+ }
+ ],
+ "limits": "Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap."
+ },
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1070",
+ "ref": "const,const,const",
+ "rows": 20,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ }
+ ],
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.019975
+ }
+ },
+ "project_uuid": "e9bffb2b-d4b9-42e6-add4-e9fef1532cf7",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.088638
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.01782
+ }
+ },
+ "project_uuid": "e9bffb2b-d4b9-42e6-add4-e9fef1532cf7",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.131056
+ },
+ "seconds": 0.21972979203565046,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "workflow_peak_rss_before_durable_oracle_bytes": 170164224,
+ "durable_snapshot_verify_seconds": 0.03063724999083206,
+ "loaded_product_modules": {
+ "workspace_tree": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tree.py",
+ "workspace_predicates": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_predicates.py",
+ "workspace_cache_lifecycle": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_cache_lifecycle.py",
+ "workspace_metadata_objects": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_metadata_objects.py",
+ "workspace_disk_index": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_disk_index.py",
+ "workspace_catalog_identity": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_catalog_identity.py",
+ "recording_workspace": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/recording_workspace.py",
+ "workspace_recipes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_recipes.py",
+ "workspace_tree_code": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tree_code.py",
+ "workspace_service": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_service.py",
+ "workspace_tree_pages": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tree_pages.py",
+ "workspace_projects": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_projects.py",
+ "workspace_mysql_profile": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_mysql_profile.py",
+ "workspace_native_mysql": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_native_mysql.py",
+ "workspace_mysql_runtime": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_mysql_runtime.py",
+ "workspace_storage": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_storage.py",
+ "workspace_diff": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_diff.py",
+ "workspace_protocol_identity": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_protocol_identity.py",
+ "workspace_import_check": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_import_check.py",
+ "workspace_audit": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_audit.py",
+ "workspace_api": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_api.py",
+ "workspace_state_snapshot": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_state_snapshot.py",
+ "workspace_author_preferences": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_author_preferences.py",
+ "workspace_curation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_curation.py",
+ "workspace_annotations": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_annotations.py",
+ "workspace_shared_tag_index": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_shared_tag_index.py",
+ "workspace_paths": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_paths.py",
+ "workspace_desktop_paths": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_desktop_paths.py",
+ "workspace_startup_registry": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_startup_registry.py",
+ "workspace_explorer": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_explorer.py",
+ "workspace_datastores": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_datastores.py",
+ "workspace_suggestions": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_suggestions.py",
+ "workspace_tag_predicates": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tag_predicates.py",
+ "workspace_export_folder": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_export_folder.py",
+ "workspace_import_progress": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_import_progress.py",
+ "workspace_project_preferences": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_project_preferences.py",
+ "workspace_protocol_state": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_protocol_state.py",
+ "workspace_state_generation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_state_generation.py",
+ "workspace_project_servers": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_project_servers.py",
+ "workspace_launcher": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_launcher.py",
+ "workspace_app_routes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_app_routes.py",
+ "workspace_folder_browser": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_folder_browser.py",
+ "workspace_h5_inbox": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_h5_inbox.py",
+ "workspace_tag_exchange": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_tag_exchange.py",
+ "workspace_external_tags": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_external_tags.py",
+ "workspace_qc": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_qc.py",
+ "workspace_search": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_search.py",
+ "workspace_matlab_masks": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_matlab_masks.py",
+ "workspace_matlab_routes": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_matlab_routes.py",
+ "workspace_mask_refresh": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_mask_refresh.py",
+ "workspace_matching_epochs": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_matching_epochs.py",
+ "workspace_candidate_exports": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_candidate_exports.py",
+ "workspace_backup_scheduler": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_backup_scheduler.py",
+ "workspace_recovery_store": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_recovery_store.py",
+ "workspace_recovery_generation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_recovery_generation.py",
+ "workspace_annotation_preparation": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_annotation_preparation.py",
+ "workspace_native_tag_lookup": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_native_tag_lookup.py",
+ "workspace_lifecycle": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_lifecycle.py",
+ "workspace_native_tag_filter": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-7porgom9/python/workspace_native_tag_filter.py"
+ },
+ "owned_native_runtime_stopped": true,
+ "source_inventory_after": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_concurrent_requests.py": "f074a762536ab5bb52abe4ea56faada41babb1cd765512acb3caba18dd503648",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "17c37104693d46b416064ab05027c54added8d26ddd2ba6b7de202911ce5d592",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_event_paging.py": "e79cd1de3bd7469818e255cc01f087a608c11e799a48e81e4ca0ac29ff7a578e",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "3f4b346a59e1b78aeea51b1d1c2812582eed009b4cb2cc36831830e0d62601ca",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "f852f8af8b9795c63772800c3ca3cb7203fa0a1b81e0ef562ef848c1e31a06b0",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7fe035916c152aad2df6f41cc6f5c55e8d30cb4168f53c4ab1aa9e171bdd7cfc",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6d89f8a735c34e6ec3f7f3db291e5d21c1bd8e64a60e7b12563e82dc18eb9f24",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "8efc55e5fb6079072014c616223872464c0451c6d85bfcb2c61693f9baaed1ff",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "66a6a04c107053b3c0492e5ccd2e6f6e10b8479c107c0b58d5b5fd712800312c",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "a43e66ff47aaec64d0a862277b7751901775ab7f193f58c0106437b9fab1331b",
+ "python/workspace_native_tag_lookup.py": "d549e8084af074ab484759a2851fdacde7e632c334f55e2d65caee368e04ffd5",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "40ba54acf2c5e9983c2e2766179a3d365b30ad1cdc4955410571936f7dfb4954",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "1917ae5b36553048027a19e1b68f99677a754dae1501d1e52be1cd3faaa2b4a5",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "source_unchanged": true,
+ "harness_unchanged": true,
+ "seconds": 9.253306082973722,
+ "finished_at": "2026-09-30T15:17:25.584669+00:00",
+ "peak_rss_bytes": 170164224,
+ "passed": true
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-results.json b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-results.json
new file mode 100644
index 0000000..d854ec4
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-results.json
@@ -0,0 +1,888 @@
+{
+ "scope": "Native SQL lookup versus prior SQLite tag cache, same scientist workflow. Disposable native database, synthetic acquisition metadata, HTTP test-client timings.",
+ "passed": true,
+ "source_and_harness_unchanged_across_both_native_runs": true,
+ "samples_per_workflow_operation": 5,
+ "snapshot_manifest": "native-tag-source-pre-dictionary-snapshot.json",
+ "native_harness_sha256": "468173271c08020e7e1a7ce8b0fcdfc59693be0f9e3699a8caec342b2e77affd",
+ "scales": {
+ "500": {
+ "native_receipt": "native-tag-sequence-pre-dictionary-500.json",
+ "prior_receipt": "tag-sequence-after-500.json",
+ "comparison_ms": {
+ "first10_save": {
+ "prior_p50": 15.186292002908885,
+ "native_p50": 17.74270902387798,
+ "native_first_sample": 33.97225000662729,
+ "native_maximum": 33.97225000662729
+ },
+ "second10_save": {
+ "prior_p50": 13.435167027637362,
+ "native_p50": 16.915374959353358,
+ "native_first_sample": 18.42504198430106,
+ "native_maximum": 18.42504198430106
+ },
+ "filter20": {
+ "prior_p50": 31.089458032511175,
+ "native_p50": 23.176041955593973,
+ "native_first_sample": 27.24245801800862,
+ "native_maximum": 35.16616701381281
+ },
+ "tag10_then10_filter20_http_total": {
+ "prior_p50": 73.59912293031812,
+ "native_p50": 69.27029194775969,
+ "native_first_sample": 92.87745808251202,
+ "native_maximum": 92.87745808251202
+ },
+ "cell_save": {
+ "prior_p50": 6.7298339563421905,
+ "native_p50": 7.502541993744671,
+ "native_first_sample": 7.502541993744671,
+ "native_maximum": 18.350000027567148
+ },
+ "filter_cell100": {
+ "prior_p50": 41.47870797896758,
+ "native_p50": 38.77783298958093,
+ "native_first_sample": 38.77783298958093,
+ "native_maximum": 40.11900001205504
+ },
+ "filter_cell_and_epoch10": {
+ "prior_p50": 17.395791015587747,
+ "native_p50": 18.302250013221055,
+ "native_first_sample": 18.00766703672707,
+ "native_maximum": 20.278000039979815
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "prior_p50": 48.06662496412173,
+ "native_p50": 46.2803749833256,
+ "native_first_sample": 46.2803749833256,
+ "native_maximum": 58.46900003962219
+ }
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "checks": [
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 167
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1070",
+ "ref": "const,const,const",
+ "rows": 20,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ }
+ ],
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 3787,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 542,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 542,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1108",
+ "ref": null,
+ "rows": 9769,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index for group-by (scanning)"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "index",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1108",
+ "ref": null,
+ "rows": 7575,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 9,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ }
+ ],
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 0.0019153340253978968
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.0005384999676607549
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.0007603340200148523
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0002502499846741557
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 0.0018970409873872995
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.00045908300671726465
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.0006526659708470106
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00018674996681511402
+ }
+ ],
+ "autocomplete_empty_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 0.001917750050779432
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.005181541957426816
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.005963417002931237
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0002708330284804106
+ }
+ ]
+ },
+ "autocomplete_requests": {
+ "autocomplete_cold_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_warm_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_empty_diagnostic": {
+ "q": "",
+ "limit": 12,
+ "total": 41
+ }
+ },
+ "autocomplete_diagnostic_ms": {
+ "autocomplete_cold_diagnostic": 23.283249989617616,
+ "autocomplete_warm_diagnostic": 29.437625024002045,
+ "autocomplete_empty_diagnostic": 33.25066698016599
+ },
+ "initial_native_setup": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.054246
+ }
+ },
+ "project_uuid": "e9bffb2b-d4b9-42e6-add4-e9fef1532cf7",
+ "reused": false,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.233545
+ },
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.019975
+ }
+ },
+ "project_uuid": "e9bffb2b-d4b9-42e6-add4-e9fef1532cf7",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.088638
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.01782
+ }
+ },
+ "project_uuid": "e9bffb2b-d4b9-42e6-add4-e9fef1532cf7",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.131056
+ },
+ "seconds": 0.21972979203565046,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "same_server_native_reuse_filter_ms": 20.739166997373104,
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:17:25.252632+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "mysql_version": "8.4.2",
+ "runtime_seconds": 9.253306082973722,
+ "seed_seconds": 0.4073622500291094,
+ "seed_checkpoint_seconds": 0.18052658397937194,
+ "owned_native_runtime_stopped": true
+ },
+ "100000": {
+ "native_receipt": "native-tag-sequence-pre-dictionary-100000.json",
+ "prior_receipt": "tag-sequence-after-100000.json",
+ "comparison_ms": {
+ "first10_save": {
+ "prior_p50": 17.622290994040668,
+ "native_p50": 24.99641600297764,
+ "native_first_sample": 56.14608299219981,
+ "native_maximum": 56.14608299219981
+ },
+ "second10_save": {
+ "prior_p50": 16.18437498109415,
+ "native_p50": 23.883083020336926,
+ "native_first_sample": 23.883083020336926,
+ "native_maximum": 26.714917039498687
+ },
+ "filter20": {
+ "prior_p50": 119.21920796157792,
+ "native_p50": 126.01441703736782,
+ "native_first_sample": 227.16829099226743,
+ "native_maximum": 227.16829099226743
+ },
+ "tag10_then10_filter20_http_total": {
+ "prior_p50": 165.9542090492323,
+ "native_p50": 202.98049901612103,
+ "native_first_sample": 327.71416497416794,
+ "native_maximum": 327.71416497416794
+ },
+ "cell_save": {
+ "prior_p50": 7.809958013240248,
+ "native_p50": 8.758000039961189,
+ "native_first_sample": 10.468583030160517,
+ "native_maximum": 10.496834001969546
+ },
+ "filter_cell100": {
+ "prior_p50": 129.82887495309114,
+ "native_p50": 160.0392499822192,
+ "native_first_sample": 154.43866699934006,
+ "native_maximum": 163.26100000878796
+ },
+ "filter_cell_and_epoch10": {
+ "prior_p50": 106.19708400918171,
+ "native_p50": 124.78045799070969,
+ "native_first_sample": 123.39312495896593,
+ "native_maximum": 136.43654203042388
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "prior_p50": 137.83216592855752,
+ "native_p50": 168.2225419790484,
+ "native_first_sample": 164.90725002950057,
+ "native_maximum": 172.01900004874915
+ }
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "checks": [
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 33334
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 66666
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 66667
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1070",
+ "ref": "const,const,const",
+ "rows": 20,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ }
+ ],
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 827077,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 204390,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 204390,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1108",
+ "ref": null,
+ "rows": 2951014,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index for group-by (scanning)"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "index",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1108",
+ "ref": null,
+ "rows": 1654154,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 12,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ }
+ ],
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 3.526624999998603
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.08437079098075628
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.10236137500032783
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0004254169762134552
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 3.0357274590060115
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 0.09343941701808944
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 0.1018385419738479
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0004039999912492931
+ }
+ ],
+ "autocomplete_empty_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s GROUP BY project_uuid,tag",
+ "seconds": 2.991644333000295
+ },
+ {
+ "sql": "SELECT tag,COUNT(DISTINCT target_kind,target_uuid) AS amount FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag",
+ "seconds": 4.095707458036486
+ },
+ {
+ "sql": "SELECT tag,profile_uuid,MAX(CAST(CONCAT(target_kind,'/',target_uuid) AS BINARY)) AS position FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) GROUP BY project_uuid,tag,profile_uuid",
+ "seconds": 4.103170584014151
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0005604579928331077
+ }
+ ]
+ },
+ "autocomplete_requests": {
+ "autocomplete_cold_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_warm_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_empty_diagnostic": {
+ "q": "",
+ "limit": 12,
+ "total": 41
+ }
+ },
+ "autocomplete_diagnostic_ms": {
+ "autocomplete_cold_diagnostic": 3740.355083020404,
+ "autocomplete_warm_diagnostic": 3255.884749989491,
+ "autocomplete_empty_diagnostic": 11217.491208051797
+ },
+ "initial_native_setup": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 1.488408
+ }
+ },
+ "project_uuid": "3a791e27-101f-4a82-a7e8-f8be38297694",
+ "reused": false,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 1.672049
+ },
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.068396
+ }
+ },
+ "project_uuid": "3a791e27-101f-4a82-a7e8-f8be38297694",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.151797
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.052229
+ }
+ },
+ "project_uuid": "3a791e27-101f-4a82-a7e8-f8be38297694",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.165251
+ },
+ "seconds": 0.31707962503423914,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "same_server_native_reuse_filter_ms": 138.54400004493073,
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:21:20.833735+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "mysql_version": "8.4.2",
+ "runtime_seconds": 223.9138767500408,
+ "seed_seconds": 127.78472875000443,
+ "seed_checkpoint_seconds": 13.33677566703409,
+ "owned_native_runtime_stopped": true
+ }
+ },
+ "performance_complete": false,
+ "remaining_responsiveness_findings": [
+ "At 100k, prefix autocomplete takes 3.3–3.7 seconds, dominated by the dictionary covering scan.",
+ "At 100k, empty autocomplete takes 11.2 seconds, adding full-vocabulary counts and winning positions.",
+ "The 100k two-save/filter sequence takes 203.0 ms versus the prior 166.0 ms; bounded point-tag SQL does not remove all metadata work."
+ ]
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-results.md b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-results.md
new file mode 100644
index 0000000..4b61eb8
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-pre-dictionary-results.md
@@ -0,0 +1,65 @@
+# Native SQL tag workflow validation
+
+The final native SQL runs pass at 500 and 100,000 epochs using the same verified source snapshot. Exact-membership, author/revision, hierarchy, removal, recovery-mirror and native-index checks pass. Neither run calls the legacy SQLite tag membership or refresh path.
+
+Performance is mixed. The 500-epoch two-save/filter sequence improves from 73.6 to 69.3 ms, while the 100k sequence measures 203.0 ms versus the prior 166.0 ms. At 100k, prefix autocomplete still takes 3.3–3.7 seconds and an empty query takes 11.2 seconds. Passing correctness checks does not establish that the responsiveness work is complete.
+
+Each fixture has three profiles, five seeded tags per annotation, cell annotations, and two independent protocol curation scopes. Five complete cycles read revisions and durably tag 10 epochs, read revisions and tag another 10 in another cell, then immediately filter the exact 20.
+
+| Operation (median milliseconds) | 500 prior | 500 native | 100k prior | 100k native |
+|---|---:|---:|---:|---:|
+| First 10 epochs: durable POST | 15.2 | 17.7 | 17.6 | 25.0 |
+| Next 10 epochs: durable POST | 13.4 | 16.9 | 16.2 | 23.9 |
+| Filter after the two saves: exactly 20 epochs | 31.1 | 23.2 | 119.2 | 126.0 |
+| Two revision reads + two saves + filter | 73.6 | 69.3 | 166.0 | 203.0 |
+| One cell: durable POST | 6.7 | 7.5 | 7.8 | 8.8 |
+| Cell filter: exactly 100 inherited epochs | 41.5 | 38.8 | 129.8 | 160.0 |
+| Cell AND epoch filter: exactly 10 epochs | 17.4 | 18.3 | 106.2 | 124.8 |
+| Cell save + cell filter | 48.1 | 46.3 | 137.8 | 168.2 |
+
+The total uses paired per-cycle timings and includes both revision reads. POST timing ends after canonical MySQL save acknowledgment. The separately scheduled recovery mirror is flushed and checked after the operations. Timings include Flask HTTP client JSON decoding, not browser interaction or paint.
+
+The first filter request after seeding took 27.2 ms at 500 epochs and 227.2 ms at 100k; the first full sequences took 92.9 and 327.7 ms. Native application startup performs its metadata preparation; the harness performs no preparatory tag query or SQLite tag checkpoint after seeding. These are not cold MySQL/OS-cache measurements. Five samples do not establish tail-latency guarantees.
+
+## Index, persistence and correctness
+
+The actual 20-epoch filter uses the persistent `by_tag` covering index with `ref` access and an estimate of 20 lookup rows at both scales. Both runs distinguish `QC` from `qc` and NFC `é` from decomposed `é`; they verify AND, OR, NOT, inherited cell membership, direct-only epoch scope, exact author chips and revisions. Cell tagging changes one canonical cell record and creates no child annotation copies; both protocol curation scopes remain unchanged.
+
+A fresh Python lookup adapter reuses the persistent SQL table in the same server. Its exact-20 filter takes 20.7/138.5 ms at 500/100k. This is not a MySQL process restart. Initial preparation timing in the raw receipts precedes annotation seeding and is not a populated legacy-database migration benchmark.
+
+Both final backup receipts are `current`, with no pending/running work or error, and requested/completed sequence 34. The flushed recovery mirror matches all 63 tracked canonical records, including final removals. Both owned native servers stopped successfully.
+
+## Autocomplete diagnostic
+
+After the timed workflow, two calls use `q=gate&limit=12` and a third uses an empty query with `limit=12`. An independent seeded oracle verifies exact counts and profile authors for all returned tags. Each is one observation; first/repeated labels do not mean cold/warm storage.
+
+| Scale | First gate-prefix ms | Repeated gate-prefix ms | Empty query ms |
+|---|---:|---:|---:|
+| 500 | 23.3 | 29.4 | 33.3 |
+| 100,000 | 3740.4 | 3255.9 | 11217.5 |
+
+The final query shape reads the tag dictionary through `by_tag`, filters the dictionary with the existing Unicode casefold prefix semantics, counts matching memberships through `by_tag`, and reads the chosen authors through complete literal canonical keys. The empty query still counts the complete vocabulary. Actual SQL, per-statement timings and EXPLAIN output are in each receipt.
+
+The 100k optimizer chooses a covering project-prefix scan for the dictionary (`by_tag`, `ref`, 827,077 estimated rows), not a loose distinct-tag scan. That query takes 3.0–3.5 seconds and dominates prefix autocomplete. The bounded prefix count and winning-position queries each take roughly 0.1 second; complete primary-key author reads take less than 1 ms (`PRIMARY`, key length 331, three chosen rows). For the empty query, counts and winning positions additionally take about 4.1 seconds each. No manual `ANALYZE` or other query-plan preparation was added to this benchmark.
+
+| Scale/request | SQL statement durations (ms, execution order) |
+|---|---|
+| 500 / cold | 1.9 / 0.5 / 0.8 / 0.3 |
+| 500 / warm | 1.9 / 0.5 / 0.7 / 0.2 |
+| 500 / empty | 1.9 / 5.2 / 6.0 / 0.3 |
+| 100,000 / cold | 3526.6 / 84.4 / 102.4 / 0.4 |
+| 100,000 / warm | 3035.7 / 93.4 / 101.8 / 0.4 |
+| 100,000 / empty | 2991.6 / 4095.7 / 4103.2 / 0.6 |
+
+## Reproducible evidence
+
+- [Final 500-epoch receipt](native-tag-sequence-pre-dictionary-500.json) and [final 100,000-epoch receipt](native-tag-sequence-pre-dictionary-100000.json).
+- [Prior SQLite 500-epoch receipt](tag-sequence-after-500.json) and [prior 100,000-epoch receipt](tag-sequence-after-100000.json).
+- [Native harness](benchmark_native_tag_sequence.py), [machine-readable comparison](native-tag-sequence-pre-dictionary-results.json), and [source snapshot manifest](native-tag-source-pre-dictionary-snapshot.json).
+- [Preserved provisional pre-autocomplete-fix report](native-tag-sequence-pre-autocompletefix-results.md): 100k correctness passed, but concurrent source edits invalidated its overall receipt; its gate-prefix autocomplete took 9–10 seconds.
+
+The final runs used one private copy of Python/config, the three harness dependencies, frontend source/package metadata needed by audit fingerprints, and retinanalysis source. The existing pinned runtime binaries were reused. Copy-time source/copy hashes matched; every copied file remained unchanged through both final runs. Both receipts additionally attest identical before/after Python inventories, harness hashes and loaded-module paths. Concurrent edits in the working checkout do not alter this measured snapshot.
+
+Native harness SHA-256: `468173271c08020e7e1a7ce8b0fcdfc59693be0f9e3699a8caec342b2e77affd`.
+
+These receipts validate the stated workflow and checks; they do not certify a complete desktop release.
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-results.json b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-results.json
new file mode 100644
index 0000000..8c01322
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-results.json
@@ -0,0 +1,1462 @@
+{
+ "scope": "Native SQL lookup versus prior SQLite tag cache, same scientist workflow. Disposable native database, synthetic acquisition metadata, HTTP test-client timings.",
+ "passed": true,
+ "source_and_harness_unchanged_across_both_native_runs": true,
+ "samples_per_workflow_operation": 5,
+ "snapshot_manifest": "native-tag-source-snapshot.json",
+ "native_harness_sha256": "26d058c7a3b9c5f67071e38d70df66a566b37cef7b543656b0bcb521c04df133",
+ "scales": {
+ "500": {
+ "native_receipt": "native-tag-sequence-500.json",
+ "prior_receipt": "tag-sequence-after-500.json",
+ "comparison_ms": {
+ "first10_save": {
+ "prior_p50": 15.186292002908885,
+ "native_p50": 20.486874971538782,
+ "native_first_sample": 44.35537499375641,
+ "native_maximum": 44.35537499375641
+ },
+ "second10_save": {
+ "prior_p50": 13.435167027637362,
+ "native_p50": 19.969207991380244,
+ "native_first_sample": 20.426875038538128,
+ "native_maximum": 22.125250019598752
+ },
+ "filter20": {
+ "prior_p50": 31.089458032511175,
+ "native_p50": 24.433874990791082,
+ "native_first_sample": 33.14845898421481,
+ "native_maximum": 33.14845898421481
+ },
+ "tag10_then10_filter20_http_total": {
+ "prior_p50": 73.59912293031812,
+ "native_p50": 77.54574995487928,
+ "native_first_sample": 116.95775098633021,
+ "native_maximum": 116.95775098633021
+ },
+ "cell_save": {
+ "prior_p50": 6.7298339563421905,
+ "native_p50": 8.321208006236702,
+ "native_first_sample": 12.93837500270456,
+ "native_maximum": 12.93837500270456
+ },
+ "filter_cell100": {
+ "prior_p50": 41.47870797896758,
+ "native_p50": 43.737958010751754,
+ "native_first_sample": 43.737958010751754,
+ "native_maximum": 61.345916998106986
+ },
+ "filter_cell_and_epoch10": {
+ "prior_p50": 17.395791015587747,
+ "native_p50": 22.31991698499769,
+ "native_first_sample": 19.91629198892042,
+ "native_maximum": 27.239874994847924
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "prior_p50": 48.06662496412173,
+ "native_p50": 51.80883401772007,
+ "native_first_sample": 56.676333013456315,
+ "native_maximum": 72.56833399878815
+ }
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "checks": [
+ {
+ "name": "populated_native_migration_ready_before_timed_operations",
+ "passed": true
+ },
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "autocomplete_has_no_membership_aggregation",
+ "passed": true,
+ "membership_selects": 30,
+ "maximum_rows_per_seek": 1
+ },
+ {
+ "name": "autocomplete_compact_dictionary_fetches_are_bounded",
+ "passed": true,
+ "dictionary_selects": 9,
+ "scope": "Counts may scan matching compact entries; no unbounded dictionary rows are fetched for Python prefix filtering."
+ },
+ {
+ "name": "autocomplete_membership_winner_seeks_are_indexed",
+ "passed": true,
+ "plans": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1098",
+ "ref": "const,const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Backward index scan; Using index"
+ }
+ ]
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 167
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 333
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 250
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": "const,const",
+ "rows": 20,
+ "filtered": 10.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ }
+ ],
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": "const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using index condition; Using filesort"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_authors",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1098",
+ "ref": "const,const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Backward index scan; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 41,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_rank) WHERE project_uuid=%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_rank",
+ "key": "by_rank",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_authors",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 24,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 9,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ }
+ ],
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.00012283300748094916,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "seconds": 0.0001301250304095447,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.00014424999244511127,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "seconds": 0.00013224995927885175,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001396249863319099,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013120798394083977,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011324998922646046,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00016866601072251797,
+ "result_rows": 3
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.0001443749642930925,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "seconds": 0.000144040968734771,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.0001740000443533063,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "seconds": 0.00016950001008808613,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00016325002070516348,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013179198140278459,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00019791699014604092,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00021045899484306574,
+ "result_rows": 3
+ }
+ ],
+ "autocomplete_empty_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.0002182909520342946,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s",
+ "seconds": 0.00022295804228633642,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_rank) WHERE project_uuid=%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.0002453330089338124,
+ "result_rows": 12
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0002537500113248825,
+ "result_rows": 24
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00014916702639311552,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013404199853539467,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011974998051300645,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00017691700486466289,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013070798013359308,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011962500866502523,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012362503912299871,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012741703540086746,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001378749730065465,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013474997831508517,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001284580212086439,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001437499886378646,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013579102233052254,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013800000306218863,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011987495236098766,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011399999493733048,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011145800817757845,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011766701936721802,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011616700794547796,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012120796600356698,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00020866701379418373,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00018604198703542352,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010916695464402437,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010958401253446937,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.0004837500164285302,
+ "result_rows": 9
+ }
+ ]
+ },
+ "autocomplete_requests": {
+ "autocomplete_cold_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_warm_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_empty_diagnostic": {
+ "q": "",
+ "limit": 12,
+ "total": 41
+ }
+ },
+ "autocomplete_diagnostic_ms": {
+ "autocomplete_cold_diagnostic": 21.61020803032443,
+ "autocomplete_warm_diagnostic": 24.96337500633672,
+ "autocomplete_empty_diagnostic": 30.342624988406897
+ },
+ "initial_native_setup": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.06283
+ }
+ },
+ "project_uuid": "69b6a7e7-762d-4adf-ad65-b1c3a8eff7f1",
+ "reused": false,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.436276
+ },
+ "fixture_construction": {
+ "strategy": "canonical seed before normal app startup and populated native migration",
+ "native_derived_tables_absent_during_seed": true,
+ "canonical_shared_records": 1515,
+ "canonical_curation_records": 1000,
+ "product_triggers_disabled_during_timed_operations": false
+ },
+ "populated_native_startup_seconds": 1.002701541990973,
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.022153
+ }
+ },
+ "project_uuid": "69b6a7e7-762d-4adf-ad65-b1c3a8eff7f1",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.102921
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.02182
+ }
+ },
+ "project_uuid": "69b6a7e7-762d-4adf-ad65-b1c3a8eff7f1",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.175279
+ },
+ "seconds": 0.27823945798445493,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "same_server_native_reuse_filter_ms": 26.880667021032423,
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:39:04.669427+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "mysql_version": "8.4.2",
+ "runtime_seconds": 11.342676208994817,
+ "seed_seconds": 0.08865733398124576,
+ "seed_checkpoint_seconds": 0.05130733299301937,
+ "owned_native_runtime_stopped": true
+ },
+ "100000": {
+ "native_receipt": "native-tag-sequence-100000.json",
+ "prior_receipt": "tag-sequence-after-100000.json",
+ "comparison_ms": {
+ "first10_save": {
+ "prior_p50": 17.622290994040668,
+ "native_p50": 20.654458028730005,
+ "native_first_sample": 50.29199999989942,
+ "native_maximum": 50.29199999989942
+ },
+ "second10_save": {
+ "prior_p50": 16.18437498109415,
+ "native_p50": 24.3177090305835,
+ "native_first_sample": 22.857792035210878,
+ "native_maximum": 24.691416998393834
+ },
+ "filter20": {
+ "prior_p50": 119.21920796157792,
+ "native_p50": 128.1725830049254,
+ "native_first_sample": 231.92245897371322,
+ "native_maximum": 231.92245897371322
+ },
+ "tag10_then10_filter20_http_total": {
+ "prior_p50": 165.9542090492323,
+ "native_p50": 191.6327920043841,
+ "native_first_sample": 331.8292510230094,
+ "native_maximum": 331.8292510230094
+ },
+ "cell_save": {
+ "prior_p50": 7.809958013240248,
+ "native_p50": 7.998415967449546,
+ "native_first_sample": 7.998415967449546,
+ "native_maximum": 9.052958048414439
+ },
+ "filter_cell100": {
+ "prior_p50": 129.82887495309114,
+ "native_p50": 156.9541670032777,
+ "native_first_sample": 156.9541670032777,
+ "native_maximum": 164.91170797962695
+ },
+ "filter_cell_and_epoch10": {
+ "prior_p50": 106.19708400918171,
+ "native_p50": 115.23075000150129,
+ "native_first_sample": 122.18404200393707,
+ "native_maximum": 122.18404200393707
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "prior_p50": 137.83216592855752,
+ "native_p50": 164.95258297072724,
+ "native_first_sample": 164.95258297072724,
+ "native_maximum": 172.59591701440513
+ }
+ },
+ "sqlite_tag_cache_calls": {
+ "membership_index": 0,
+ "refresh": 0
+ },
+ "checks": [
+ {
+ "name": "populated_native_migration_ready_before_timed_operations",
+ "passed": true
+ },
+ {
+ "name": "native_autocomplete_exact_counts_and_profile_authors",
+ "passed": true
+ },
+ {
+ "name": "autocomplete_has_no_membership_aggregation",
+ "passed": true,
+ "membership_selects": 30,
+ "maximum_rows_per_seek": 1
+ },
+ {
+ "name": "autocomplete_compact_dictionary_fetches_are_bounded",
+ "passed": true,
+ "dictionary_selects": 9,
+ "scope": "Counts may scan matching compact entries; no unbounded dictionary rows are fetched for Python prefix filtering."
+ },
+ {
+ "name": "autocomplete_membership_winner_seeks_are_indexed",
+ "passed": true,
+ "plans": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1098",
+ "ref": "const,const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Backward index scan; Using index"
+ }
+ ]
+ },
+ {
+ "name": "actual_filter_uses_persistent_by_tag_sql_index",
+ "passed": true,
+ "queries": 1
+ },
+ {
+ "name": "persistent_native_lookup_reused_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "exact_native_case_upper",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_case_lower",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_unicode_nfc",
+ "passed": true,
+ "count": 33334
+ },
+ {
+ "name": "exact_native_unicode_nfd",
+ "passed": true,
+ "count": 66666
+ },
+ {
+ "name": "exact_native_or",
+ "passed": true,
+ "count": 66667
+ },
+ {
+ "name": "exact_native_not_with_inheritance",
+ "passed": true,
+ "count": 50000
+ },
+ {
+ "name": "exact_native_inherited_excluded_from_direct",
+ "passed": true,
+ "count": 0
+ },
+ {
+ "name": "no_sqlite_tag_membership_or_refresh_used",
+ "passed": true,
+ "calls": {
+ "membership_index": 0,
+ "refresh": 0
+ }
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "native_lookup_query_plans": [
+ {
+ "sql": "SELECT DISTINCT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND target_kind IN (%s) AND tag=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1060",
+ "ref": "const,const",
+ "rows": 20,
+ "filtered": 10.0,
+ "Extra": "Using where; Using index; Using temporary"
+ }
+ ]
+ }
+ ],
+ "autocomplete_query_plans": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": "const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "1571",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using index condition; Using filesort"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_authors",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_lookup",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_tag",
+ "key": "by_tag",
+ "key_len": "1098",
+ "ref": "const,const,const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using where; Backward index scan; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 3,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_prefix",
+ "key": "by_prefix",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 41,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_rank) WHERE project_uuid=%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_dictionary",
+ "partitions": null,
+ "type": "ref",
+ "possible_keys": "by_rank",
+ "key": "by_rank",
+ "key_len": "38",
+ "ref": "const",
+ "rows": 1,
+ "filtered": 100.0,
+ "Extra": "Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "app_shared_tag_authors",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "1060",
+ "ref": null,
+ "rows": 24,
+ "filtered": 100.0,
+ "Extra": "Using where; Using index"
+ }
+ ]
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "explain": [
+ {
+ "id": 1,
+ "select_type": "SIMPLE",
+ "table": "shared_annotation",
+ "partitions": null,
+ "type": "range",
+ "possible_keys": "PRIMARY",
+ "key": "PRIMARY",
+ "key_len": "331",
+ "ref": null,
+ "rows": 12,
+ "filtered": 100.0,
+ "Extra": "Using where"
+ }
+ ]
+ }
+ ],
+ "autocomplete_sql": {
+ "autocomplete_cold_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.0001382920308969915,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "seconds": 0.0001492909505032003,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.00015729200094938278,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "seconds": 0.0001496669719927013,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00015233299927785993,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013812503311783075,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013883301289752126,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00019345799228176475,
+ "result_rows": 3
+ }
+ ],
+ "autocomplete_warm_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.00014100002590566874,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s",
+ "seconds": 0.00015254097525030375,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key>=%s AND fold_key<%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.00016245798906311393,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s)",
+ "seconds": 0.00017329101683571935,
+ "result_rows": 3
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00016587501158937812,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00014037505025044084,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001366669894196093,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00019987503765150905,
+ "result_rows": 3
+ }
+ ],
+ "autocomplete_empty_diagnostic": [
+ {
+ "sql": "SELECT tag FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s AND fold_key IS NULL LIMIT 200",
+ "seconds": 0.0002447500010021031,
+ "result_rows": 0
+ },
+ {
+ "sql": "SELECT COUNT(*) AS amount FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_prefix) WHERE project_uuid=%s",
+ "seconds": 0.00015116599388420582,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT tag,target_count FROM `recording_workspace`.`app_shared_tag_dictionary` FORCE INDEX (by_rank) WHERE project_uuid=%s ORDER BY target_count DESC,fold_key,tag LIMIT %s",
+ "seconds": 0.00016816600691527128,
+ "result_rows": 12
+ },
+ {
+ "sql": "SELECT tag,profile_uuid FROM `recording_workspace`.`app_shared_tag_authors` WHERE project_uuid=%s AND tag IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00023812497965991497,
+ "result_rows": 24
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00014874996850267053,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00017024995759129524,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00029045797418802977,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012725003762170672,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00020070798927918077,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001289580250158906,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013695796951651573,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012425001477822661,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00012670899741351604,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011995801469311118,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011025002459064126,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011108402395620942,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.0001033750013448298,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011508399620652199,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011308299144729972,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00014779099728912115,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011537497630342841,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010775000555440784,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011224998161196709,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010175001807510853,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010091596050187945,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00010791700333356857,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00013529200805351138,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid FROM `recording_workspace`.`app_shared_tag_lookup` FORCE INDEX (by_tag) WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1",
+ "seconds": 0.00011187494965270162,
+ "result_rows": 1
+ },
+ {
+ "sql": "SELECT target_kind,target_uuid,profile_uuid,author_name FROM `recording_workspace`.`shared_annotation` WHERE project_uuid=%s AND (target_kind,target_uuid,profile_uuid) IN ((%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s),(%s,%s,%s))",
+ "seconds": 0.00030766602139919996,
+ "result_rows": 12
+ }
+ ]
+ },
+ "autocomplete_requests": {
+ "autocomplete_cold_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_warm_diagnostic": {
+ "q": "gate",
+ "limit": 12,
+ "total": 3
+ },
+ "autocomplete_empty_diagnostic": {
+ "q": "",
+ "limit": 12,
+ "total": 41
+ }
+ },
+ "autocomplete_diagnostic_ms": {
+ "autocomplete_cold_diagnostic": 22.329957981128246,
+ "autocomplete_warm_diagnostic": 20.90041700284928,
+ "autocomplete_empty_diagnostic": 25.776874972507358
+ },
+ "initial_native_setup": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 1.508189
+ }
+ },
+ "project_uuid": "4dff9ec3-f69a-4387-9e77-a43eb4b14f4a",
+ "reused": false,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 128.9519
+ },
+ "fixture_construction": {
+ "strategy": "canonical seed before normal app startup and populated native migration",
+ "native_derived_tables_absent_during_seed": true,
+ "canonical_shared_records": 303000,
+ "canonical_curation_records": 200000,
+ "product_triggers_disabled_during_timed_operations": false
+ },
+ "populated_native_startup_seconds": 144.54315195797244,
+ "same_server_native_lookup_reuse": {
+ "checkpoint": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.054597
+ }
+ },
+ "project_uuid": "4dff9ec3-f69a-4387-9e77-a43eb4b14f4a",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.139045
+ },
+ "reopened": {
+ "status": "ready",
+ "storage": "native_sql",
+ "phases": {
+ "preparing_protocol_reads": {
+ "seconds": 0.052419
+ }
+ },
+ "project_uuid": "4dff9ec3-f69a-4387-9e77-a43eb4b14f4a",
+ "reused": true,
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "phase": "complete",
+ "seconds": 0.181751
+ },
+ "seconds": 0.32084808399667963,
+ "scope": "Native lookup checkpoint followed by a fresh Python lookup adapter in the same server; not a MySQL-process restart."
+ },
+ "same_server_native_reuse_filter_ms": 120.17941597150639,
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T15:43:05.619622+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "mysql_version": "8.4.2",
+ "runtime_seconds": 219.8653925419785,
+ "seed_seconds": 14.418896374991164,
+ "seed_checkpoint_seconds": 0.03377008403185755,
+ "owned_native_runtime_stopped": true
+ }
+ },
+ "query_architecture_gates_passed": true,
+ "workflow_uniformly_faster_than_prior_sqlite": false,
+ "limitations": [
+ "The 100k two-save/filter median is 191.6 ms versus the prior 166.0 ms; other metadata and response work remains.",
+ "Populated 100k application startup/migration took 144.543 seconds; subsequent same-server lookup-adapter preparation reused the structures in 0.182 seconds.",
+ "The exact empty-query total counts 41 compact dictionary entries; no membership aggregation or unbounded dictionary row fetch occurs."
+ ],
+ "autocomplete_100k_sql_summary": {
+ "autocomplete_cold_diagnostic": {
+ "statements": 8,
+ "seconds": 0.0012172909919172525,
+ "maximum_statement_seconds": 0.00019345799228176475
+ },
+ "autocomplete_warm_diagnostic": {
+ "statements": 8,
+ "seconds": 0.001272082095965743,
+ "maximum_statement_seconds": 0.00019987503765150905
+ },
+ "autocomplete_empty_diagnostic": {
+ "statements": 29,
+ "seconds": 0.0042779138893820345,
+ "maximum_statement_seconds": 0.00030766602139919996
+ }
+ }
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-sequence-results.md b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-results.md
new file mode 100644
index 0000000..55ffdfe
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-sequence-results.md
@@ -0,0 +1,62 @@
+# Native SQL tag workflow validation
+
+The final native SQL runs pass at 500 and 100,000 epochs using the same verified source snapshot. Exact-membership, author/revision, hierarchy, removal, recovery-mirror and native-index checks pass. Neither run calls the legacy SQLite tag membership or refresh path. At 100k, autocomplete now takes 21–26 ms, versus 3.3–3.7 seconds for a prefix and 11.2 seconds for an empty query before the compact dictionary.
+
+Each fixture has three profiles, five seeded tags per annotation, cell annotations, and two independent protocol curation scopes. Canonical rows are bulk-seeded before normal application startup installs and backfills native tag structures. No triggers are disabled for timed operations. Five complete cycles read revisions and durably tag 10 epochs, read revisions and tag another 10 in another cell, then immediately filter the exact 20.
+
+| Operation (median milliseconds) | 500 prior | 500 native | 100k prior | 100k native |
+|---|---:|---:|---:|---:|
+| First 10 epochs: durable POST | 15.2 | 20.5 | 17.6 | 20.7 |
+| Next 10 epochs: durable POST | 13.4 | 20.0 | 16.2 | 24.3 |
+| Filter after the two saves: exactly 20 epochs | 31.1 | 24.4 | 119.2 | 128.2 |
+| Two revision reads + two saves + filter | 73.6 | 77.5 | 166.0 | 191.6 |
+| One cell: durable POST | 6.7 | 8.3 | 7.8 | 8.0 |
+| Cell filter: exactly 100 inherited epochs | 41.5 | 43.7 | 129.8 | 157.0 |
+| Cell AND epoch filter: exactly 10 epochs | 17.4 | 22.3 | 106.2 | 115.2 |
+| Cell save + cell filter | 48.1 | 51.8 | 137.8 | 165.0 |
+
+The broader save/filter workflow has not become uniformly faster: its 100k median is 191.6 ms versus the prior 166.0 ms. The native point lookup is indexed, while other metadata and response work remains. The total uses paired per-cycle timings and includes both revision reads. POST timing ends after canonical MySQL save acknowledgment. The separately scheduled recovery mirror is flushed and checked after the operations. Timings include Flask HTTP client JSON decoding, not browser interaction or paint.
+
+The first filter request after seeding took 33.1 ms at 500 epochs and 231.9 ms at 100k; the first full sequences took 117.0 and 331.8 ms. Native application startup performs its metadata preparation; the harness performs no preparatory tag query or SQLite tag checkpoint after seeding. These are not cold MySQL/OS-cache measurements. Five samples do not establish tail-latency guarantees.
+
+## Index, persistence and correctness
+
+The actual 20-epoch filter uses the persistent `by_tag` covering index with `ref` access and an estimate of 20 lookup rows at both scales. Both runs distinguish `QC` from `qc` and NFC `é` from decomposed `é`; they verify AND, OR, NOT, inherited cell membership, direct-only epoch scope, exact author chips and revisions. Cell tagging changes one canonical cell record and creates no child annotation copies; both protocol curation scopes remain unchanged.
+
+A fresh Python lookup adapter reuses the persistent SQL table in the same server. Its exact-20 filter takes 26.9/120.2 ms at 500/100k. This is not a MySQL process restart. Initial preparation now measures normal startup on the populated canonical fixture, including native backfill, content proof and bounded protocol preparation. The raw receipt separately records total application startup and canonical fixture construction.
+
+The populated migration cost is explicit: canonical seeding took 0.089/14.419 seconds at 500/100k; normal application startup took 1.003/144.543 seconds, including 0.436/128.952 seconds in native annotation preparation. At 100k this creates lookup/dictionary structures for 303,000 canonical shared-annotation records. Subsequent same-server preparation with a fresh lookup adapter reused those structures in 0.182 seconds; this does not measure a full desktop restart.
+
+Both final backup receipts are `current`, with no pending/running work or error, and requested/completed sequence 34. The flushed recovery mirror matches all 63 tracked canonical records, including final removals. Both owned native servers stopped successfully.
+
+## Autocomplete diagnostic
+
+After the timed workflow, two calls use `q=gate&limit=12` and a third uses an empty query with `limit=12`. An independent seeded oracle verifies exact counts and profile authors for all returned tags. Each is one observation; first/repeated labels do not mean cold/warm storage.
+
+| Scale | First gate-prefix ms | Repeated gate-prefix ms | Empty query ms |
+|---|---:|---:|---:|
+| 500 | 21.6 | 25.0 | 30.3 |
+| 100,000 | 22.3 | 20.9 | 25.8 |
+
+Autocomplete now queries the persisted compact dictionary with maintained distinct-target counts and stored Unicode fold keys. Profile references come from the compact author table; any membership reads are bounded `by_tag` winner seeks returning at most one row, followed by complete canonical primary-key reads. Harness gates reject membership aggregates and unbounded dictionary row fetches. An exact total may count matching compact dictionary entries. Actual SQL, returned row counts, per-statement timings and EXPLAIN output are preserved.
+
+At 100k, prefix count/ranking uses `by_prefix` range access over three dictionary entries. Empty-query ranking uses `by_rank` with a 12-row limit; its exact total counts 41 compact dictionary entries. Membership winner reads use `by_tag` with project/tag/profile keys, backward index traversal and `LIMIT 1`; canonical author reads use the full primary key (length 331). There is no membership aggregation or unbounded dictionary fetch into Python.
+
+| 100k request | Traced tag SQL statements | Combined tag SQL time | Slowest statement |
+|---|---:|---:|---:|
+| First gate prefix | 8 | 1.22 ms | 0.19 ms |
+| Repeated gate prefix | 8 | 1.27 ms | 0.20 ms |
+| Empty query | 29 | 4.28 ms | 0.31 ms |
+
+## Reproducible evidence
+
+- [Final 500-epoch receipt](native-tag-sequence-500.json) and [final 100,000-epoch receipt](native-tag-sequence-100000.json).
+- [Prior SQLite 500-epoch receipt](tag-sequence-after-500.json) and [prior 100,000-epoch receipt](tag-sequence-after-100000.json).
+- [Native harness](benchmark_native_tag_sequence.py), [machine-readable comparison](native-tag-sequence-results.json), and [source snapshot manifest](native-tag-source-snapshot.json).
+- [Pre-dictionary baseline](native-tag-sequence-pre-dictionary-results.md): immutable-source correctness passed, but 100k prefix autocomplete took 3.3–3.7 seconds and empty autocomplete 11.2 seconds. The earlier [provisional report](native-tag-sequence-pre-autocompletefix-results.md) remains preserved with its source-change limitation.
+
+The final runs used one private copy of Python/config, the three harness dependencies, frontend source/package metadata needed by audit fingerprints, and retinanalysis source. The existing pinned runtime binaries were reused. Copy-time source/copy hashes matched; every copied file remained unchanged through both final runs. Both receipts additionally attest identical before/after Python inventories, harness hashes and loaded-module paths. Concurrent edits in the working checkout do not alter this measured snapshot.
+
+Native harness SHA-256: `26d058c7a3b9c5f67071e38d70df66a566b37cef7b543656b0bcb521c04df133`.
+
+These receipts validate the stated workflow and checks; they do not certify a complete desktop release.
diff --git a/docs/dev/scale-audit-2026-09-29/native-tag-source-snapshot.json b/docs/dev/scale-audit-2026-09-29/native-tag-source-snapshot.json
new file mode 100644
index 0000000..0d275bb
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native-tag-source-snapshot.json
@@ -0,0 +1,564 @@
+{
+ "created_at": "2026-09-30T15:38:40.119352+00:00",
+ "source_root": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI",
+ "snapshot_root": "/private/var/folders/41/tclkvrfs1v9386sxps6_w5t40000gn/T/rieke-native-tags-frozen-3hwp8_rq",
+ "copy_was_stable": true,
+ "source_files": {
+ ".rieke-runtime/retinanalysis/src/.DS_Store": "930bc23ea1d7e34b68d9e4a27682d176bce3e59fd319dc837c65f3d9872a315d",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/.DS_Store": "a63ad641449dc2397feb8049373ee6956c282557fe2a0355bef20883596ed956",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/__init__.py": "77cfc5692391f123c633adb6d52d96a12d5a6200ac7079f6ddfbeb130d2f27b9",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/_database.py": "ac13b09d29067430928248f6713571231da2a8d8231c2917563b9bf3f7435b3c",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cell_types.csv": "da09e4cf6b3c12f67688ac460ba8fdfee94da69c96b0c18b3dd60dfc077bbae6",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/classification_files/20250514C/movingLetters_chunk3_autoClassification.txt": "cc5dd63d7904923d1be10a076b2fbf4a7f781ac490016ef0da06ea3e412e5637",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/mouse/m_cone_spectrum.txt": "b755579e2b3d0b75de379c93e3a21d0b5f50b3dffd7fe5b780373fd713bce9f9",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/mouse/rod_spectrum.txt": "87cf8c8fced8e775d29d501108163b802dc418755db4ff6988c793e6ec7a8706",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/mouse/s_cone_spectrum.txt": "ad27d242413fe15f1082a4142877f96716b64030f511d91ba5c978b3eb8f9bdb",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/primate/l_cone_spectrum.txt": "4ee94d5e1062b6fa972c6f7501739d5ae4bffc27ff4a26c8315f61f06eb96d99",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/primate/m_cone_spectrum.txt": "3d3fb74bf345422bcfc47b93067a3dc894eccda1e9a2f118f3c387e3f85d5698",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/primate/rod_spectrum.txt": "7782cf580e6627e045941e94371f848da4e237f2ad114ff1b356e3fa76fb8eb7",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/primate/s_cone_spectrum.txt": "b179d17b4423fe13bef40aaa0e9653d75734f8845745033e9a69d93ea26eb057",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/zebrafish/l_cone_spectrum.txt": "657b31bf03681caf4c213a0ce7d1d3bafd9961909ddee2d1c79a673e84b1f584",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/zebrafish/m_cone_spectrum.txt": "b4f28c22ac948b8774d516add9cd913b9b30997eb3ad2eeb550c223752e678b7",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/zebrafish/rod_spectrum.txt": "eea4957a60ea0dd2a34675e4ba3719f2c59d2daf4fa79b602cd9b854dbdb5271",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/zebrafish/s_cone_spectrum.txt": "28fbfbc6b2c8f2697089231e290905ed2f5104bde9404d211669f0c1af52b6c6",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/cone_spectra/zebrafish/uv_cone_spectrum.txt": "7bfa3ef8bbe4e96ce59b1628130d864d00d731620856331e80e2df24a671a6c2",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/nd_filters/suction505.json": "f8a83db6d52c88e060f790479df1abfb7e20156786af00db6334aa4c0537fe58",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/nd_filters/suction565.json": "f3992f83fc98b904f33438466886642d7dd60de105ad495f66febc79ab6c5029",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/assets/nd_filters/tinymea.json": "2070440c0bd7f38b6273f9b988ece2da7b2b21366ab9bcf8a5c5e8d7ca970bf1",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/analysis_chunk.py": "02caa277411066356ac04ee54722d7d6fd4ecc1e4e28e344b36b0ac06f2b73f2",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/dedup.py": "db5298777a41f8fb43b9b596bd739eed408f631e916c241cee8460843fcbcca5",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/mea_pipeline.py": "3740fd4905b663cc2443c53be072d9c8267fcf18ff8d5a3ad20f4aadf5121c64",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/qc.py": "4deb28211c07f4ad3386df30f4df29075758e78da2f7ad14f35b30ddf98463f4",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/raw.py": "85b71022f6e3b80a2610567916e385c7e803f34108179b59c2baa97b7ef005be",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/response.py": "8ebcee32916f6e081597a7b8b06acabe1029ca45140eb411a399f63a869bc7a6",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/sc_pipeline.py": "038d2afb60a4b0c6a201f88bc7223cf0665819da750e8452bbc6465c1652b3c9",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/classes/stim.py": "8e8b6782f6b13046b7c5abf82250c6cf176638b356d2ac81e07ec808365395a8",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/config/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/config/config.ini": "737be1cbac752a8cd62746b0c9d4393b4344c4f2245356dc14b76d75cb094c61",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/config/config_create.py": "b60fef2624c552e66a999fc763c8364249f4df1049d12af62a67d1ac7e205fff",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/config/schema.py": "13abf98c831dd4906e8e3c1fe3c79e2e0b28269ef329f5682def12beb82cb5bb",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/config/settings.py": "da0c4354f5d19c02cd8bb826d63c9cb8461f892bc666986a0ee8a3b20ad63909",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/preprocessing/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/preprocessing/ei_merge.py": "73b202081c9e9d2745e53177ef1a5bfe107d15569ece75845ce2b8ea91ac7cdb",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/preprocessing/ks_to_vision.py": "4522cda7967eeb8f5d2efbd170b55548df3697c68c6b08d452c398727566916d",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/preprocessing/rfs.py": "9e60c6a85b5c965bc0479adf6d6dc1d3ae6c8eb775b963351a4fa1fe33fba2f4",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/preprocessing/sta.py": "1194b1283ed08aba331d4509102aa1e9f5840190170f8dd05b672172dd53d9e8",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/sorting/run_vision_ss.sh": "64d3242812cd422f8916a35075e8e3ec1a6bf35210a2e6b5a6b6c9b4e085e75d",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/__init__.py": "f55619d98ca30d15d719ab6b636333cdd7984cf2dbf8e224995a0778f0f1a69a",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/database_pop.py": "54cf9c50e646308b4ce1e59065c2b1c06b09ae88a8c36ea6099ac75cee061411",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/database_utils.py": "c3dbf59feb749b6caa8c555cfc6d6b91d32d60d9b97f83d148972b821436d3a7",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/datajoint_utils.py": "6fb4f8bebf1acb4552576e560a29ead16b87836c6914b9b4f79736203d2855b5",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/ei_utils.py": "2b0b65525272ad85df9851d5268facaf7b8f7580516156010a0cf021544b455b",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/parse_data.py": "0d63fa4df6e087cda60fd85305ea3c4a7d287949abab325b9f1dd2e5f12779d2",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/regen.py": "609021eb6c5bed9588a9617b97b825ffdb897fd19ab59b94d69f5fa6f1d591f5",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/spike_detector.py": "6ab1419b5bf7218b5fb00e8435ecd0ab8b6daa1ee33a70550911823617aed739",
+ ".rieke-runtime/retinanalysis/src/retinanalysis/utils/vision_utils.py": "3fce9b896cc3639ea4005ecc272a02ce2debdcc0029f4687197d26c09373b2c4",
+ "docs/dev/scale-audit-2026-09-29/benchmark_native_tag_sequence.py": "26d058c7a3b9c5f67071e38d70df66a566b37cef7b543656b0bcb521c04df133",
+ "docs/dev/scale-audit-2026-09-29/benchmark_scientist_tag_workflow.py": "272fe050c8762ed4ea45bd013e236e8723d8fe46f5e7e293c58e2edf1fd52ddc",
+ "docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py": "38caf8353c7040d2ba691edde81de751324a259a87acdf9f77bd02823d9d8e92",
+ "python/.pytest_cache/.gitignore": "3ed731b65d06150c138e2dadb0be0697550888a6b47eb8c45ecc9adba8b8e9bd",
+ "python/.pytest_cache/CACHEDIR.TAG": "37dc88ef9a0abeddbe81053a6dd8fdfb13afb613045ea1eb4a5c815a74a3bde4",
+ "python/.pytest_cache/README.md": "73fd6fccdd802c419a6b2d983d6c3173b7da97558ac4b589edec2dfe443db9ad",
+ "python/.pytest_cache/v/cache/lastfailed": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
+ "python/.pytest_cache/v/cache/nodeids": "2fde60015384781ae0e52876e70ab08fa1c7c8725e8f61d9f681cb637d468d45",
+ "python/.pytest_cache/v/cache/stepwise": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "d81d9c5e03e62024c30474612596ee3fec2753a745e39c2db005010c85b8ffff",
+ "python/requirements.txt": "22b6b140bffbde7685a14e636d1580177d6f17a09fcc6880687d35e5399135e0",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_concurrent_requests.py": "f074a762536ab5bb52abe4ea56faada41babb1cd765512acb3caba18dd503648",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "b70fe1d82baa6ab40901d17e49ef8946297379a6d1adc3929a9cb979ae947e5a",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_empty_blocks.py": "ff0973074a72cf110b90dd8f76f032d5016315bbb21f84569e9fa701b11a7d88",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_event_paging.py": "e79cd1de3bd7469818e255cc01f087a608c11e799a48e81e4ca0ac29ff7a578e",
+ "python/tests/test_workspace_export_paths.py": "070b987b09afb06090bcafda808706316d272f20a24b5d09776bebf73a98002c",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_native_tag_filter.py": "86912660ab6c59706286c4779e1873223ab7569ed772bf85412c2b45e2bdf3a1",
+ "python/tests/test_workspace_native_tag_integration.py": "3cf4ab9a1b5db849600c2716e9026cfb3d0817389f240d5d9cee9dbfe0b7922d",
+ "python/tests/test_workspace_native_tag_lookup.py": "57445bfc3e8841bb0541b52181151a985199e599c2e296fe0242908bb0089243",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_path_api.py": "f852f8af8b9795c63772800c3ca3cb7203fa0a1b81e0ef562ef848c1e31a06b0",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_root_boundaries.py": "ed05c71c1c58fac7fe7c8175f983fc849ec7959cd95de064bb17cba357f5ccc2",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7fe035916c152aad2df6f41cc6f5c55e8d30cb4168f53c4ab1aa9e171bdd7cfc",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "a7bc5ddb5f153aacb23c13eb11b1ca14515ddcd12526567f41e49252931ddf96",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace-mysql-runtime.json": "2474da9de24c81322ba0149536847daf4a716eadd8d2589cdd01c3b519aa41db",
+ "python/workspace-requirements.txt": "9c5b2fd02d67a11d7b517af100536114368d98b195d17e281e40f42ff4ee8d28",
+ "python/workspace-runtime.in": "555d294e27881d44a1a6c6730ffb4e12b24497429f30ec425e78cbe6808b2298",
+ "python/workspace-runtime.lock": "af575b1b6d05b26a6bffad1aac27b6ff00483949412c1bc41191dccd2f1b2708",
+ "python/workspace-source.json": "aa72758754aaeb4e18046ee18d8e1998d43bd100bf4fda5f9d2dc32e90420441",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "6d89f8a735c34e6ec3f7f3db291e5d21c1bd8e64a60e7b12563e82dc18eb9f24",
+ "python/workspace_annotations.py": "7c6bf29aa97bcb7c49c39311ac34f71c2ca744c8dc2125849e22ed97a8b735b1",
+ "python/workspace_api.py": "8efc55e5fb6079072014c616223872464c0451c6d85bfcb2c61693f9baaed1ff",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "cca734e7ee8faab854204a080c729236339fd45dce2e39b99302c46f62964e07",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "e5a2421919e08351d2b8367ff773a4019f2433de46ca65cf598781fc55a0b51a",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "9bf32c59a9847f5b7231987348fd54ad1f6eec4773323b125ae34d1239632fbb",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "6b89cd32272a3ae095a1807f0583f317230fe9a726dc54fd1af04c29d6011057",
+ "python/workspace_installation.py": "e195c160696ac9e2f59a7bd6e79c78e8638dbe3730cdfbb3ebda7296cdff8838",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_native_tag_filter.py": "8cb336a98cf2f487a3bf30db1ddc6ad5db1081018cee394d2690dffa04f8c1bd",
+ "python/workspace_native_tag_lookup.py": "ae8d7fd3b2f80d56078777b03de4189b648f536ead1ca1225ad6c72a7c59a466",
+ "python/workspace_paths.py": "c39843552ae94a69fa85aeee558fedea4b0fd7db3151adb039c82283eab733ab",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "bd372ddfe82b46735a0f84c1fa65181d1c8bdc182e42b42b2ff3d80779cc09ce",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "40ba54acf2c5e9983c2e2766179a3d365b30ad1cdc4955410571936f7dfb4954",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "68e142d6008248c67310462b692b130f0951a87a802187f59d0e6302a78073fc",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a",
+ "rieke.py": "33d5c3f181cb58482d6d443b863d014209c06ed35033453811842b7f7f7a2b49",
+ "workspace-app/package.json": "0005abc7db80a9e86d61d254eb5405061904fa4147736ceb22e4e5482deafefd",
+ "workspace-app/src/App.jsx": "a5244175e5ca184ad0ba9cf3c91eb20dd33f7295c70061103f4dadada614a922",
+ "workspace-app/src/WorkspaceNavigation.css": "4c8128ec6230513a5a93686a2708d8709d8bfb6dcae271d278dae48fe021856a",
+ "workspace-app/src/annotationProfile.js": "20dc38eb4f2e86aff440adc9252e844019183f2b8279422d6a0cd14fd4a94f2b",
+ "workspace-app/src/annotationReceiptWorkflow.test.js": "9771c0925d8b7a5604631046f3140d30c00c11503b7a2602cafa98a4b40e36d9",
+ "workspace-app/src/annotationReceipts.js": "609f8b878e03a65c7f7f53d02375d73777ae3bb975e685cfd93299e9d8c167b7",
+ "workspace-app/src/annotationTags.js": "292b784b215148fb4c8eeb044f7acf8db19d3c6fe4ead9f0c4a5dd465aa89d41",
+ "workspace-app/src/annotationTags.test.js": "bdde5edfd0656b008566dda3cd0b8ef623fa589969d67427d7df4e42e6453091",
+ "workspace-app/src/api.js": "9adac79c901fb909e3ae2f00c93a2bad5305abb26ff96f44077b1085c4139c55",
+ "workspace-app/src/appUpdates.js": "792ab7043c569716d9d8e9088fdc4dcff1931cbd343106c9c0f71081e34376a7",
+ "workspace-app/src/appUpdates.test.js": "00f5c1d1cbed0f3185c53f332f54d20f51e106f9a1cc1cbc593ca2e7eefbea79",
+ "workspace-app/src/appUpdatesLifecycle.test.js": "2db1fdff06ded6ffc2d67da1761eeb8fb6a7332efc6502746f21728662452071",
+ "workspace-app/src/boundedTree.js": "dba203e26cda6933e8bcead18e31409e7f74b6da14054eeaf7de023ca4be78e9",
+ "workspace-app/src/boundedTree.test.js": "d53b30290e624cd20e3111b90892fd5d0169296e94ec91f5690d4a951ca9eec0",
+ "workspace-app/src/columnTreeNavigation.js": "78168c3c27ab20942d60150492c0d00946c4ac7fe38ff371842100aea5ee6892",
+ "workspace-app/src/columnTreeNavigation.test.js": "738ae5b04c000ffc6205c1342d5d71c0d134cf204b7330ede8956ca49f3a197a",
+ "workspace-app/src/components/AnnotationIndicator.jsx": "27094bdc5b540abe9cefb7e96d7431abb2988b21d09c5c9d44ae8d5e77eefbd8",
+ "workspace-app/src/components/AnnotationProfile.jsx": "7a1498567883a0661ddbc7f011fbeaf79400b510621092bff6775d3b2851052b",
+ "workspace-app/src/components/AnnotationTags.css": "5b98288d299cd1a3ca6a3c9565df7698f716c883eebaa8156d1f8781a941633c",
+ "workspace-app/src/components/AnnotationTags.jsx": "cbbbf6caf02f9adb986d4906f05db3839a29f07094bf11ac71a85712c431d854",
+ "workspace-app/src/components/AppUpdates.css": "73ccb301ea77a72747d5311bff24e8d09a6272eb106ba9f7378c233ba5247552",
+ "workspace-app/src/components/AppUpdates.jsx": "e099cc3ca679b596c57cf2fe4767640b5e3c076480aa5c4d95368e482100746e",
+ "workspace-app/src/components/BrowseFilesButton.jsx": "a64d39292490df41fcd3d68279a9da9d27f941985de9ec739a0ab05b144f006f",
+ "workspace-app/src/components/CandidateExportPanel.css": "e5bc81b3f742f8d01da663632c9cbc2b8355659105d658bd6281b10e1eee6126",
+ "workspace-app/src/components/CandidateExportPanel.jsx": "040eeda2357c7fbb7c53623433bd3ff9d3e1c69f2ca8665c31b9d22623480bd7",
+ "workspace-app/src/components/CellList.css": "0a78471a8c564595482bc0e8e892b367bb273be773baecdd62f86069abcfbe8b",
+ "workspace-app/src/components/CellQC.css": "0b868c00e767e7523bafb9e5867985d071b8fb231e6dde858bbdc096d56b0db8",
+ "workspace-app/src/components/CellQC.jsx": "28b872b17920892834ef3e4e8d6642cc7b6a72e0660e598119acde39c03ffd3b",
+ "workspace-app/src/components/ColumnTree.css": "9b8733010cc0475a5e29b34733106a10ccbe20a1817382d5b68b072c6899f450",
+ "workspace-app/src/components/ColumnTree.jsx": "1c589ae833fe6c143a165a8f7b1e2e4e5d5e97a4953a245435adce5d170e404b",
+ "workspace-app/src/components/Common.jsx": "7f602f4b8b21532dae0a6b9eda08dac191a106da2eca7edb617f6ebcb28c1351",
+ "workspace-app/src/components/DataStores.css": "f745e08bd31946b9abadc939dfda6ad9c79714ea0184d47fdf365271e2380e5e",
+ "workspace-app/src/components/DataStores.jsx": "d6148e430bf069ca84df112a0702bb8473aa77394adc6e94bc174cc71789db98",
+ "workspace-app/src/components/DesktopDraftRecovery.jsx": "c92953552716df3fe4c91463888f4c8be77e6ca1a0b88909b805bc5919cc5862",
+ "workspace-app/src/components/EpochAnalysisInclusion.jsx": "03ace5d560a0545ccc4a1621d481f02ffae6c154b8f97f2d42d2c4da64d7e79d",
+ "workspace-app/src/components/EpochBrowserChrome.jsx": "d9b0e6ffebe43073beb8467aa066c2694c743369adbd8fa9622b70f1b8524812",
+ "workspace-app/src/components/EpochBrowserLayout.jsx": "3ffdf64cbd7c5dd0930184519f51702578de700a67fc1b76cfece73f1380473a",
+ "workspace-app/src/components/EpochConnections.css": "f05cb74270ee2763fb8584a4ed9080b88fd39f74c88a14b8f8cc662fb3c7abc2",
+ "workspace-app/src/components/EpochConnections.jsx": "22d2a3d50e8b5778de5f3264b01f4477d834e41a3e2901f51f0ce2be525d0e57",
+ "workspace-app/src/components/EpochDetailHeading.jsx": "378e2f2c0ab79b284f8bdd2c4b5b26633e608ccbf5ced7d0e41f11eca3e60994",
+ "workspace-app/src/components/EpochInclusionToggle.css": "535519c7f881f9637f7a971a96b13d8493828616b0e1dac9ee28a854360b3041",
+ "workspace-app/src/components/EpochInclusionToggle.jsx": "7a2aaa8a08370ca9f2658f69f1c2dcc55270f49fe7cf7d858acbfba297c9f1c1",
+ "workspace-app/src/components/EpochSkimList.jsx": "314a36708443dcfb59da04d7656e2538c0f4035284c5b4721b29c146d27b7b95",
+ "workspace-app/src/components/EpochTags.css": "107f9535c3196a597917e8310a60cc394320eaeb5a9dca58e3d74cc4045b0876",
+ "workspace-app/src/components/EpochTags.jsx": "2f2d4be04269e45d82d03df0a7f5d5cb51a4e20330162512e1c9cf0a946c78da",
+ "workspace-app/src/components/EpochTreePane.jsx": "c161c747a715519092e5b7095472cda91f29d3871f9657a198fc08827881a203",
+ "workspace-app/src/components/EpochViewer.css": "10c3d7f2b161171e7c9bcb9f589421d0ea39c1a71774555ffbe2bfc42fd7064a",
+ "workspace-app/src/components/EpochViewer.jsx": "868505b903c22a75920f44a9fbaf069818883d92522fa2fe527aecc14c0aea90",
+ "workspace-app/src/components/ExportSelectionDialog.css": "e66b8470441ba77bb6016a1e3cd75a456f8e0d2b1af24415a87bf1bcf23fb9db",
+ "workspace-app/src/components/ExportSelectionDialog.jsx": "465e98b52d5725a343b1089698e493e786b7fb046696b04ac7557a75ae8e4bc0",
+ "workspace-app/src/components/ExternalTagSync.jsx": "b92c95f6904f9e672f19640f0c235d09cb322aedb7430879a71a927956c05713",
+ "workspace-app/src/components/FolderBrowserDialog.css": "208acaff373635344e850500c7bf0da42cb588b7521b350c6d9cc8554d9e91b7",
+ "workspace-app/src/components/FolderBrowserDialog.jsx": "19c600a5dac295327bd9cbcefbad3e6f9944e60782a33e069b49224cbbf99651",
+ "workspace-app/src/components/FolderPathInput.css": "507ed928e721e5cb326211452e768c24caca610a91d5e95ea9651cb38d90f021",
+ "workspace-app/src/components/FolderPathInput.jsx": "d4a23e65331c7eb063f2750544864fe8cfddddfa8b9059708e897e25aa0dbdf9",
+ "workspace-app/src/components/GlobalSearch.css": "93f12d293727faa377046f16a8a7d21b11be8fc7be27eb6c7e02b3cfceb17c51",
+ "workspace-app/src/components/GlobalSearch.jsx": "fc3884c6260e3a50dd3ad726286ba9b1b7a664946a295aaca9049510641f4e9c",
+ "workspace-app/src/components/H5Inbox.css": "0282deb5bce72f137980aca75a7b3f502a92b14df9e12e4594283dfe65cd132c",
+ "workspace-app/src/components/H5Inbox.jsx": "198b771e76a07c9d5348ee3075f31e7a5c043503472e79db8b7afdb8c45bee61",
+ "workspace-app/src/components/HierarchyTree.css": "50fe5ca506db43074798810af75d521e2e7844455f5d9d18e4300804ab917d61",
+ "workspace-app/src/components/HierarchyTree.jsx": "cafd448f6773e35d8d20596b82b960aa2b237812dbbfe5f8071e892548c004a8",
+ "workspace-app/src/components/ImportHistory.css": "fd50cfc26c9e35a6ba50029e7d5aa6d74762e8bd14c6ddd29f6c8fef8eb98e67",
+ "workspace-app/src/components/ImportHistory.jsx": "0d377697bdfcddc48cb6a34a8c57d21163eb34492a92c95074e167c77073dd55",
+ "workspace-app/src/components/ImportStatusBar.css": "aa4e4fdc268e3e11dd088d87264368ddc9c3831c1fd084bf7c58fdc7b7c6c598",
+ "workspace-app/src/components/ImportStatusBar.jsx": "a0058f256ddbb4cda7573e564dfd6d660708ebb8c2470600c0882f308cf041ab",
+ "workspace-app/src/components/InspectionCellTree.css": "dff8e589b627be899b9031491fd25b4dc51be703fb163635bd87800446d93702",
+ "workspace-app/src/components/InspectionCellTree.jsx": "5698d31ff446bd7254043c777cd65aa4c3304ed45d2260af2bad84b7c242f3bb",
+ "workspace-app/src/components/Inspector.css": "d7d86e4a1c78e6884010dfcc19edc35a08c9f212b5b7bd4c0fc2c92bbba8e2e7",
+ "workspace-app/src/components/Inspector.jsx": "3c67d11f9370786d34fd802278b60e56ab90ab46563914b29b5a45c9f9cd787c",
+ "workspace-app/src/components/InspectorActions.jsx": "4cfc53a0bf2a3f82a9fa44e0e5a606c2bb946486fb7a35eb93b1960e40bec180",
+ "workspace-app/src/components/InspectorPolish.css": "9114e1aadc6148996b5720cf28e573270bdadbe1ec8400c5d95b532574f4c246",
+ "workspace-app/src/components/JointGroupingEditor.jsx": "34662c156a1fc4bc3b5a962cd01b6995fa0651756c0be03a02052072a4fad1b4",
+ "workspace-app/src/components/LocalExportFolder.jsx": "32c4b01bb7283449d101122d52b3dcdc32f23f60bab13ed3656657cb5f14e324",
+ "workspace-app/src/components/MasterLog.css": "02fd7220dfaaca4f37003ad9f24bbe0006fb45248e35e9696a78d8583e0291fb",
+ "workspace-app/src/components/MasterLog.jsx": "35fb9e4619f95bf9b419666d177bc6884edd247f9372f47472dc33353c78d9a9",
+ "workspace-app/src/components/MatchingEpochs.css": "b3f4be8ec6bb60a98d8618176620bf2775cba2aa16c8eb480e3253542e94b4fc",
+ "workspace-app/src/components/MatchingEpochs.jsx": "29ffaa736b939f640c9e77001e3d87b72924915e7a7644510a165d71447cdf79",
+ "workspace-app/src/components/MatlabMaskImport.css": "4ce9eb258f5a7a972819b7e3617c37f9bf6bc682e93ef9d828c25926c986627c",
+ "workspace-app/src/components/MetadataExplorer.css": "c934d543b4556ba2633059e9e726a4dad178b16afa339ac189bc5e18cf977bac",
+ "workspace-app/src/components/MetadataExplorer.jsx": "e2f2cd1b1991d3ede3b39222347b156724f4bf75da1c4b06ea4dfc5ac76cb6ee",
+ "workspace-app/src/components/MetadataPanel.css": "aaa91263881b0fa946886456c30a1a0fd7f8e89c467fc378eeba8233079d4ec6",
+ "workspace-app/src/components/MetadataPanel.jsx": "40b33eccc1fb6a0dc96fd99a6bd94723205408047b14187a0c6a5051dc872a18",
+ "workspace-app/src/components/MetadataRefresh.css": "39adef5f82610e4b28146f34dbfac14891e5d5342ed36828ce1a5cb3ef286207",
+ "workspace-app/src/components/MetadataRefresh.jsx": "003e70dfbe6b4788f41d7e2bec4c2c387142994d9b0fda85919b6b508b597855",
+ "workspace-app/src/components/MetadataTable.jsx": "357e09938aea2e9eea0fc245a087da74f82d0cd111ae0f5d0b0b10bec3a39f3f",
+ "workspace-app/src/components/NavigationLoading.css": "e72c85c4783a4962879a03f48188beebcd7af29a2b65b3f41946b43f27f3428f",
+ "workspace-app/src/components/NavigationLoading.jsx": "42202a49d6db5ca42cad94e590fa73b50c7848ef23d89cd6c5de9ecf5380fe04",
+ "workspace-app/src/components/NeuronIcon.jsx": "4c8a156735a96855ed1b6d821c7e13ce0da6bdde66386d91e39afa6f8ffb50b3",
+ "workspace-app/src/components/NewPinnedProtocol.jsx": "963724a9a2c787737a4c507d12c105e8d86e47d85e81c915778c131030f9791c",
+ "workspace-app/src/components/Overview.css": "6c73bbf9c67a438f683aa246c11f1a3b51b6ae94ea802a3d90f569a69c4c279f",
+ "workspace-app/src/components/Overview.jsx": "6243f74ed7b69ab4b312d52f9e1b0fe73edccb002cd514cfbc257818b65d4e05",
+ "workspace-app/src/components/PagedTree.css": "1d98473881ed79693cce57d35e5fd6842d160637469f97b68001e1af4242f976",
+ "workspace-app/src/components/PagedTree.jsx": "e1fac84aab80a372e9a5b0697ff38450e8a994ea63ad4598e9031093f6f8d105",
+ "workspace-app/src/components/PaneDivider.jsx": "621b273a7c1f72bd97e3215afa55c611c131dc9284decf54cb094e5d93b3aebc",
+ "workspace-app/src/components/PredicateBuilder.css": "3bda6621dd2e265c54155513ea48ac7b4325fec2bc07251f019bfe48a53b370e",
+ "workspace-app/src/components/PredicateBuilder.jsx": "a06e10c872079f6cdf72b243a162cee4426b3fafc7f65720c7b903507d9330ad",
+ "workspace-app/src/components/PredicateDialog.css": "83504219bd24cfaa332f7364cd60fa4cc385b3bc8e0718206d90cfd538db902f",
+ "workspace-app/src/components/PredicateDialog.jsx": "480af6a45f4b13ce39837752fbe43a1dbf32fae883faa20a5f8eba226f1749dc",
+ "workspace-app/src/components/PredicateValueInput.jsx": "8f3881087cf517f130ddace33776798abc5067d8fb94af3813aeb7bcffeb83a4",
+ "workspace-app/src/components/ProjectClosedNotice.css": "75399b5a1f91b2a47576a7c606efd7341993405f3da0af8b00a40b446b6d37c0",
+ "workspace-app/src/components/ProjectClosedNotice.jsx": "83acfaba053fd77e31aaebabedfcdd80ba605e66e2ab79dd43f61abb68cd37a0",
+ "workspace-app/src/components/ProjectFiles.css": "6757af571074a7b5d1f6be99468aa6ebbcd46b768e009897369ab09fbd6551f8",
+ "workspace-app/src/components/ProjectFiles.jsx": "60c6be5d1ef004d1268b128050f5e036947a72bb840ea8080fedb802b71f3df6",
+ "workspace-app/src/components/ProjectFolder.css": "e32f571f891c4a0ec300619834c1479e015b512a104a4b7004c28f262e467ee6",
+ "workspace-app/src/components/ProjectFolder.jsx": "c968611f14e5e5f7448f49dc5fa4de6278b532564bc0f916b4c1ea1414ea541b",
+ "workspace-app/src/components/ProjectNavigator.css": "7b605ab6236571a86524a513bdf603e8b47ccb7508bae94a93fa3ffc4b0cdcca",
+ "workspace-app/src/components/ProjectNavigator.jsx": "ccae4f7bd4f2321f569f0f5dec69a49204f5690a51501102cb563c890da78db4",
+ "workspace-app/src/components/ProjectOnboarding.css": "64f124093f3d920da3c346428acf883c021da249d39c02d89a04b3ee78fc8367",
+ "workspace-app/src/components/ProjectOnboarding.jsx": "70b8e1e1f688c8bee7f74386c45db2459d819f87ef2c0326bc4696fc0b0edaad",
+ "workspace-app/src/components/ProjectSetupDialog.css": "74987456de3f0fc3f81a92c297842f1cc60845732e8fc24038c33579a31ea9ce",
+ "workspace-app/src/components/ProjectSetupDialog.jsx": "9c3fb019fd73b37e9392a3387f946dfdf189a47e1a1a78c0308c8dc1933de603",
+ "workspace-app/src/components/ProtocolApplyPanel.jsx": "b7c5fc76576a4fa103fe11a63c7cbce43149f85b9100dcac89293e9333f48fc6",
+ "workspace-app/src/components/ProtocolDiff.css": "2ee5da02d20e185f8f0f6b469b070125afff09f6c91ed13f2dc8ce479f34c6c9",
+ "workspace-app/src/components/ProtocolDiff.jsx": "0601a64b2e5d2ff428fc70151ff970ad2b7ff020a4516196db40a51c87e07797",
+ "workspace-app/src/components/ProtocolExportDialog.jsx": "025ecb022d624e99d5cc4666d1945937cb357da0498b688fcb3447095c4908ce",
+ "workspace-app/src/components/ProtocolExports.css": "1df4feb393d3cf65908b4297240d1a26465d428c4b76d9b2dfc20a31d77f5f0c",
+ "workspace-app/src/components/ProtocolExports.jsx": "30871b36357e0e22837fc39fd275886fea7e36c6d3a9cac4098fa955acd00198",
+ "workspace-app/src/components/ProtocolInfographic.css": "b5713e5f8f390ab6f92135b4a7d0a4a024d046117476248b666b5e12127afcab",
+ "workspace-app/src/components/ProtocolInfographic.jsx": "036c666c24acd7d542190629c7c4a3a19f7d830eedc8d21230e40be387d66a2f",
+ "workspace-app/src/components/ProtocolSidebar.css": "17acc97de03c883199fae299fe9c5d94dd6dbd16a6a185c2e098c22372aeacfe",
+ "workspace-app/src/components/ProtocolSidebar.jsx": "465a569ac9d0b08ce050b218a9cf1f505f6a50d4d7ba2dd59856cf590333485e",
+ "workspace-app/src/components/ProtocolSuggestion.css": "1913b1a507f186a10ad940e607d3fdf6b43aee010943a7950c1c708cd6ecd61b",
+ "workspace-app/src/components/ProtocolSuggestion.jsx": "1e6cde4228f1667f92811ad6f603342a49e89fd1363368307888e917e8a48d95",
+ "workspace-app/src/components/ProtocolTagSummary.jsx": "b0948be30deda310ebbc4e4df227b91b69af32e14653d213bfb7d9bb95549c6b",
+ "workspace-app/src/components/ProtocolViewFilter.css": "4d8d11df6eba13ebc627bd6c200462ea5426aab60124e238fafe9f21879c68db",
+ "workspace-app/src/components/ProtocolViewFilter.jsx": "8a040f349dd05740cf26430987dea60050ca40e075163d074a89b92a9020e5b2",
+ "workspace-app/src/components/QueryPresetDialog.jsx": "966211a91c810a28a2f52905cc2745775d18d24197333caec6dbdee6d44695ec",
+ "workspace-app/src/components/QueryPresetHistory.jsx": "e936645f3fd19c698130df543b57d43b7e561f14de79c6461fac9418acddd381",
+ "workspace-app/src/components/RecordingSize.jsx": "104efe706dbfd7949c2c5503319fc1c4884cf7172daf59ef9c9e6432fd03159f",
+ "workspace-app/src/components/ScientificContext.jsx": "02d45b7925421aec4106dcc849aa44180d610659bb7d8631a62279f0e73cbc8e",
+ "workspace-app/src/components/SearchPresets.css": "76634320a62a0a5224c90facc7d648dcb1119c8588fdbc4d2ce34374a3537a66",
+ "workspace-app/src/components/SearchPresets.jsx": "a349df170349497ec983590eefa67621dc74fbc0e55ce217baf446251205ef45",
+ "workspace-app/src/components/SelectionMaskDialog.jsx": "fecf1e9ce7569dc68d8d140617abfb0c826c4b53ef68527357146085d79086b5",
+ "workspace-app/src/components/SelectionOverview.jsx": "eb1230017552cdefb5933a96925eb8ed5644a268bbcfa630cfbd20f0cf7c40a0",
+ "workspace-app/src/components/SourcePropagation.css": "b1b66ab2fac3d049f52a04e9c7d36efe6dc90a317a78579453cf7f93d9aef6aa",
+ "workspace-app/src/components/SourcePropagation.jsx": "00bf1d0be5a13b1efbb7f69aa3c9ea1a95c34c99c0b14442dbc503e0cf160dee",
+ "workspace-app/src/components/StableContent.css": "f23b6bfdf641260e0af75a822fef5caf0aba5a96ed7542dc58192191b63e7a4c",
+ "workspace-app/src/components/StableContent.jsx": "4d0a4a157dc649b534332c061949212988e907c6736234dc63ee7318a45f12e5",
+ "workspace-app/src/components/TagExchange.css": "0ba2ec4ce16a16dee8e76d389572969eb2d7eb0cbbc4903f3af2b758d8547ff9",
+ "workspace-app/src/components/TagExchangeControls.jsx": "b9e19873c711e68c052f4323038f3bbd6dc8514c7c339fea95afa656f3982f76",
+ "workspace-app/src/components/TagExchangeDialog.jsx": "9b4cd31adf90ca88682dc19056c342994eae321af1ea6b963169ffa0ff3746e5",
+ "workspace-app/src/components/TraceViewer.css": "d10504280c216eee76f1cb4653411dfb0162c12c98cf0cd122a1c28446c7df19",
+ "workspace-app/src/components/TraceViewer.jsx": "00ee6a726cafc7dd8cc830cfcf73f954d9fe725ec3a7363413a547a6920a08eb",
+ "workspace-app/src/components/TreeBuilder.css": "221a64f02bafd393096ad48fbd3c34389552e1af127484f7626b50f838eddcd8",
+ "workspace-app/src/components/TreeBuilder.jsx": "cca4dd472e5ecce7349bb0f30d75127502d787766d4bb32b73f62fee2a2ac794",
+ "workspace-app/src/components/TreePreview.css": "9568ee3b49cef57602d6043f874ab48ed7735893eb0b1668863c707d079bae93",
+ "workspace-app/src/components/TreePreview.jsx": "fd32bbb0d77dac26117491cd1bbb963d86556b63f5017bfb4f2f6fef2c6b0750",
+ "workspace-app/src/components/metadataValues.js": "7ce8b253bb1c02dc5b35c79792c1b7fea311f54e854ddb9027cc5e6d08e41906",
+ "workspace-app/src/components/overviewModel.js": "533dc9377addaf547fd1ae663b7c8229726b7b07c25316cd7f8c3defe4bac766",
+ "workspace-app/src/components/predicateEditor.js": "4fa96999a5d0ff304af60ce710b8ab6bd823af3d8a53e4e579f147e9db2081c8",
+ "workspace-app/src/components/predicateState.js": "56ee3c61ae235941344f67f299fa0b42935e482ec68d1d1d7cf9d10c435683c4",
+ "workspace-app/src/components/traceGeometry.js": "e2c9a9fd769a68a86d87cbfed9aade2a2f601b7d35c0f15e4d955f7b4994206a",
+ "workspace-app/src/curation-scope.test.js": "03b9d50421dc3ca0fddcf84aa919d869ff8aa12de9d39b3bb31f159b250a3111",
+ "workspace-app/src/curationSelection.js": "e13d930a935f0f5777205d12986b754fec75f1dd506b1fcebdf8703a384b4009",
+ "workspace-app/src/curationSelection.test.js": "764f90744d79a4d28f777f9c2361548a12cb9173c815b54855f6d3d5b256a1f8",
+ "workspace-app/src/desktopDraftSession.js": "e7e3405950fcb1e8d039080591ae8393fb8ecca76c0c650ff991a938ca868de9",
+ "workspace-app/src/desktopDraftSession.test.js": "9de106d7ec40c8e9241c1dcf7cf6e4503a414c77d14204849109bfdd3274749a",
+ "workspace-app/src/desktopLifecycle.js": "9fcf5128487dfb59fdb27356d64ae3ba9345776041a72a625d6fd97fe70fb666",
+ "workspace-app/src/desktopLifecycle.test.js": "153714b002b3dd5c8145a4f719d09855c9a034e21fa8b6053bf0a57c38c85fc3",
+ "workspace-app/src/epochBrowserSource.js": "c6857102d97fd288634deb1dd61cfde87097e4cf55718de0137925c1bb3ca782",
+ "workspace-app/src/epochBrowserSource.test.js": "e61ab6d5d4d65b4122ba18b287cfb52bb059bbdc96963b3051d705ce52a7837f",
+ "workspace-app/src/epochListScroll.js": "6d21ad32bf35ec3d497bc30ae12d687948b6afd00b4e1c6019900ead96b270a6",
+ "workspace-app/src/epochListScroll.test.js": "95a82ff95404156fe766f7eb46d3af14ad9b0e11e1c7fba87e20ab53a11f1e22",
+ "workspace-app/src/epochNavigationIntent.js": "4bd629912657d5a8677ddfd1de9b005fbc37b485d935f51a570f44420b838296",
+ "workspace-app/src/epochNavigationIntent.test.js": "13db4b46eac9baea550fbd7097ed22f707c35a3099b6555a3e2791e096e64a95",
+ "workspace-app/src/epochSelection.js": "eef553eab8b53ee1f4174fbf960a3f7bd4015dcae2807a44ddd4e497ebebf222",
+ "workspace-app/src/epochSelection.test.js": "c16dd9e6e30160eff8178c055fbc26aa3d7b74620ae6e2fa8456568e5ccd254d",
+ "workspace-app/src/epochSkimGroups.js": "06dddaf99cdca361030dae7e80de142f790283ee1329622b7a731d9d6c30ec42",
+ "workspace-app/src/epochSkimGroups.test.js": "722d25fde0467d94808fe3c28820e102af8d48ba640deb5bb252ea3bf0f7b107",
+ "workspace-app/src/epochViewerArchitecture.test.js": "9aa453133ac25c1a243102f2b23a2cde4daf0d7ff46c27fb2c471acabf75c91f",
+ "workspace-app/src/epochViewerRendering.test.js": "6a7875f14460884768f2121cd182e10b5492ab59ac6609fd10089f36f7354408",
+ "workspace-app/src/explorerScope.js": "ed260ea3caea2aad20336b694f4da892556c0cc2bd50580803759dcc8fc3ab15",
+ "workspace-app/src/explorerScope.test.js": "a0401aad546e612cdc36b835d4eb00b8e4990076f0af1b0c16c10f1e8e8e7e51",
+ "workspace-app/src/export-policy.test.js": "9236c64edbfb57aa56ddffc2dc086703205b04c89b889b9b6512f7c4e7624648",
+ "workspace-app/src/exportFormats.js": "11d0118746055bc0aa7c1a3afa4c11e839444c5ac3ccda9ffb37277b98efc606",
+ "workspace-app/src/exportFormats.test.js": "fe616c9ba0a18ae9d64c0df80aed7e385358bb717ee51b505b572024ee9080ed",
+ "workspace-app/src/exportProtocolTargets.js": "3e8dbdf6a2bc507f5402ce8d317ca342535d40403c72a1174aaed9e4071e1525",
+ "workspace-app/src/exportProtocolTargets.test.js": "ff0c41e9dd019cab6a6ba479ecbc317811d7c2c4cd81843ede2fa862bd290388",
+ "workspace-app/src/exportReuse.js": "0aba1205e885629cb6e373fd666f66f858b1e0f0df70524fdc38495168ee0650",
+ "workspace-app/src/exportReuse.test.js": "48c875a98b619112caaef89fb217c62b263c583d3383cccc9e6f294489a6303f",
+ "workspace-app/src/externalTagMonitor.js": "e980a4466f5e57b2982cca0f307010bec968edda06da78fedca24a35f20ad5a9",
+ "workspace-app/src/externalTagMonitor.test.js": "e32d55506ef167f245c0eadde6fb698b24ea9998868b26d0b0c4ae0215ad17cc",
+ "workspace-app/src/folderBrowser.js": "2ffb8fec1c9c72b8d68af7d25223c3ecb58f31cbdfb86ba80a7ff44f14932730",
+ "workspace-app/src/folderBrowser.test.js": "4b92d9b74350fc08e6b5357381f416f31c14ca31bdc1b1507422029afda9e5ea",
+ "workspace-app/src/folderPathInputLifecycle.test.js": "b6a10f0bc13603e82540e874fe13dd8c648c6f62be7ea6ddb0b6c1db5a9fade7",
+ "workspace-app/src/hierarchyTreeState.js": "f3d17947164ab469bfd16c517c8d10f9ca8cd2927420dab436f071c0c2d9a0e2",
+ "workspace-app/src/hierarchyTreeState.test.js": "4d55ea07487f4682f82715b4afab1c4d14b32091cddeb0080e2c2f56fd604fce",
+ "workspace-app/src/importProgress.js": "650bbab3b93836b4d315afba9c9577f35942772345b2fa448a64e0a434568f40",
+ "workspace-app/src/importProgress.test.js": "9dabb78af52e9c73d8e4881be81c8b81e7e6a698894dfc75fe723caca45c8226",
+ "workspace-app/src/importReadiness.js": "ae89817b7c6a6abfe6fbd20d2a785ac94f27616c1a93a3f981803894f9269531",
+ "workspace-app/src/importReadiness.test.js": "89114d71e675aad724ae8646916a8a5a4415e391df181ec9830c1b5d5001ecc1",
+ "workspace-app/src/inspectionCellTree.js": "3e5b9983cc6a727380aad5ecec1e9264301b7f9962fbf426a6bf1ddce3188fab",
+ "workspace-app/src/inspectionCellTree.test.js": "d60c92efb7ac54227df535880b8e9568ac87d49e11df3f1326407e9033a366d7",
+ "workspace-app/src/inspectionScope.js": "b752a7ac4a809ac1d433d59a75ace47d0847b2851d77777dbe1a96240c549bc4",
+ "workspace-app/src/inspectionScope.test.js": "6071c16db65255b65d963470c0e3368c008a8f8eb46752daa4dfc5e4acfeaf5e",
+ "workspace-app/src/inspectionTreeLifecycle.test.js": "f2567c3c32bb63ee2905db311c610af1fcdcd24105f05332eb8b97694f50d373",
+ "workspace-app/src/inspectorCurationLifecycle.test.js": "72359c24261254bea423a8b8250fc3c8aef7940ebf0f35d95d4d1d699b7e9412",
+ "workspace-app/src/inspectorInteraction.js": "9c3b2c69d45314eb41b1fd62ec1b1f0663fca451784db3e4d2b165d6ca6c04a4",
+ "workspace-app/src/inspectorInteraction.test.js": "34e0e236715e785894c86465910eb1a9f903f902736c875663d2e8bab5ef9c07",
+ "workspace-app/src/jointGrouping.js": "afe7fcd6d9676a64bf6d96f5ba4f9b7a4fe27416fe0523114d154a1e67edb4ec",
+ "workspace-app/src/jointGrouping.test.js": "508af81429c764688db6e549e28ace293b82d29bcf2c8a12affc5e880d128263",
+ "workspace-app/src/loadingNotice.js": "37e7ac79febafd4b07a54beeebd8b11e2de143e6c7ab6dab066f65175233dd9a",
+ "workspace-app/src/loadingNotice.test.js": "ed96cc0f78b5e72f8249eacaac784a86400a5e582dcbd1830279937eacdd1bcd",
+ "workspace-app/src/main.jsx": "71e69d3115df4c686a931737c07357f1e85123c5719c903175a4e032be7536f6",
+ "workspace-app/src/metadataRefresh.js": "ca29104e42a02cc99a23598958582bca509fc2dd7cb229d124d99a0e54239314",
+ "workspace-app/src/metadataRefresh.test.js": "85a68db32a16a3e67666a9c353c45f4e3761ddae2e6de8bf4690af25c2a90be5",
+ "workspace-app/src/metadataSearch.js": "766b7a7d12fcf827a0761a0ff367d34748a3d093275f819f7c7ae7103f637474",
+ "workspace-app/src/metadataSearch.test.js": "d74db36dc9d27b39f3e90fd71b6a34f51f90a368b4e69e8dfc29bbd31f9168c8",
+ "workspace-app/src/metadataValues.test.js": "4baf4f36cd38e3e8849d725225b52ef7cb194545ad780011c8b3ab3a98147c14",
+ "workspace-app/src/ordering.js": "3fe5ca26a4eedff0c101e513fdf5f5973554afaadddda2f75fab6476d3e5064e",
+ "workspace-app/src/ordering.test.js": "a97c50b8a3c69719ee0243b4fcc1dd74ca423bffa74a9104fcbd60c7f36facb7",
+ "workspace-app/src/overviewModel.test.js": "cf8ee3fc618de607bf4bdbdd0ee76118471015ae8d5e0a2aeaa1aacc224bdb5a",
+ "workspace-app/src/pagedTreeLifecycle.test.js": "ca49e829a3d84889542c1c72c9b251119a569ebe7f12011e4ca6b416d12d2eb4",
+ "workspace-app/src/pagedTreeRequest.js": "e6ccfb4db1224452719fd6ecab59e3e05697c057e19bf34552104de28593b887",
+ "workspace-app/src/pagedTreeRequest.test.js": "84e0a69b7e780eb927676d2e2a17ef83a010eb4038797a72531771b545101e28",
+ "workspace-app/src/pointerDrag.js": "c1062622121f82cdd4d20847b990d1657708e160008c0741c9b41855d8b7d2e0",
+ "workspace-app/src/pointerDrag.test.js": "261ad37352c817b0e44723c7db4f0c7d1612e1b65c66e3acf4961eac267faf83",
+ "workspace-app/src/predicateEditor.test.js": "98ad8f7e803eb31b460645891e696b4e1cbdf97e0527762374f4304e171b15fa",
+ "workspace-app/src/predicateFieldPresentation.js": "95c8215c786e14ed0b96b807df2a41a09948c3133fc3004dabbddd77728693ec",
+ "workspace-app/src/predicateFieldPresentation.test.js": "1d4695da51af48fa8676d5de98e2ff00e6960618a4f106fe906f48da6c36f8d2",
+ "workspace-app/src/predicateIdentity.js": "59cf1abd3454c8c3382cb35ab7845d96f4d338bdbbd43f0efb6723722b6c75c1",
+ "workspace-app/src/predicateIdentity.test.js": "98369a6618d0bb2928a63d3ac4e304f82efe713d1191d5f36f981daad4f55f5f",
+ "workspace-app/src/predicateState.test.js": "0a29a48443fce3d729d5ac8db161623095e0b2968544f2d9aabbc338ccfd3922",
+ "workspace-app/src/predicateValueSuggestions.js": "24afa38099571f416d1e54771ee0fa60450e24fdcf295245e9885dae14a95d74",
+ "workspace-app/src/predicateValueSuggestions.test.js": "28ca7ca5ad5d06e86d48d30666215d01534544c5198aeab4d92dbb0a72e86b1b",
+ "workspace-app/src/projectNavigation.js": "4a53606229e8ed0787144a018f82910da5ca27e73cacad7341e350baaf3053de",
+ "workspace-app/src/projectNavigation.test.js": "50f6c9743e491740ac3099ef21ac9d71b76dddbbb9afe165edb357c0f4d5af0c",
+ "workspace-app/src/projectPreferences.js": "c8e35df6c18d80895fe7e12d86dd3827e59670d4e7868732e65b85050482a08e",
+ "workspace-app/src/projectPreferences.test.js": "99216a435e9963ab8ff1a4d75ebf2ea5cab5bc9b56fa61205898a70462263f67",
+ "workspace-app/src/projectSearchPresets.js": "d0ad7e9a9ee5bf433fb38c9fdc6df737751993ed30eedf2cde37250df2de8c49",
+ "workspace-app/src/projectSearchPresets.test.js": "d0116a52e5c694e8da5576f7fdc0a76d056826fa5555430b127f94549cfe22f8",
+ "workspace-app/src/projectTransfer.js": "f49fe91f3850fa7b36250eff35d2d39d156aafb67ddfa14b5fb8cca86aa60432",
+ "workspace-app/src/projectTransfer.test.js": "796b09a088c51a5e65753ab66fb9151625349a7688e9a38d6fc75b4f651a8bef",
+ "workspace-app/src/protocolOverviewModel.js": "56bb36a40dc42c15c4bf8f0a1ee50521ac513dac3e3a614ac4c1a3531a21afa7",
+ "workspace-app/src/protocolOverviewModel.test.js": "980162b6e21d70d178ead87159318f22d9412a06d4694f6dc2b5c386eedc84a9",
+ "workspace-app/src/protocolSuggestions.js": "bf5674469ca030db7721182f0e5a2858ef1e156f6e7e508dd00ebfaec054894e",
+ "workspace-app/src/protocolSuggestions.test.js": "1fdb6bda2f131a3db6fce1a439e0b294e9d5c1f60645534aa60676f4893e26c7",
+ "workspace-app/src/protocolViewFilter.js": "691e89aa4a444629f8415eb35c0a920001fb02f1b3b9cfd98729602697f11962",
+ "workspace-app/src/protocolViewFilter.test.js": "4a4b7245382e83a75cbfee7591f9d7f21c8ca0a714fe533eff8a462a7f3236f6",
+ "workspace-app/src/recordingIdentity.js": "14171269f759d95e2050b2a433957dca930106efc7cbb5d6de4ede5c472f1e32",
+ "workspace-app/src/resourceCache.js": "17ca8c7c8a45768430d967e3ef53c358fa91ff37f25666734de1fa8df760cbad",
+ "workspace-app/src/resourceCache.test.js": "c79a1eebec4c49cbaee89e2b7ab969fe693efa95b9522547b8bce464cb7b71d7",
+ "workspace-app/src/resourceRequest.js": "7f7ed9af2de01b03cff6687e8221f129a91ffd5ea7230cf3c9d6859d1dc33e76",
+ "workspace-app/src/resourceRequest.test.js": "e8cc14c6a9c3cb0238eb478852b6f200a05b493cae1cd6ec5c91cb6b3ee41c08",
+ "workspace-app/src/searchInclusion.js": "ac63f91061362bae667791420f611ae52c89327f1f5e6ec692838b011ad97aa6",
+ "workspace-app/src/searchInclusion.test.js": "e3e12732312841ec822c8d19772e2c348b6441bcd31eb4842fc7c5c0be8d1439",
+ "workspace-app/src/searchPresets.js": "00837ffb304a76e41352d7137a3b97164d27614236159cc8b3f3f02c78d23dcc",
+ "workspace-app/src/searchPresets.test.js": "130cf9c2613f368f56582f46502f524bc796a8ef63830c85b759697e170c2c05",
+ "workspace-app/src/styles.css": "5c74ca2166464b975cbdafd6e3ed3a11d918eae7b929be0d8a8a431b5d247fb0",
+ "workspace-app/src/tagExchange.js": "8192d55f54542717960dd5443cb2cbac24728092dc802257adf2c2dfbb747a69",
+ "workspace-app/src/tagExchange.test.js": "2afa7da8fa2b84c65602060e411af73e1c68f24b9d1d3bd2169da757613a9f34",
+ "workspace-app/src/test-support/inspectionTreeHarness.js": "58c038bc97337a92de3fb0ae64a9fb608adbba634a6dce48852d9190e0de32e7",
+ "workspace-app/src/test-support/inspectorHarness.js": "09a1905ac6e3c5899d89a5a2b1d35b5804fecddb42a83c4b4b0b6bd40c13f9c7",
+ "workspace-app/src/test-support/pagedTreeHarness.js": "3c714ac9cf9dcf71c516c80cfd28326bde9a7f503925642f243ec68cb6548180",
+ "workspace-app/src/test-support/workflowHarness.js": "836e9fdc3411d7dca9e3585058650592e79c4dd7b2ac9135c2d49aea91ba6de7",
+ "workspace-app/src/traceGeometry.test.js": "afa9d853be58ceb78b0bd5593ffc983ce7365318e9145fdf2bb1c5940092d76f",
+ "workspace-app/src/treeBranchPresentation.js": "da35be6063e256faf1c89f9b2a93fcd1834ac7fee26d9c3a00ba517ae7f841c0",
+ "workspace-app/src/treeBranchPresentation.test.js": "0406f370dff4f4f7673d0f70c36a7a2ede68fda878817da455db4a4808bd8bde",
+ "workspace-app/src/treeFieldPresentation.js": "f64652cdcfafb7b526f5fa3646ed55bbeeea05540cef6bd8e2ca5272544851d5",
+ "workspace-app/src/treeFieldPresentation.test.js": "b3103dfb4fbeaa0bf84eb523ae0de1d9421880dde7443397a7487754e57aab81",
+ "workspace-app/src/treeLayoutPersistence.js": "84e5ddb9bdfb5bbc53072a1de7f1816dcdeaa415c81b4dec5001e1d7864ab24f",
+ "workspace-app/src/treeLayoutPersistence.test.js": "9a2c719ca3f0410263ab6c28dd3aa91870e05a4136e789ca9dd3c76efbbc5afc",
+ "workspace-app/src/uploadRecording.js": "f7587bc910536b4796d3206d27a2cfe9f31815a057f3c7e28375f117a22584bf",
+ "workspace-app/src/uploadRecording.test.js": "7a5d8cd30f1a7a98e92aaec7e1750e786e776ec23e5a6552f684397c8744a69a",
+ "workspace-app/src/useAnnotationReceipts.js": "761e02d9f9c523880193ffbc16c661475b87b474a1cacd99d8f9349964b10c49",
+ "workspace-app/src/useDesktopDraft.js": "0dcdae9aa69cf869166b2673633f74d568a3b67d32995d607a8658636d1ccde7",
+ "workspace-app/src/useEpochBrowserPage.js": "3e61a3bdb70371a53180463646fece3dca9ede345502932e8e5e787472de30ec",
+ "workspace-app/src/useImportMonitor.js": "caa732e28c72921dbe5609337e71d366e513c465c61cf63bb07b02bdc929fb4c",
+ "workspace-app/src/useImportQueue.js": "9af2c96cdd5eb30e3a6fb768634c79082b0804851cc7ee15028b8296e80e8071",
+ "workspace-app/src/useProjectPreference.js": "c920a9fed4d829a5a768aab1f595b9e3f8ce2dd9df02182deeaa9d4d82ea4e1e",
+ "workspace-app/src/useProtocolTreeLayout.js": "edb12b76a4349ae940ed8e38ab52a6c9062ea867045889e1b612dc9baeef2c0d",
+ "workspace-app/src/useWorkspaceChanges.js": "cfb71618d364764fd6fc9060a5d8e3a5550fb61de81562cf25bd942c2baa4670",
+ "workspace-app/src/useWorkspaceNavigation.js": "19d42b45a2456dca6410c357a9a266dcc90e1dc983dc031d24a4ef0d986d9378",
+ "workspace-app/src/workflowResponsiveness.test.js": "2b4d5af0ca966e399d16a9af37a38a63f794daf9dc4c25e5d8e6925c126488be",
+ "workspace-app/src/workspaceNavigation.js": "cc44e94ca7a4ed4b2b5735d1b737509b8ce02acbe23f972852d60400c0d9581d",
+ "workspace-app/src/workspaceNavigation.test.js": "713433cb63af4f325df982405edd6bddb50f66aec2a53277529ff7d4092fa0b4"
+ },
+ "total_bytes": 5395127,
+ "scope": "Python sources/config/tests, frontend audit source/package metadata, retinanalysis src, three benchmark scripts; excludes runtime binaries/databases/node_modules/bytecode.",
+ "unchanged_after_500_and_before_100000": true,
+ "unchanged_after_both_scales": true,
+ "validated_receipts": [
+ "native-tag-sequence-500.json",
+ "native-tag-sequence-100000.json"
+ ]
+}
diff --git a/docs/dev/scale-audit-2026-09-29/native_generation_authority_probe.py b/docs/dev/scale-audit-2026-09-29/native_generation_authority_probe.py
new file mode 100644
index 0000000..8f2845d
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/native_generation_authority_probe.py
@@ -0,0 +1,283 @@
+"""Independent native checks of the production generation authority.
+
+Only creates its own disposable project; intentionally kills only the verified
+owned MySQL child. Minimal source tables exercise the real production triggers.
+This does not certify DataJoint/service integration or artifact restore.
+"""
+import argparse
+from dataclasses import asdict
+import hashlib
+import json
+from pathlib import Path
+import shutil
+import struct
+import tempfile
+import threading
+import time
+import traceback
+import uuid
+
+import pymysql
+from workspace_projects import create_project
+from workspace_native_mysql import ensure_native_database, stop_native_database, connection_parameters, _owned_process
+import workspace_state_generation as generation
+
+
+class Connection:
+ """Only adapt cursor/result spelling; no generation behavior is emulated."""
+ def __init__(self, project):
+ self.project=project;self.in_transaction=False;self.reconnect()
+ def reconnect(self):
+ self._conn=pymysql.connect(**connection_parameters(self.project),autocommit=True)
+ def query(self,sql,args=(),as_dict=False,reconnect=False):
+ cursor=self._conn.cursor(pymysql.cursors.DictCursor if as_dict else pymysql.cursors.Cursor)
+ cursor.execute(sql,args);return cursor
+ def close(self):
+ self._conn.close()
+
+
+def main():
+ ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--output',type=Path,required=True);args=ap.parse_args()
+ if args.output.exists():ap.error('Choose an unused output file')
+ root=Path(tempfile.mkdtemp(prefix='rieke-native-authority-'));project=None;connections=[]
+ before_hash=hashlib.sha256(Path(generation.__file__).read_bytes()).hexdigest()
+ report={'scope':'Production authority + native SQL; minimal source table adapter; not service/restore certification',
+ 'source_sha256_before':before_hash,'probe_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
+ 'cases':[],'release_ready':False}
+ def check(name,condition,**extra):
+ row={'name':name,'passed':bool(condition),**extra};report['cases'].append(row);print(json.dumps(row,default=str),flush=True)
+ if not condition:raise AssertionError(name)
+ try:
+ project=Path(create_project(root/'projects','Native authority probe')['path']);runtime=ensure_native_database(project)
+ connection=Connection(project);other=Connection(project);connections=[connection,other]
+ sql=lambda statement,params=():connection.query(statement,params).fetchall()
+ external=lambda statement,params=():other.query(statement,params).fetchall()
+ project_id=str(uuid.UUID(int=1));project_two=str(uuid.UUID(int=2));protocol=str(uuid.UUID(int=3));protocol_two=str(uuid.UUID(int=4))
+ epoch=str(uuid.UUID(int=5));epoch_two=str(uuid.UUID(int=6));profile=str(uuid.UUID(int=7));profile_two=str(uuid.UUID(int=8))
+ report['mysql_version']=sql('SELECT VERSION()')[0][0]
+ sql('CREATE DATABASE IF NOT EXISTS recording_workspace')
+ definitions={
+ 'curation':'project_uuid VARCHAR(36),protocol_uuid VARCHAR(36),epoch_uuid VARCHAR(36),tags JSON,revision INT UNSIGNED,PRIMARY KEY(project_uuid,protocol_uuid,epoch_uuid)',
+ 'shared_annotation':'project_uuid VARCHAR(36),target_kind VARCHAR(16),target_uuid VARCHAR(36),profile_uuid VARCHAR(36),tags JSON,author_name VARCHAR(120),revision INT UNSIGNED,PRIMARY KEY(project_uuid,target_kind,target_uuid,profile_uuid)',
+ 'annotation_profile':'project_uuid VARCHAR(36),profile_uuid VARCHAR(36),display_name VARCHAR(120),PRIMARY KEY(project_uuid,profile_uuid)',
+ }
+ for table,definition in definitions.items():sql(f'CREATE TABLE recording_workspace.{table} ({definition}) ENGINE=InnoDB')
+ authority=generation.bootstrap(connection,project_id);other_authority=generation.bootstrap(other,project_id)
+ check('bootstrap',authority.ready and other_authority.ready,reason=authority.reason)
+ external("SELECT GET_LOCK('rieke_state_generation_v1',0)")
+ def release_bootstrap():
+ time.sleep(.1);external("SELECT RELEASE_LOCK('rieke_state_generation_v1')")
+ releaser=threading.Thread(target=release_bootstrap,daemon=True);releaser.start();started=time.perf_counter()
+ try:contended=generation.bootstrap(connection,project_id)
+ finally:releaser.join(timeout=5)
+ check('concurrent_bootstrap_waits_and_recovers',contended.ready and time.perf_counter()-started>=.08,reason=contended.reason)
+ def token(ident=protocol):
+ value=authority.token(ident)
+ if value is None:raise AssertionError('Authority unavailable: '+str(authority.reason))
+ return value
+ def head(value):return (value.protocol_epoch,value.protocol_generation)
+ initial=token();check('second_connection_agrees_on_scope',head(initial)==head(other_authority.token(protocol)))
+ external('INSERT INTO recording_workspace.curation VALUES (%s,%s,%s,%s,1)',(project_id,protocol,epoch,json.dumps(['QC','qc','é','e\u0301','first'])))
+ inserted=token();check('curation_insert',head(initial)!=head(inserted))
+ external('UPDATE recording_workspace.curation SET tags=%s',(json.dumps(['QC','qc','é','e\u0301','other']),))
+ updated=token();check('same_revision_external_update',head(inserted)!=head(updated) and sql('SELECT revision FROM recording_workspace.curation')==((1,),))
+ external('REPLACE INTO recording_workspace.curation VALUES (%s,%s,%s,%s,1)',(project_id,protocol,epoch,json.dumps(['replacement'])))
+ replaced=token();check('replace_delete_and_insert',replaced.protocol_generation==updated.protocol_generation+2)
+ old_two=token(protocol_two)
+ external('UPDATE recording_workspace.curation SET protocol_uuid=%s',(protocol_two,))
+ moved=token();new_two=token(protocol_two);check('protocol_key_move_invalidates_both',head(moved)!=head(replaced) and head(old_two)!=head(new_two))
+ third=generation.bootstrap(other,project_two);old_project=token(protocol_two);new_project=third.token(protocol_two)
+ external('UPDATE recording_workspace.curation SET project_uuid=%s',(project_two,))
+ check('project_key_move_invalidates_both',head(old_project)!=head(token(protocol_two)) and head(new_project)!=head(third.token(protocol_two)))
+ external('UPDATE recording_workspace.curation SET project_uuid=%s,protocol_uuid=%s',(project_id,protocol))
+ before=token();rows=sql('SELECT * FROM recording_workspace.curation');other._conn.begin()
+ external('UPDATE recording_workspace.curation SET tags=%s',(json.dumps(['rolled back']),))
+ during=token();other._conn.rollback()
+ check('rollback_preserves_visible_counter_and_rows',before==during==token() and rows==sql('SELECT * FROM recording_workspace.curation'))
+ before=token();external('DELETE FROM recording_workspace.curation');check('delete_last_row',head(before)!=head(token()))
+ external('INSERT INTO recording_workspace.curation VALUES (%s,%s,%s,%s,1)',(project_id,protocol,epoch,json.dumps(['restored'])))
+ # Shared/profile updates and old-key tombstones share project commit order.
+ before=token();external('INSERT INTO recording_workspace.annotation_profile VALUES (%s,%s,%s)',(project_id,profile,'same name'))
+ after=token();check('profile_insert',before.shared_generationold_generation and feed_count<=maximum,
+ old_generation=old_generation,generation=floor[0],retained_after=floor[1],feed_rows=feed_count,inserted=count,seconds=time.perf_counter()-started)
+ save('expired_delete_tombstone_requires_full_without_resurrecting_row',incremental=False)
+ def delete_extras():
+ with second._conn.cursor() as cursor:
+ for start in range(0,count,1000):
+ cursor.executemany('DELETE FROM recording_workspace.shared_annotation WHERE project_uuid=%s AND target_kind=%s AND target_uuid=%s AND profile_uuid=%s',
+ [(row['project_uuid'],row['target_kind'],row['target_uuid'],row['profile_uuid']) for row in extras[start:start+1000]])
+ if ring:
+ def ring_state():
+ clock=second.query('SELECT scope_epoch,generation,retained_after FROM recording_workspace.app_recovery_clock WHERE project_uuid=%s',(project_id,)).fetchall()
+ rows=second.query('SELECT slot,table_id,key_bytes,generation,deleted FROM recording_workspace.app_recovery_changes WHERE project_uuid=%s ORDER BY slot',(project_id,)).fetchall()
+ return hashlib.sha256(repr((clock,rows)).encode()).hexdigest()
+ ring_before=ring_state();second._conn.begin();delete_extras()
+ overwritten=ring_state()!=ring_before;second._conn.rollback()
+ check('rolled_back_full_ring_overwrite_restores_every_slot_and_floor',overwritten and ring_state()==ring_before)
+ save('ring_rollback_preserves_complete_scientific_state',incremental=True)
+ with second.transaction:delete_extras()
+ del model['shared_annotation'][-count:]
+ save('expired_bulk_deletion_feed_requires_full_without_resurrection',incremental=False)
+ if args.legacy_migrations:
+ specs=generation.table_specs(connection)
+ def trigger_rows():
+ return second.query('SELECT TRIGGER_NAME,EVENT_OBJECT_TABLE,EVENT_MANIPULATION,ACTION_TIMING,ACTION_STATEMENT '
+ 'FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s ORDER BY TRIGGER_NAME',(generation.SCHEMA,),as_dict=True).fetchall()
+ def install(trigger_name,trigger):
+ second.query(f'CREATE TRIGGER recording_workspace.`{trigger_name}` AFTER {trigger["event"]} ON '
+ f'recording_workspace.`{trigger["table"]}` FOR EACH ROW {trigger["body"]}')
+ for trim in (False,True):
+ label='trimmed' if trim else 'untrimmed'
+ for row in trigger_rows():
+ if row['TRIGGER_NAME'].startswith(generation.PREFIX):second.query(f'DROP TRIGGER recording_workspace.`{row["TRIGGER_NAME"]}`')
+ second.query('DROP TABLE recording_workspace.app_recovery_changes')
+ second.query('CREATE TABLE recording_workspace.app_recovery_changes (project_uuid varchar(36) NOT NULL,'
+ 'table_id smallint unsigned NOT NULL,key_bytes varbinary(1024) NOT NULL,generation bigint unsigned NOT NULL,'
+ 'deleted tinyint unsigned NOT NULL,PRIMARY KEY(project_uuid,table_id,key_bytes),'
+ 'KEY generation_lookup(project_uuid,generation)) ENGINE=InnoDB')
+ old=generation.manifest_from_specs(specs,legacy=trim)
+ for name,trigger in old.items():install(name,trigger)
+ update('shared_annotation',0,'tags',['QC','qc','é','e\u0301','legacy-'+label])
+ save('legacy_'+label+'_migration_forces_exact_full_checkpoint',incremental=False)
+ columns=second.query('SHOW COLUMNS FROM recording_workspace.app_recovery_changes').fetchall()
+ check('legacy_'+label+'_feed_migrates_to_fixed_slots',any(row[0]=='slot' for row in columns))
+ expected=generation.manifest_from_specs(specs);name=next(iter(expected));trigger=expected[name]
+ second.query(f'DROP TRIGGER recording_workspace.`{name}`')
+ install(name,{**trigger,'body':'BEGIN SET @recovery_oracle_unknown = 1; END'})
+ before_triggers=trigger_rows();before_table=second.query("SELECT TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME='recording_workspace/app_recovery_changes'").fetchone()
+ rejected=generation.RecoveryTracker(second,project_id).bootstrap()
+ check('unknown_owned_trigger_body_refuses_without_replacing_tables_or_triggers',not rejected.ready and trigger_rows()==before_triggers and
+ second.query("SELECT TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME='recording_workspace/app_recovery_changes'").fetchone()==before_table,
+ reason=rejected.reason)
+ second.query(f'DROP TRIGGER recording_workspace.`{name}`');install(name,trigger)
+ save('known_trigger_repair_forces_exact_full_checkpoint',incremental=False)
+ before_clock=connection.query('SELECT scope_epoch,generation FROM recording_workspace.app_recovery_clock WHERE project_uuid=%s',(project_id,)).fetchone()
+ second._conn.begin()
+ second.query('UPDATE recording_workspace.shared_annotation SET tags=%s WHERE project_uuid=%s',(json.dumps(['uncommitted at server crash']),project_id))
+ owned=_owned_process(runtime)
+ if owned is None:raise AssertionError('Owned disposable MySQL process is missing')
+ owned.kill();owned.wait(timeout=10)
+ with contextlib.suppress(Exception):second.close()
+ with contextlib.suppress(Exception):connection.close()
+ runtime=ensure_native_database(root);dj=connect({'kind':'native-project'},project_dir=root);connection=dj.conn(reset=True);second.reconnect()
+ service=SimpleNamespace();service_two=SimpleNamespace()
+ after_clock=connection.query('SELECT scope_epoch,generation FROM recording_workspace.app_recovery_clock WHERE project_uuid=%s',(project_id,)).fetchone()
+ check('server_sigkill_rolls_back_source_and_recovery_clock_together',before_clock==after_clock)
+ save('restart_forces_coherent_full_recovery_checkpoint',incremental=False)
+ # Restore actual SQL after all current authored tables are cleared.
+ desired=expected_state();restore_point=temporary/'restore-point.sqlite';shutil.copy2(mirror.inspect(root)['path'],restore_point)
+ with second.transaction:
+ for table in snapshot.TABLES:second.query(f'DELETE FROM recording_workspace.`{table}` WHERE project_uuid=%s',(project_id,))
+ (root/'protocols'/(protocol+'.json')).write_text(json.dumps({'temporary':'must be replaced'}))
+ with patch.object(snapshot,'query_service',side_effect=AssertionError('Explicit frozen members must never be requeried')):
+ result=snapshot.restore(root,connection,restore_point)
+ check('actual_sql_restore_exact_current_state',canonical(snapshot.load(root/'app-state.json'))==canonical(desired))
+ actual={}
+ for table in snapshot.TABLES:
+ rows=connection.query(f'SELECT * FROM recording_workspace.`{table}` WHERE project_uuid=%s',(project_id,),as_dict=True).fetchall()
+ actual[table]=[{key:json.loads(value) if key in JSON_FIELDS.get(table,set()) and isinstance(value,(str,bytes)) else value for key,value in row.items()} for row in rows]
+ check('restored_native_rows_match_independent_oracle',canonical({**desired,'tables':actual})==canonical(desired))
+ check('restored_protocol_file_exact',json.loads((root/'protocols'/(protocol+'.json')).read_text())==desired['protocols'][protocol+'.json'])
+ check('no_op_save_remains_incremental',snapshot.save(root,connection,service=service,day='2026-09-29')['incremental'])
+ report['storage']=mirror.storage(root);report['source_hashes_after']=hashes();report['source_unchanged']=report['source_hashes_before']==report['source_hashes_after']
+ except Exception as error:
+ report['error']={'type':type(error).__name__,'message':str(error),'traceback':traceback.format_exc()};print(traceback.format_exc(),flush=True)
+ finally:
+ if second is not None:
+ with contextlib.suppress(Exception):second.close()
+ if dj is not None:
+ with contextlib.suppress(Exception):dj.conn().close()
+ if root is not None:
+ try:clean=stop_native_database(root)
+ except Exception as error:report['shutdown_error']=str(error)
+ report['clean_native_shutdown']=clean
+ if clean:shutil.rmtree(temporary);report['temporary_project_removed']=True
+ else:report['preserved_temporary_project']=str(temporary)
+ report['passed']=not report.get('error') and clean and report.get('source_unchanged',False) and all(item['passed'] for item in report['cases'])
+ args.output.write_text(json.dumps(report,indent=2,default=str)+'\n')
+ return int(not report['passed'])
+
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/run_stress_gates.py b/docs/dev/scale-audit-2026-09-29/run_stress_gates.py
new file mode 100644
index 0000000..bcac349
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/run_stress_gates.py
@@ -0,0 +1,101 @@
+"""Run implemented exact-data stress gates; preserve failures and evidence.
+
+Uses the current Python interpreter and installed Node. Always runs all stages,
+even after a failed gate. No real lab projects or production databases accessed.
+Exit 1 means an implemented gate failed. release_ready additionally requires
+the separately listed browser/native crash/power-loss trials.
+"""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import platform
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import time
+
+HERE=Path(__file__).resolve().parent
+REPO=HERE.parents[2]
+
+
+def main():
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs',type=int,default=100000)
+ parser.add_argument('--output-dir',type=Path)
+ args=parser.parse_args()
+ if not 100<=args.epochs<=1000000:parser.error('--epochs must be 100–1000000')
+ output=(args.output_dir or Path(tempfile.mkdtemp(prefix='rieke-stress-results-'))).resolve()
+ output.mkdir(parents=True,exist_ok=True)
+ if (output/'summary.json').exists():parser.error('Output already contains a run; choose a new directory to preserve evidence')
+ env=dict(os.environ)
+ env['PYTHONPATH']=os.pathsep.join([str(REPO/'python'),str(REPO/'python/tests'),env.get('PYTHONPATH','')])
+ code_files=set((REPO/'python').glob('*.py')) | set((REPO/'python/tests').glob('*.py'))
+ code_files.update(path for path in (REPO/'workspace-app/src').rglob('*') if path.suffix in {'.js','.jsx'})
+ code_files.update(HERE.glob('*.py'));code_files.update(HERE.glob('*.mjs'))
+ parser_root=REPO/'.rieke-runtime/retinanalysis'
+ code_files.update(parser_root.rglob('*.py'))
+ source_paths=sorted(str(path.relative_to(REPO)) for path in code_files)
+ hashes={name:hashlib.sha256((REPO/name).read_bytes()).hexdigest() for name in source_paths}
+ node=shutil.which('node') or 'node'
+ npm=shutil.which('npm') or 'npm'
+ stages=[
+ ('large_index_contracts',[sys.executable,str(HERE/'stress_contracts.py'),'--epochs',str(args.epochs),'--output',str(output/'contracts.json')]),
+ ('strict_acquisition_identity',[sys.executable,str(HERE/'safety_identity_probes.py'),'--strict','--output',str(output/'identity.json')]),
+ ('strict_async_selection',[node,str(HERE/'frontend-selection-race.mjs'),'--strict','--output',str(output/'selection.json')]),
+ ('trace_integrity',[sys.executable,str(HERE/'benchmark_trace_integrity.py'),'--output',str(output/'trace.json')]),
+ ('derived_index_process_kill',[sys.executable,'-m','unittest','test_workspace_disk_index_crash']),
+ ('safety_scale_regressions',[sys.executable,'-m','unittest',
+ 'test_workspace_source_identity','test_workspace_catalog_identity','test_workspace_catalog_collision',
+ 'test_workspace_backend_responsiveness','test_workspace_epoch_page_performance','test_workspace_protocol_state','test_workspace_state_generation',
+ 'test_workspace_shared_tag_index','test_workspace_shared_tag_cache_dependencies','test_workspace_shared_tag_threads','test_workspace_shared_vocabulary','test_workspace_annotation_density',
+ 'test_workspace_metadata_objects','test_workspace_metadata_object_contract','test_workspace_cache_lifecycle',
+ 'test_workspace_projection_cache','test_workspace_projection_objects','test_workspace_cache_publication',
+ 'test_workspace_disk_index','test_workspace_tree_pages','test_workspace_refresh_cache']),
+ ('export_tag_restore_regressions',[sys.executable,'-m','unittest',
+ 'test_recording_workspace','test_workspace_import_identities','test_workspace_identity_conflicts',
+ 'test_workspace_export_reader','test_workspace_sqlite',
+ 'test_workspace_state_snapshot','test_workspace_recovery_store','test_workspace_generation_portability',
+ 'test_workspace_recovery_generation',
+ 'test_workspace_curation_batch',
+ 'test_workspace_tag_lifecycle','test_workspace_tag_exchange',
+ 'test_workspace_portability','test_workspace_native_crash',
+ 'test_workspace_import_progress_failures']),
+ ('frontend_regressions',[npm,'--prefix',str(REPO/'workspace-app'),'test']),
+ ('frontend_build',[npm,'--prefix',str(REPO/'workspace-app'),'run','build'])]
+ report={'epochs':args.epochs,'output_directory':str(output),'python':platform.python_version(),
+ 'platform':platform.platform(),'source_sha256':hashes,'stages':[],
+ 'required_unexecuted_trials':['Real browser 100k interaction/revision races',
+ 'Physical large multi-source H5 parse/import/export against independent waveform oracle',
+ 'Native MySQL/API process-kill recovery matrix','Clean-machine complete portable restore drill',
+ 'Power-loss/filesystem fault and disk-full trials','Tens-of-GB lab NAS cold/warm throughput and soak']}
+ for name,command in stages:
+ started=time.perf_counter();log=output/(name+'.log')
+ print('Running '+name,flush=True)
+ error=None
+ with log.open('w') as stream:
+ try:finished=subprocess.run(command,cwd=REPO,env=env,stdout=stream,stderr=subprocess.STDOUT,timeout=1800);code=finished.returncode
+ except (OSError,subprocess.TimeoutExpired) as failure:
+ error=str(failure);stream.write(error+'\n');code=-1
+ item={'name':name,'status':'PASS' if code==0 else 'FAIL','exit_code':code,
+ 'seconds':time.perf_counter()-started,'log':str(log)}
+ if error:item['error']=error
+ raw=log.read_text()
+ if name=='export_tag_restore_regressions':
+ count=re.search(r'Ran (\d+) tests?',raw);skipped=re.search(r'skipped=(\d+)',raw)
+ item.update(tests_run=int(count.group(1)) if count else None,skipped=int(skipped.group(1)) if skipped else 0)
+ report['stages'].append(item)
+ print(name+': '+item['status'],flush=True)
+ report['release_ready']=False
+ report['source_changed_during_run']=[name for name,digest in hashes.items()
+ if hashlib.sha256((REPO/name).read_bytes()).hexdigest()!=digest]
+ report['implemented_gates_passed']=all(stage['status']=='PASS' for stage in report['stages']) and not report['source_changed_during_run']
+ (output/'summary.json').write_text(json.dumps(report,indent=2)+'\n')
+ print('Evidence: '+str(output/'summary.json'),flush=True)
+ return 0 if report['implemented_gates_passed'] else 1
+
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/docs/dev/scale-audit-2026-09-29/safety_benchmark.py b/docs/dev/scale-audit-2026-09-29/safety_benchmark.py
new file mode 100644
index 0000000..e84a1aa
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/safety_benchmark.py
@@ -0,0 +1,65 @@
+import sys,contextlib,copy,datetime as dt,json,platform,statistics,tempfile,time,uuid
+from pathlib import Path
+from types import SimpleNamespace
+sys.path[:0]=['python','python/tests']
+from test_workspace_curation import Table
+from workspace_annotations import SharedAnnotations
+from workspace_state_snapshot import save,TABLES
+
+def uid(i):return str(uuid.UUID(int=i))
+class Result:
+ def __init__(self,rows):self.rows=rows
+ def fetchall(self):return self.rows
+ def fetchone(self):return self.rows[0] if self.rows else None
+class Connection:
+ in_transaction=False
+ def __init__(self,state):self.state=state;self.sql_reads=[]
+ @property
+ def transaction(self):return contextlib.nullcontext()
+ def query(self,sql,args=None,as_dict=False):
+ self.sql_reads.append(sql)
+ if 'GET_LOCK' in sql or 'RELEASE_LOCK' in sql:return Result([(1,)])
+ if 'information_schema.tables' in sql:return Result([(x,) for x in self.state]+[('source',)])
+ if sql.startswith('SHOW COLUMNS'):return Result([('tags','json')] if 'shared_annotation' in sql else [('recipe','json'),('summary','json')])
+ if sql.startswith('SELECT source_sha256,manifest'):return Result([('a'*64,{'source_path':'synthetic.h5','metadata_sha256':'b'*64})])
+ for key,rows in self.state.items():
+ if f'`{key}`' in sql:return Result(rows)
+ raise ValueError(sql)
+
+def measure(fn,repetitions=3):
+ values=[]
+ for _ in range(repetitions):
+ start=time.perf_counter();out=fn();values.append((time.perf_counter()-start)*1000)
+ return {'median_ms':round(statistics.median(values),3),'runs_ms':[round(x,3) for x in values]},out
+
+results={'date':'2026-09-29','method':'Synthetic metadata and annotation tables; actual SharedAnnotations.apply_batch, change_revision, suggestions, state snapshot capture/compact/save functions. SQL double, no network/real SQL latency, no transaction copying. Snapshot writes/fsync are real into TemporaryDirectory. Dataset contains one annotation per epoch, one author, two tags, one source and 100 epochs per cell. Single tag mutation only. Three runs per operation; project fully resident before measurement.','python':platform.python_version(),'platform':platform.platform(),'cases':[]}
+for n in (1000,10000,50000,100000):
+ project=uid(1);author=uid(2);now=dt.datetime(2026,9,29)
+ rows={uid(1000+i):{'epoch_uuid':uid(1000+i),'cell_uuid':uid(1000000+i//100),'source_sha256':'a'*64} for i in range(n)}
+ annotations=[{'project_uuid':project,'target_kind':'epoch','target_uuid':key,'profile_uuid':author,'tags':['QC','reviewed'],'author_name':'Synthetic author','revision':1,'updated_at':now} for key in rows]
+ state={'shared_annotation':annotations,'annotation_profile':[{'project_uuid':project,'profile_uuid':author,'display_name':'Synthetic author','created_at':now,'created_by':'actor'}]}
+ connection=Connection(state);dj=SimpleNamespace(conn=lambda:connection)
+ service=SimpleNamespace(project={'project_uuid':project},dj=dj,rows=rows,cells={r['cell_uuid']:{} for r in rows.values()},_fingerprints={key:'b'*64 for key in rows},match_predicate=lambda predicate,eligible:(predicate,eligible,None))
+ table=Table(('project_uuid','target_kind','target_uuid','profile_uuid'),annotations)
+ profiles=Table(('project_uuid','profile_uuid'),state['annotation_profile']);events=Table(('event_uuid',))
+ store=SharedAnnotations(service,(profiles,table,events));key=next(iter(rows));counter=[1]
+ def update():
+ result=store.apply_batch([{'target_kind':'epoch','target_uuid':key,'profile_uuid':author,'tags_add':['hot' if counter[0]%2 else 'cold'],'tags_remove':['cold' if counter[0]%2 else 'hot'],'expected_revision':counter[0]}],'actor');counter[0]+=1;return result
+ mutation,_=measure(update)
+ full_annotation_reads=[len(annotations) for r in table.read_log if all(not isinstance(x,list) for x in r['restrictions'])]
+ revision,_=measure(store.change_revision)
+ def suggestions():store._vocabulary=None;return store.suggestions('r',12)
+ vocabulary,_=measure(suggestions)
+ case={'epochs':n,'annotation_rows':n,'single_annotation_apply_batch':mutation,'full_annotation_rows_loaded_per_write':full_annotation_reads[:3],'cross_process_change_revision':revision,'suggestions_rebuild':vocabulary,'snapshots':[]}
+ with tempfile.TemporaryDirectory(prefix='rieke-safety-') as directory:
+ root=Path(directory);(root/'protocols').mkdir();(root/'project.json').write_text(json.dumps({'project_uuid':project,'format':'recording-project','version':1,'name':'Synthetic'}))
+ for pin_count in (0,1,10):
+ members=[{'uuid':key,'metadata_hash':'b'*64} for key in sorted(rows)]
+ state['explorer_revision']=[{'project_uuid':project,'revision_uuid':uid(500000+i),'parent_revision_uuid':None,'summary':{},'recipe':{'revision_uuid':uid(500000+i),'epochs':members,'predicate':{'all':[]},'source_revisions':['a'*64],'diff':{},'content_sha256':'c'*64}} for i in range(pin_count)]
+ state['protocol_binding']=[{'project_uuid':project,'protocol_uuid':uid(600000+i),'revision_uuid':uid(500000+i)} for i in range(pin_count)]
+ def snapshot():return save(root,connection,day='2026-09-29',service=service)
+ timings,out=measure(snapshot)
+ case['snapshots'].append({'active_pins':pin_count,'save':timings,'state_bytes':out['bytes'],'changed_last_run':out['changed']})
+ results['cases'].append(case)
+ print(json.dumps(case),flush=True)
+Path('docs/dev/scale-audit-2026-09-29/safety-benchmark.json').write_text(json.dumps(results,indent=2)+'\n')
diff --git a/docs/dev/scale-audit-2026-09-29/safety_identity_probes.py b/docs/dev/scale-audit-2026-09-29/safety_identity_probes.py
new file mode 100644
index 0000000..3b46a73
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/safety_identity_probes.py
@@ -0,0 +1,84 @@
+import argparse,copy,json,re,sys,tempfile,uuid
+from pathlib import Path
+from unittest.mock import patch
+import h5py,numpy as np
+sys.path.insert(0,'python')
+import recording_workspace as workspace
+from workspace_service import WorkspaceService,read_response_window
+
+def uid(n):return str(uuid.UUID(int=n))
+REJECTION_PATTERNS={
+ 'wrong_cell_and_group':r'(cell|group).*(identity|uuid|ownership|source|mismatch|differ|mapping)|(identity|uuid|ownership|mismatch|differ|mapping).*(cell|group)',
+ 'duplicate_raw_epoch_omitted_by_parser':r'(duplicate|multiplicity|membership|more than once).*(epoch|uuid)|(epoch|uuid).*(duplicate|multiplicity|membership|more than once)',
+ 'wrong_block':r'epoch points to a different source block',
+ 'external_link_data_changed':r'(external|virtual|dependenc).*(link|data|stor|source|unsupported|not|reject|seal)|(link|data|stor|source|unsupported|reject|seal).*(external|virtual|dependenc)'}
+def expected_validation_rejection(variant,error):
+ return isinstance(error,ValueError) and bool(re.search(REJECTION_PATTERNS.get(variant,r'(?!)'),str(error),re.I))
+# A runtime/setup error must never make a hostile fixture green.
+for variant in REJECTION_PATTERNS:
+ for unrelated in (OSError('Disk unavailable'),KeyError('Missing field'),ValueError('Metadata checksum mismatch')):
+ assert not expected_validation_rejection(variant,unrelated)
+arguments=argparse.ArgumentParser(description='Reproduce importer identity gaps on disposable fixtures; --strict fails when unsafe fixtures are accepted.')
+arguments.add_argument('--strict',action='store_true')
+arguments.add_argument('--output',type=Path)
+args=arguments.parse_args()
+parser,parser_path=workspace.load_parser(Path('.rieke-runtime/retinanalysis').resolve())
+results={'date':'2026-09-29','method':'Tiny disposable real HDF5 recording with actual pinned parser ExperimentObj/parse_value. Cached parser document is generated and checksum sealed in the fixture to simulate semantically wrong parser output. Calls production prepare() and read-model _read_source_metadata(); no live project/catalog mutation. This tests validator behavior, not evidence that the parser produces these errors in real data.','unrelated_exception_controls_passed':12,'probes':[]}
+for variant in ('valid','wrong_cell_and_group','duplicate_raw_epoch_omitted_by_parser','wrong_block','external_link_data_changed'):
+ with tempfile.TemporaryDirectory(prefix='rieke-identity-') as folder:
+ root=Path(folder);source=root/'fixture.h5';epoch_path=f'/experiment-{uid(1)}/epochGroups/group-{uid(3)}/epochBlocks/block-{uid(4)}/epochs/epoch-{uid(5)}';stream_path=epoch_path+'/responses/Amp1-'+uid(6)
+ with h5py.File(source,'w') as h5:
+ experiment=h5.create_group('/experiment-'+uid(1));experiment.attrs['uuid']=uid(1).encode();experiment.attrs['label']=b'Experiment';experiment.create_group('properties')
+ animal=experiment.create_group('sources/animal-'+uid(7));animal.attrs['uuid']=uid(7).encode();animal['experiment']=experiment
+ preparation=animal.create_group('sources/preparation-'+uid(8));preparation.attrs['uuid']=uid(8).encode()
+ cell=h5.create_group(preparation.name+'/sources/cell-'+uid(2));cell.attrs['uuid']=uid(2).encode();cell.attrs['label']=b'Actual cell'
+ group=h5.create_group(experiment.name+'/epochGroups/group-'+uid(3));group.attrs['uuid']=uid(3).encode();group['source']=cell
+ block=h5.create_group(group.name+'/epochBlocks/block-'+uid(4));block.attrs['uuid']=uid(4).encode();block.attrs['protocolID']=b'example.Protocol';block.create_group('protocolParameters')
+ epoch=h5.create_group(epoch_path);epoch.attrs['uuid']=uid(5).encode();epoch.create_group('protocolParameters')
+ stream=h5.create_group(stream_path);stream.attrs['uuid']=uid(6).encode();stream.attrs['sampleRate']=10000.
+ data=np.zeros(4,dtype=[('quantity','f8'),('units','S8')]);data['quantity']=np.arange(4);data['units']=b'pA';
+ if variant=='external_link_data_changed':
+ with h5py.File(root/'external.h5','w') as external:external.create_dataset('values',data=data)
+ stream['data']=h5py.ExternalLink(str(root/'external.h5'),'/values')
+ else:stream.create_dataset('data',data=data)
+ if variant=='duplicate_raw_epoch_omitted_by_parser':
+ duplicate=h5.create_group(block.name+'/epochs/duplicate-physical-epoch');duplicate.attrs['uuid']=uid(5).encode();duplicate.create_group('protocolParameters')
+ epoch={'uuid':uid(5),'start_time':'09/29/2026 12:00:00:000000','parameters':{},'properties':{},'attributes':{},'responses':{'Amp1':{'uuid':uid(6),'h5path':stream_path,'sampleRate':10000.,'sampleRateUnits':'Hz'}},'stimuli':{}}
+ block={'uuid':uid(4),'protocolID':'example.Protocol','epochs':[epoch]};group={'uuid':uid(3),'epoch_blocks':[block]};cell={'uuid':uid(2),'label':'Actual cell','start_time':'09/29/2026 11:00:00:000000','epoch_groups':[group]}
+ if variant=='wrong_cell_and_group':cell.update(uuid=uid(100),label='Wrong cell');group['uuid']=uid(101)
+ if variant=='wrong_block':block['uuid']=uid(104)
+ raw={'uuid':uid(1),'rig_type':'PATCH','animals':[{'uuid':uid(7),'preparations':[{'uuid':uid(8),'cells':[cell]}]}]}
+ sha=workspace.digest(source);project=root/'project';cache=project/'imports'/('fixture-'+sha[:12]);cache.mkdir(parents=True);raw_path=cache/'metadata.raw.json';workspace.write_json(raw_path,raw)
+ workspace.write_json(cache/'parse-manifest.json',{'status':'parsed','sha256':sha,'metadata_sha256':workspace.digest(raw_path),'parser_sha256':workspace.digest(parser_path)})
+ outcome={'variant':variant,'actual_h5_cell_uuid':uid(2),'actual_h5_group_uuid':uid(3),'actual_h5_epoch_objects':2 if variant=='duplicate_raw_epoch_omitted_by_parser' else 1}
+ try:
+ experiment,rows,manifest,location=workspace.prepare(source,project,Path('.rieke-runtime/retinanalysis').resolve())
+ outcome.update(prepare='accepted',parsed_epochs=manifest['counts']['epochs'],parsed_cell_uuid=rows[0]['cell_uuid'],parsed_group_uuid=rows[0]['group_uuid'])
+ service=WorkspaceService.__new__(WorkspaceService)
+ stat=source.stat();signature=(str(source),stat.st_dev,stat.st_ino,stat.st_size,stat.st_mtime_ns,stat.st_ctime_ns)
+ record={'manifest':manifest,'source_sha256':sha}
+ projection=service._read_source_metadata(record,experiment,source,signature)
+ outcome['read_model']='accepted';outcome['read_model_cell_uuid']=projection['rows'][uid(5)]['cell_uuid']
+ if variant=='external_link_data_changed':
+ trace_row=projection['rows'][uid(5)];trace_stream=trace_row['streams'][0]
+ before=read_response_window(source,signature,trace_row,trace_stream,count=4)['values']
+ with h5py.File(root/'external.h5','r+') as external:
+ values=external['values'][:];values['quantity']+=1000;external['values'][:]=values
+ outcome.update(source_sha_unchanged=workspace.digest(source)==sha,trace_before=before,trace_after=read_response_window(source,signature,trace_row,trace_stream,count=4)['values'])
+ except Exception as error:
+ outcome.update(error_type=type(error).__name__,error=str(error))
+ outcome['expected_validation_error']=expected_validation_rejection(variant,error)
+ outcome['unexpected_runtime_error']=not outcome['expected_validation_error']
+ if 'prepare' not in outcome:outcome['prepare']='rejected'
+ else:outcome['later_validation']='rejected'
+ expected='accepted' if variant=='valid' else 'rejected'
+ outcome['expected_prepare']=expected
+ outcome['gate_passed']=outcome.get('prepare')==expected and (outcome.get('read_model')=='accepted' if variant=='valid' else outcome.get('expected_validation_error',False))
+ results['probes'].append(outcome)
+ print(json.dumps(outcome),flush=True)
+results['strict']=args.strict
+results['failed_gates']=[probe['variant'] for probe in results['probes'] if not probe['gate_passed']]
+results['gate_result']='PASS' if not results['failed_gates'] else 'FAIL'
+output='safety-identity-strict.json' if args.strict else 'safety-identity-probes.json'
+(args.output or Path('docs/dev/scale-audit-2026-09-29',output)).write_text(json.dumps(results,indent=2)+'\n')
+if args.strict and results['failed_gates']:sys.exit(1)
diff --git a/docs/dev/scale-audit-2026-09-29/stress_contracts.py b/docs/dev/scale-audit-2026-09-29/stress_contracts.py
new file mode 100644
index 0000000..68de67e
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/stress_contracts.py
@@ -0,0 +1,225 @@
+"""Exact metadata contracts for one large disposable project.
+
+Independent oracle: generated integer acquisition ordinals determine expected
+parents, sources, pointers and typed membership. No production match/tree code
+is used to compute expected sets. This tests the indexed read model, not the
+real Symphony parser or physical 100k-waveform H5 recording.
+"""
+from __future__ import annotations
+import argparse
+import concurrent.futures
+import copy
+import json
+from pathlib import Path
+import random
+import shutil
+import sqlite3
+import tempfile
+import time
+import unittest
+from unittest.mock import patch
+from benchmark_service_scale import Details, fixture, uid
+from workspace_disk_index import DiskMetadataIndex
+from workspace_tree_pages import StaleTreePage, TreePages
+
+EPOCH_COUNT = 1000
+
+
+class AcquisitionDetails(Details):
+ def __getitem__(self, key):
+ result = super().__getitem__(key)
+ row = self.rows[key]; ordinal = row['synthetic_index']
+ result['parameters']['typed'] = [True, 1, 1.0, '1', None, [1, False]][ordinal%6]
+ if ordinal%7:
+ result['parameters']['sometimes'] = None if ordinal%7 == 1 else 42
+ result['metadata']['group'] = {'uuid':row['group_uuid']}
+ result['metadata']['epoch'] = {'uuid':key}
+ return result
+
+
+class LargeProjectContracts(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.temp = tempfile.TemporaryDirectory(prefix='rieke-stress-contracts-')
+ cls.addClassCleanup(cls.temp.cleanup)
+ cls.folder = Path(cls.temp.name)
+ cls.service, cls.protocol = fixture(EPOCH_COUNT)
+ cls.oracle = {}
+ for ordinal, (key, row) in enumerate(cls.service.rows.items()):
+ source = f'{(ordinal//100)%10+1:064x}'
+ cell, group, block = uid(EPOCH_COUNT+ordinal//100), uid(3*EPOCH_COUNT+ordinal//100), uid(2*EPOCH_COUNT+ordinal//20)
+ stream = uid(5*EPOCH_COUNT+ordinal)
+ pointer = f'/cells/{cell}/groups/{group}/blocks/{block}/epochs/{key}/responses/{stream}'
+ row.update(group_uuid=group, streams=[{'uuid':stream,'kind':'responses','device':'Amp1',
+ 'h5_path':pointer,'data_path':pointer+'/data','sample_rate':10000.,'sample_count':20000,'units':'pA'}])
+ # Store independently defined expected identity relationships.
+ cls.oracle[key] = (ordinal,source,cell,group,block,stream,pointer)
+ cls.raw_details = AcquisitionDetails(cls.service.rows)
+ started = time.perf_counter()
+ cls.index = DiskMetadataIndex.build(cls.folder/'index.sqlite',cls.service.rows,
+ cls.raw_details,cls.service.sources,'stress-generation',cls.service.project['project_uuid'])
+ cls.build_seconds = time.perf_counter()-started
+ cls.service.disk_index = cls.index; cls.service.details = cls.index.details
+ cls.service._fingerprints = cls.index.fingerprints()
+ cls.pager = TreePages(cls.service)
+
+ def expected(self, condition):
+ return {key for key,(ordinal,*_) in self.oracle.items() if condition(ordinal)}
+
+ def test_every_indexed_parent_source_and_pointer_matches_manifest(self):
+ rows = self.index.rows()
+ self.assertEqual(set(rows),set(self.oracle))
+ for key, (_,source,cell,group,block,stream,pointer) in self.oracle.items():
+ row = rows[key]
+ self.assertEqual((row['source_sha256'],row['cell_uuid'],row['group_uuid'],row['block_uuid']),
+ (source,cell,group,block))
+ actual = row['streams'][0]
+ self.assertEqual((actual['uuid'],actual['h5_path'],actual['data_path']),
+ (stream,pointer,pointer+'/data'))
+ self.assertEqual(tuple(actual[field] for field in ('kind','device','sample_rate','sample_count','units')),
+ ('responses','Amp1',10000.,20000,'pA'))
+
+ def test_filters_have_exact_typed_membership(self):
+ cases = [
+ ({'field':'parameters/typed','operator':'eq','value':True},lambda i:i%6==0),
+ ({'field':'parameters/typed','operator':'eq','value':1},lambda i:i%6 in (1,2)),
+ ({'field':'parameters/typed','operator':'eq','value':'1'},lambda i:i%6==3),
+ ({'field':'parameters/typed','operator':'eq','value':[1,False]},lambda i:i%6==5),
+ ({'field':'parameters/typed','operator':'is_null'},lambda i:i%6==4),
+ ({'field':'parameters/sometimes','operator':'missing'},lambda i:i%7==0),
+ ({'field':'parameters/sometimes','operator':'is_null'},lambda i:i%7==1),
+ ({'not':{'field':'parameters/sometimes','operator':'exists'}},lambda i:i%7==0),
+ ({'all':[{'field':'parameters/contrast','operator':'eq','value':.3},
+ {'field':'parameters/currentMean','operator':'gte','value':100}]},lambda i:i%5==3 and i%3>=1)]
+ with patch('h5py.File',side_effect=AssertionError('Metadata filtering must not read waveforms')):
+ for predicate, condition in cases:
+ with self.subTest(predicate=predicate):
+ _, actual = self.index.match(predicate)
+ self.assertEqual(set(actual),self.expected(condition))
+ self.assertEqual(len(actual),len(set(actual)))
+
+ def test_scoped_not_never_selects_foreign_epochs(self):
+ scope = [key for key,(ordinal,*_) in self.oracle.items() if ordinal%13==0]
+ _, actual = self.index.match({'not':{'field':'parameters/contrast','operator':'eq','value':.3}},scope)
+ self.assertEqual(set(actual),self.expected(lambda i:i%13==0 and i%5!=3))
+ self.assertEqual(actual,[key for key in scope if self.oracle[key][0]%5!=3])
+
+ def test_random_uuid_selection_and_details_never_cross_map(self):
+ keys = random.Random(927).sample(list(self.oracle),min(32,EPOCH_COUNT))
+ for key in keys:
+ _, actual = self.index.match({'field':'epoch','operator':'eq','value':key})
+ self.assertEqual(actual,[key])
+ detail = self.index.details[key]; _,_,cell,group,block,*_ = self.oracle[key]
+ self.assertEqual(tuple(detail['metadata'][level]['uuid'] for level in ('cell','group','block','epoch')),
+ (cell,group,block,key))
+
+ def test_small_pages_and_far_anchors_have_exact_chronology(self):
+ ordered = sorted(self.service.rows,key=lambda key:(self.service.rows[key]['date'],
+ self.service.rows[key]['start_time'][11:],key))
+ with patch('h5py.File',side_effect=AssertionError('Page navigation must not read waveforms')):
+ for position in (0,EPOCH_COUNT//2,EPOCH_COUNT-1):
+ key = ordered[position]
+ flat = self.pager.page({'splits':'','anchor_uuid':key,'limit':80})
+ self.assertEqual(flat['anchor']['index'],position)
+ self.assertEqual([row['epoch_uuid'] for row in flat['epochs']],
+ ordered[(position//80)*80:(position//80+1)*80])
+ grouped = self.pager.page({'splits':'cell,block','anchor_uuid':key,'limit':80})
+ self.assertIn(key,[row['epoch_uuid'] for row in grouped['epochs']])
+ self.assertTrue(all(row['cell_uuid']==self.oracle[key][2] and row['block_uuid']==self.oracle[key][4]
+ for row in grouped['epochs']))
+
+ def test_stale_revision_is_rejected_after_fingerprint_change(self):
+ root = self.pager.page({'splits':'cell','limit':80})
+ key = next(iter(self.oracle)); previous = self.service._fingerprints[key]
+ try:
+ self.service._fingerprints[key] = 'c'*64
+ with self.assertRaises(StaleTreePage):
+ self.pager.page({'splits':'cell','limit':80,'revision':root['revision'],
+ 'path':root['branches'][0]['path']})
+ finally:self.service._fingerprints[key] = previous
+
+ def test_source_projection_contains_exact_owned_epochs(self):
+ for source in self.service.sources:
+ sha = source['source_sha256']; projected = self.index.source_projection(sha,lazy_details=True)
+ expected = {key for key,entry in self.oracle.items() if entry[1]==sha}
+ self.assertEqual(set(projected['rows']),expected)
+ self.assertEqual(set(projected['details']),expected)
+ foreign = next((key for key in self.oracle if key not in expected),None)
+ if foreign is not None:
+ with self.assertRaises(KeyError):projected['details'][foreign]
+ # Validate every detail via bounded source partitions, independently
+ # checking relationships and exact JSON representation of parameters.
+ eager = self.index.source_projection(sha,lazy_details=False)
+ for key,detail in eager['details'].items():
+ ordinal,_,cell,group,block,*_ = self.oracle[key]
+ self.assertEqual(tuple(detail['metadata'][level]['uuid'] for level in ('cell','group','block','epoch')),
+ (cell,group,block,key))
+ parameters = {'contrast':(ordinal%5)/10,'seed':ordinal,'stimTime':1000,
+ 'currentMean':(ordinal%3)*100,
+ 'typed':[True,1,1.0,'1',None,[1,False]][ordinal%6]}
+ if ordinal%7:parameters['sometimes']=None if ordinal%7==1 else 42
+ self.assertEqual(json.dumps(detail['parameters'],sort_keys=True),json.dumps(parameters,sort_keys=True))
+ del eager
+
+ def test_concurrent_detail_readers_keep_cache_bounded_and_copies_isolated(self):
+ keys = random.Random(928).sample(list(self.oracle),min(140,EPOCH_COUNT))
+ def read(key):
+ detail = self.index.details[key]
+ self.assertEqual(detail['metadata']['epoch']['uuid'],key)
+ detail['parameters'].clear()
+ self.assertIn('typed',self.index.details[key]['parameters'])
+ with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool:
+ list(pool.map(read,keys*3))
+ self.assertLessEqual(len(self.index.details.cache),64)
+
+ def test_failed_index_rebuild_preserves_last_verified_generation(self):
+ key = next(iter(self.oracle)); datum = copy.deepcopy(self.raw_details[key])
+ datum['parameters']['invalid'] = float('nan')
+ previous_inventory = set(self.folder.iterdir())
+ with self.assertRaises(ValueError):
+ DiskMetadataIndex.build(self.index.path,[self.service.rows[key]],{key:datum},self.service.sources,
+ 'failed-generation',self.service.project['project_uuid'])
+ restored = DiskMetadataIndex.open(self.index.path,'stress-generation',self.service.project['project_uuid'])
+ self.assertEqual(len(restored.details),EPOCH_COUNT)
+ self.assertEqual(restored.details[key],self.raw_details[key])
+ self.assertEqual(set(self.folder.iterdir()),previous_inventory)
+
+ def test_corrupt_index_copy_fails_closed_without_affecting_current_reader(self):
+ corrupt = self.folder/'corrupt.sqlite'
+ try:
+ shutil.copyfile(self.index.path,corrupt)
+ shutil.copyfile(str(self.index.path)+'.sha256.json',str(corrupt)+'.sha256.json')
+ with sqlite3.connect(corrupt) as connection:
+ connection.execute("UPDATE epochs SET row_json='{}' WHERE epoch_id=1")
+ with self.assertRaisesRegex(ValueError,'checksum'):
+ DiskMetadataIndex.open(corrupt,'stress-generation',self.service.project['project_uuid'])
+ self.assertEqual(len(self.index.details),EPOCH_COUNT)
+ finally:
+ corrupt.unlink(missing_ok=True);Path(str(corrupt)+'.sha256.json').unlink(missing_ok=True)
+
+
+class RecordingResult(unittest.TextTestResult):
+ def startTest(self,test):self.started=time.perf_counter();super().startTest(test)
+ def stopTest(self,test):
+ self.records.append({'test':test.id(),'seconds':time.perf_counter()-self.started})
+ super().stopTest(test)
+
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--epochs',type=int,default=1000)
+ parser.add_argument('--output',type=Path,required=True)
+ args=parser.parse_args()
+ if not 100<=args.epochs<=1000000:parser.error('--epochs must be 100–1000000')
+ EPOCH_COUNT=args.epochs
+ RecordingResult.records=[]
+ suite=unittest.defaultTestLoader.loadTestsFromTestCase(LargeProjectContracts)
+ result=unittest.TextTestRunner(verbosity=2,resultclass=RecordingResult).run(suite)
+ report={'epochs':EPOCH_COUNT,'fixture':'One disposable project; synthetic pointers, independent ordinal-based identity/membership oracle.',
+ 'successful':result.wasSuccessful(),'tests_run':result.testsRun,
+ 'failures':[{'test':test.id(),'traceback':trace} for test,trace in result.failures],
+ 'errors':[{'test':test.id(),'traceback':trace} for test,trace in result.errors],
+ 'measurements':result.records,'build_seconds':getattr(LargeProjectContracts,'build_seconds',None),
+ 'limits':'No physical100k-H5 parsing, liveSQL, browser, export or actual process-kill recovery. Strict adversarial import/UI gates are separate and currently fail.'}
+ args.output.write_text(json.dumps(report,indent=2)+'\n')
+ raise SystemExit(0 if result.wasSuccessful() else 1)
diff --git a/docs/dev/scale-audit-2026-09-29/summarize_scientist_tag_workflow.py b/docs/dev/scale-audit-2026-09-29/summarize_scientist_tag_workflow.py
new file mode 100644
index 0000000..725308d
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/summarize_scientist_tag_workflow.py
@@ -0,0 +1,123 @@
+"""Summarize matched native workflow receipts without hiding raw evidence."""
+import argparse
+import json
+import math
+from pathlib import Path
+import statistics
+
+SIZES=(500,1000,5000,100000)
+COMMON={
+ 'cold_filter_A':'First multi-tag filter page, cold shared index (one sample)',
+ 'suggestions_cold':'First autocomplete request after index initialization (one sample)',
+ 'suggestions_warm':'Warm autocomplete (baseline may reuse its two-second TTL)',
+ 'A_B_A_sequential_server_bundle':'A → B → A, summary + page each',
+ 'return_A_page':'Return to A page (after summary)',
+ 'shared_add_durable_save':'Add shared tag to 10 epochs, durable save',
+ 'first_after_shared_add_server_bundle':'First summary + page after shared add',
+ 'suggestions_after_shared_add':'Autocomplete after shared add',
+ 'shared_remove_durable_save':'Remove shared tag from 10 epochs, durable save',
+ 'suggestions_after_shared_remove':'Autocomplete after shared removal',
+ 'added_tag_filter':'Affected marker predicate after add (index already maintained)',
+ 'removed_tag_filter':'Affected marker predicate after remove (index already maintained)',
+ 'dataset_add_durable_save':'Add protocol tag to 10 epochs, durable save',
+ 'first_after_dataset_add_server_bundle':'First summary + page after protocol add',
+}
+PAGE_FIRST={
+ 'measured_page_first_A_B_A':'A → B → A, page first with filtered cell counts',
+ 'page_first_return_A':'Return to A page, no preceding summary',
+ 'measured_shared_add_save_plus_first_page':'Shared add + first page',
+ 'measured_shared_remove_save_plus_first_page':'Shared remove + first page',
+ 'measured_shared_add_save_page_focus':'Shared add + first page + focused metadata',
+ 'measured_shared_remove_save_page_focus':'Shared remove + first page + focused metadata',
+ 'measured_dataset_add_save_plus_first_page':'Protocol add + first page',
+ 'measured_dataset_remove_save_plus_first_page':'Protocol remove + first page',
+ 'page_first_suggestions_after_shared_add':'Autocomplete after page-first shared add',
+ 'metadata_fields_cold':'First registered metadata-field definitions request',
+ 'metadata_fields_warm':'Warm registered metadata-field definitions request',
+}
+
+def main():
+ p=argparse.ArgumentParser(description=__doc__);p.add_argument('--directory',type=Path,required=True)
+ p.add_argument('--after-prefix',default='scientist-workflow-after-');p.add_argument('--output',type=Path,required=True)
+ p.add_argument('--baseline-only',action='store_true');a=p.parse_args();receipts={};paths={}
+ for stage,prefix in [('baseline','scientist-workflow-baseline-')]+([] if a.baseline_only else [('after',a.after_prefix)]):
+ receipts[stage]={};paths[stage]={}
+ for size in SIZES:
+ path=a.directory/(prefix+str(size)+'.json');value=json.loads(path.read_text())
+ if not value['passed']:raise ValueError('Failed receipt: '+str(path))
+ if value['epochs']!=size or value['samples']!=20 or value['tags_per_annotation']!=5 or value['protocols']!=2:
+ raise ValueError('Mismatched workflow contract: '+str(path))
+ if not value['source_unchanged'] or not value['owned_native_runtime_stopped']:
+ raise ValueError('Source stability or cleanup failed: '+str(path))
+ if value['source_inventory_before']!=value['source_inventory_after']:
+ raise ValueError('Changed source inventory: '+str(path))
+ if stage=='after' and (not value['harness_unchanged'] or not value['page_cells']):
+ raise ValueError('After harness or page payload changed: '+str(path))
+ receipts[stage][size]=value;paths[stage][size]=str(path)
+ stage_values=list(receipts[stage].values())
+ if any(value['harness_sha256']!=stage_values[0]['harness_sha256'] or value['source_inventory_before']!=stage_values[0]['source_inventory_before'] for value in stage_values):
+ raise ValueError('Matrix source or harness mismatch: '+stage)
+ result={'receipts':paths,'sizes':SIZES,'scope':'Native disposable MySQL/Flask/index/durable hooks; synthetic acquisition; no browser timing claim.',
+ 'baseline_harness_hashes':sorted({value['harness_sha256'] for value in receipts['baseline'].values()}),'comparisons':{},'new_page_first':{}}
+ result['receipt_validation']={stage:{'harness_sha256':next(iter(values.values()))['harness_sha256'],
+ 'python_source_files':len(next(iter(values.values()))['source_inventory_before']),
+ 'source_roots':sorted({value['source_root'] for value in values.values()}),
+ 'all_passed':True,'matching_source_and_harness_across_sizes':True,'all_owned_native_runtimes_stopped':True}
+ for stage,values in receipts.items()}
+ lines=['# Scientist multi-tag workflow measurements','',
+ 'Each size runs 20 sequential native cycles on a disposable project, with three author profiles, five tags per annotation, inherited cell tags and two independent protocol scopes. Cold setup requests have one sample and are labeled separately; their repeated p50/p95 value is not a tail-latency estimate. Tag predicates use the same all/any/not structure as the view filter. Exact full memberships, page order, author chips, case/Unicode distinctions and final durable state were checked. Times are milliseconds; cells show p50 / p95.','',
+ 'The original baseline runs use the retained frozen source tree. After runs retain those summary→page operation sequences and separately measure the new page-first flow. After pages also include exact filtered cell counts for the new UI, so they return additional data and are not byte-identical baseline requests. The first page after a save precedes suggestions/summary in the separate page-first flow, so it includes index maintenance.','']
+ if 'after' in receipts:
+ lines+=['## Current interaction sequence','',
+ '| Epochs | Shared add + first page | Shared add + page + focused metadata | A → B → A pages |',
+ '| ---: | ---: | ---: | ---: |']
+ for size in SIZES:
+ cells=[]
+ for key in ('measured_shared_add_save_plus_first_page','measured_shared_add_save_page_focus','measured_page_first_A_B_A'):
+ value=receipts['after'][size]['operations'][key]
+ cells.append(f"{value['p50_seconds']*1000:.1f} / {value['p95_seconds']*1000:.1f}")
+ lines.append(f'| {size:,} | '+ ' | '.join(cells)+' |')
+ lines+=['', 'These are measured sequential backend request times with exact correctness checks. They exclude browser rendering/network latency and any optimistic-revision preflight. Cold initialization and warm-autocomplete tradeoffs remain visible in the detailed comparisons below.','']
+ for key,label in COMMON.items():
+ lines+=['## '+label,'','| Epochs | Baseline p50 / p95 | After p50 / p95 | p50 speedup |','| ---: | ---: | ---: | ---: |']
+ result['comparisons'][key]={}
+ for size in SIZES:
+ old=receipts['baseline'][size]['operations'][key];new=receipts.get('after',{}).get(size,{}).get('operations',{}).get(key)
+ row={'baseline':{'p50_ms':old['p50_seconds']*1000,'p95_ms':old['p95_seconds']*1000}}
+ old_text=f"{row['baseline']['p50_ms']:.1f} / {row['baseline']['p95_ms']:.1f}"
+ if new:
+ row['after']={'p50_ms':new['p50_seconds']*1000,'p95_ms':new['p95_seconds']*1000};row['p50_speedup']=old['p50_seconds']/new['p50_seconds']
+ new_text=f"{row['after']['p50_ms']:.1f} / {row['after']['p95_ms']:.1f}";ratio=f"{row['p50_speedup']:.2f}×"
+ else:new_text=ratio='pending'
+ result['comparisons'][key][size]=row;lines.append(f'| {size:,} | {old_text} | {new_text} | {ratio} |')
+ lines.append('')
+ if 'after' in receipts:
+ lines+=['## Measured page-first flow','','These are direct after-version measurements, not an inferred browser speedup. The baseline page timings followed a summary request and therefore cannot serve as a cold, summary-free first-after-edit comparison.','']
+ for key,label in PAGE_FIRST.items():
+ values={size:receipts['after'][size]['operations'][key] for size in SIZES}
+ result['new_page_first'][key]={size:{'p50_ms':value['p50_seconds']*1000,'p95_ms':value['p95_seconds']*1000} for size,value in values.items()}
+ lines+=['### '+label,'','| Epochs | p50 / p95 |','| ---: | ---: |']
+ for size,value in result['new_page_first'][key].items():lines.append(f"| {size:,} | {value['p50_ms']:.1f} / {value['p95_ms']:.1f} |")
+ lines.append('')
+ lines+=['## Evidence and limits','',
+ '- Raw samples, nearest-rank quantiles, SQL session counters, full Python inventories, source origins and cleanup outcomes are retained in the timing receipts; CPU profiles are retained in the baseline receipts and the separately linked final diagnostic receipt.',
+ '- The four baseline receipts share one original harness and one Python source inventory; the four final receipts share one v2 harness and one final Python source inventory. Every receipt verifies the selected source stayed unchanged and its owned native MySQL runtime stopped.',
+ '- Warm autocomplete is an intentional tradeoff: the baseline could return its two-second cached vocabulary without checking current authority. The new path checks fresh authority on each request, so this case is slower while avoiding the stale window and full post-edit vocabulary rescans. The table reports both cases rather than implying every operation improved.',
+ '- SQL session deltas include the status collector’s small fixed overhead; they are comparative counters, not exact zero-query assertions.',
+ '- Sequential server bundles sum their constituent measured requests. They do not simulate browser scheduling or network/rendering latency. Frontend request-count/lifecycle evidence belongs to the separate mounted tests.',
+ '- Save measurements are durable mutation requests with known optimistic revisions. A selected-record editor may additionally fetch revisions before submitting; those preflight requests are not included in the mutation timing.',
+ '- The page-first A/B/A filters do not mention the edited workflow-selected tag. Their memberships can be reused after unrelated edits. Changing a tag literal used by the active filter still requires exact invalidation/rematching; the affected marker-filter samples occur after the primary page has already maintained the index.',
+ '- Baseline peak RSS includes the final full recovery-mirror verification and separate profiles. V2 additionally records the process high-water mark before that final recovery load; neither is a pure live-page memory measurement.',
+ '- The complete scripted cycle contains multiple filters, additions, removals, autocomplete queries and independent oracles; its wall time must not be described as one tag-save latency.','']
+ if 'after' in receipts:
+ lines+=['## Validation accompanying the final source','',
+ '- [Frozen final gate summary](frozen-scientist-tag-workflow-round2/summary.json): all nine implemented gates passed at 100,000 epochs; captured source manifest `a0ca699563e7f1fd17c2243cc736c0ea72814abfdf31214425e218638a247442`, with unchanged source, dependencies and native runtime.',
+ '- [Native authority result](frozen-scientist-tag-workflow-round2/native-authority.json) and [log](frozen-scientist-tag-workflow-round2/native-authority.log): actual disposable MySQL authority and scoped-token tests passed.',
+ '- [Mounted frontend regression log](frozen-scientist-tag-workflow-round2/gates/frontend_regressions.log) and [production build log](frozen-scientist-tag-workflow-round2/gates/frontend_build.log). The frozen [workflow request tests](frozen-scientist-tag-workflow-round2/snapshot/workspace-app/src/workflowResponsiveness.test.js) cover 500, 1,000, 5,000 and 100,000 epoch fixtures. Together with the [curation lifecycle tests](frozen-scientist-tag-workflow-round2/snapshot/workspace-app/src/inspectorCurationLifecycle.test.js), they cover stale navigation and storage failures.',
+ '- [Separate final 100,000-epoch CPU-profile receipt](scientist-workflow-after-round2-100000-profile.json) used two diagnostic cycles; its timings are excluded from the final 20-sample tables.',
+ '- These results cover the scripted synthetic acquisition and native persistence path. The gate summary retains separate unexecuted release trials including physical large H5 waveform round trips, real-browser revision races, full process-kill/power-loss recovery, clean-machine restores and lab NAS soak. Passing this workflow benchmark does not establish overall release readiness.','']
+ for stage,by_size in paths.items():
+ lines.append(stage.capitalize()+': '+', '.join(f'[{size:,} epochs]({Path(path).name})' for size,path in by_size.items())+'.')
+ a.output.write_text('\n'.join(lines)+'\n');a.output.with_suffix('.json').write_text(json.dumps(result,indent=2)+'\n')
+
+if __name__=='__main__':main()
diff --git a/docs/dev/scale-audit-2026-09-29/summarize_tag_actions.py b/docs/dev/scale-audit-2026-09-29/summarize_tag_actions.py
new file mode 100644
index 0000000..47e1b77
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/summarize_tag_actions.py
@@ -0,0 +1,84 @@
+"""Build a checked native single/batch/cell tagging report from raw receipts."""
+import argparse
+import json
+from pathlib import Path
+
+SIZES=(500,5000,100000)
+TARGETS={'epoch_1':'One epoch','epoch_5':'Five epochs','epoch_20':'Twenty epochs','cell_1':'One cell (100 child epochs)'}
+METRICS={'durable_save':'Durable save','first_stable_page':'First fresh page after save','focused_metadata':'Focused metadata after page',
+ 'save_plus_first_page':'Save + first fresh page','save_page_focus':'Save + first page + focused metadata',
+ 'affected_filter':'Changed-tag filter, after index maintenance'}
+
+def pair(operation):return f"{operation['p50_seconds']*1000:.1f} / {operation['p95_seconds']*1000:.1f}"
+def compact(operation):return {key:operation[key]*1000 for key in ('p50_seconds','p95_seconds')}
+
+def main():
+ parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--directory',type=Path,required=True)
+ parser.add_argument('--output',type=Path,required=True);parser.add_argument('--baseline-only',action='store_true')
+ args=parser.parse_args();stages=('baseline',) if args.baseline_only else ('baseline','after');receipts={};paths={}
+ for stage in stages:
+ receipts[stage]={};paths[stage]={}
+ for size in SIZES:
+ path=args.directory/f'tag-actions-{stage}-{size}.json';value=json.loads(path.read_text())
+ assert value['passed'] and value['samples']==20 and value['epochs']==size,path
+ assert value['source_unchanged'] and value['harness_unchanged'] and value['owned_native_runtime_stopped'],path
+ assert value['source_inventory_before']==value['source_inventory_after'],path
+ assert value['target_specification']=='epoch:1,epoch:5,epoch:20,cell:1',path
+ assert all(check['passed'] for check in value['checks']),path
+ receipts[stage][size]=value;paths[stage][size]=path.name
+ reference=receipts[stage][SIZES[0]]
+ assert all(value['harness_sha256']==reference['harness_sha256'] and value['source_inventory_before']==reference['source_inventory_before'] for value in receipts[stage].values()),stage
+ assert len({value['harness_sha256'] for values in receipts.values() for value in values.values()})==1
+ result={'scope':'Disposable native MySQL, real durable save hooks, Flask requests; synthetic acquisition; no browser wall-time claim.',
+ 'samples_per_add_and_remove':20,'harness_sha256':receipts['baseline'][500]['harness_sha256'],'receipts':paths,
+ 'source_roots':{stage:receipts[stage][500]['source_root'] for stage in stages},'metrics_ms':{},'profiles':{}}
+ lines=['# Single-epoch, small-batch and whole-cell tag actions','',
+ 'The benchmark runs 20 additions and 20 removals for each target size against disposable native MySQL at 500, 5,000 and 100,000 epochs. Every epoch and cell starts with five tags from each of three author profiles; two protocol curation scopes remain independent. A cell contains 100 child epochs. Small batches select the first 1, 5 or 20 contiguous epochs.','',
+ 'Each measured action is a durable save followed immediately by the existing multi-tag filtered page, then focused metadata. The active filter does not contain the edited marker. A separate changed-marker filter verifies exact membership after that first page has maintained the index. All displayed times are p50 / p95 milliseconds from raw 20-sample distributions, using nearest-rank p95. Instrumented CPU/SQL profiles are separate additional operations.','']
+ order=['save_page_focus','durable_save','save_plus_first_page','first_stable_page','focused_metadata','affected_filter']
+ for metric in order:
+ lines+=['## '+METRICS[metric],'']
+ for action in ('add','remove'):
+ lines+=['### '+action.capitalize(),'', '| Epochs | Target | '+' | '.join(stage.capitalize() for stage in stages)+' |',
+ '| ---: | :--- | '+' | '.join('---:' for _ in stages)+' |']
+ for size in SIZES:
+ for target,label in TARGETS.items():
+ key=f'{target}_{action}_{metric}';row={stage:receipts[stage][size]['operations'][key] for stage in stages}
+ lines.append(f'| {size:,} | {label} | '+' | '.join(pair(row[stage]) for stage in stages)+' |')
+ result['metrics_ms'].setdefault(key,{})[size]={stage:compact(value) for stage,value in row.items()}
+ lines.append('')
+ lines+=['## One-time first indexed read','','This first request follows seeding and the initial durable checkpoint. It includes cold derived shared-index work and is one sample, not a tail-latency estimate. Actual import scheduling and browser startup are outside this synthetic fixture.','',
+ '| Epochs | '+' | '.join(stage.capitalize()+' ms' for stage in stages)+' |','| ---: | '+' | '.join('---:' for _ in stages)+' |']
+ for size in SIZES:lines.append(f'| {size:,} | '+' | '.join(f"{receipts[stage][size]['operations']['cold_initial_shared_index']['p50_seconds']*1000:.1f}" for stage in stages)+' |')
+ lines+=['','## Separate save profiles','',
+ 'These are individually instrumented add operations, not the uninstrumented timing distributions above. Native SQL dispatch time excludes later cursor consumption; the full CPU profile retains fetch costs. Python fsync instrumentation does not intercept SQLite internal fsync. SQLite commit time is shown separately. Nested cumulative function times must not be added together.','',
+ '| Version | Epochs | Target | SQL calls | SQL dispatch ms | Snapshot save ms | Annotation update ms | Recovery-store write ms | SQLite commit ms | Python fsync ms |',
+ '| :--- | ---: | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |']
+ for stage in stages:
+ result['profiles'][stage]={}
+ for size in SIZES:
+ result['profiles'][stage][size]={}
+ for target,label in TARGETS.items():
+ profile=receipts[stage][size].get('cpu_profiles',{}).get(target+'_add')
+ if profile is None:continue
+ rows=profile['top_functions']+profile['sql_and_durability_functions']
+ unique={(row['file'],row['line'],row['function']):row for row in rows}
+ def timing(file,function):return sum(row['cumulative_seconds'] for row in unique.values() if row['file'].endswith(file) and row['function']==function)*1000
+ data={'sql_calls':len(profile['native_sql_calls']),'sql_dispatch_ms':sum(row['seconds'] for row in profile['native_sql_calls'])*1000,
+ 'snapshot_save_ms':timing('workspace_state_snapshot.py','save'),'annotation_update_ms':timing('workspace_annotations.py','update'),
+ 'recovery_store_write_ms':timing('workspace_recovery_store.py','write'),
+ 'sqlite_commit_ms':sum(row['cumulative_seconds'] for row in unique.values() if 'commit' in row['function'] and 'sqlite3.Connection' in row['function'])*1000,
+ 'python_fsync_ms':sum(profile['python_fsync_calls_seconds'])*1000}
+ result['profiles'][stage][size][target]=data
+ lines.append(f'| {stage} | {size:,} | {label} | {data["sql_calls"]} | '+' | '.join(f'{data[key]:.3f}' for key in ('sql_dispatch_ms','snapshot_save_ms','annotation_update_ms','recovery_store_write_ms','sqlite_commit_ms','python_fsync_ms'))+' |')
+ lines+=['','## Correctness and measurement limits','',
+ '- Every add/remove checks exact effective-filter UUID membership, first/last page order, filtered cell counts, full direct/inherited author chips, native target tags and optimistic revisions. Final recovery-mirror content and both untouched protocol curation scopes are independently checked.',
+ '- Whole-cell tags are inherited from one cell annotation row. All 100 child epochs are checked through effective membership and page chips; the direct epoch-only predicate remains empty. Native SQL confirms all 300 child annotation rows, revisions, authors and timestamps stay unchanged and no direct epoch annotation rows are added.',
+ '- Save timings begin with known optimistic revisions. A selected-record editor may fetch revisions before saving; that preflight, browser scheduling, transport and rendering are excluded. Sums of measured sequential requests are not browser interaction times.',
+ '- The changed-marker filter occurs after the stable page. Its samples isolate predicate rematching after index maintenance; they do not establish save-to-first-page latency when the active predicate itself contains the changed literal.',
+ '- Profiles add instrumentation overhead and include overlapping cumulative times. Their SQL counts identify repeated work; Python fsync alone is not a full operating-system I/O trace.',
+ '- All receipts validate stable product-source inventories, an unchanged identical benchmark harness and cleanup of their owned native MySQL runtime. This benchmark is not an overall release-readiness claim.','']
+ for stage in stages:lines.append(stage.capitalize()+': '+', '.join(f'[{size:,} epochs]({paths[stage][size]})' for size in SIZES)+'.')
+ args.output.write_text('\n'.join(lines)+'\n');args.output.with_suffix('.json').write_text(json.dumps(result,indent=2)+'\n')
+
+if __name__=='__main__':main()
diff --git a/docs/dev/scale-audit-2026-09-29/tag-sequence-after-100000.json b/docs/dev/scale-audit-2026-09-29/tag-sequence-after-100000.json
new file mode 100644
index 0000000..515f20d
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/tag-sequence-after-100000.json
@@ -0,0 +1,3265 @@
+{
+ "scope": "Native tag-ten, tag-another-ten, filter-twenty and hierarchical cell/epoch workflow.\n\nEvery project/database is disposable. Acquisition metadata is synthetic; actual\nMySQL, triggers, Flask routes, derived indexes and each version's save hooks\nare used. Native SQL commits are checked immediately; any background recovery\nmirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude\noracle work, and do not model browser concurrency/rendering. Sequential bundle\ntimes are sums of their separately measured requests. Profiles are separate.\n",
+ "epochs": 100000,
+ "profiles": 3,
+ "tags_per_annotation": 5,
+ "protocols": 2,
+ "samples": 5,
+ "source_root": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI",
+ "source_inventory_before": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "db14aaa6c1cf1920a52f340028407e0d66222c439feecad4af9c21faabf095be",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7afc6c297770800fb90e148ffeb4ad3d5c24c8e84f5a788094f3246f59f97782",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "49769c7f0b356e085d7e076abe17350e546b59444936d9caecea8236d60699ad",
+ "python/workspace_annotations.py": "a2eb2b9282a226006dab89691b099b73ff9fd12e15985bd548c9a4ef4ab68ee2",
+ "python/workspace_api.py": "b1b429181053bcc3981793ca70b191b00dfd5c0631ed65b94c12aa5bd34ef992",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "29c2d2cadce647b67d085febd30532d5cac726056a76ebf42c70ea40abdc112a",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "0443e20a6fc78387b6a3925b7a588045181ad4d1033fed439ba23f8c19a75706",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "fa7c39178b4e7876bdf48d5d076b4afa71906aebfcd80c552566a5a1f1fe2c2e",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "4b83f86efbfea8c3fe1194cf59c3e810970059e3ec1bf579adc56472c28c8d09",
+ "python/workspace_installation.py": "8f0787584d7650b58c422f6980592f06411c4071a1c0405cf36eb7bebd05761b",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "b3d0a1f2c3505e14f249e674574dec5e81594b2049ca48e760dbf5d6d45fa894",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "35a10441d0ba59e43c59ad564879b2b73eccc29cd19a2558e3eaf8db1a9f5782",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "f5b26e0f77797e9ffae3a26188c1ea0d75f0fe1698ff8d5085c7c892eec7693c",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "fixture_module": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py",
+ "harness_sha256": "290deaf5e34be5a5bc1554ae072d633f005758265b5e6e32f48a4f1b4ff4450e",
+ "started_at": "2026-09-30T13:56:23.758168+00:00",
+ "release_ready": false,
+ "operations": {
+ "preparation_epoch_filter": {
+ "samples_seconds": [
+ 0.18059012497542426
+ ],
+ "p50_seconds": 0.18059012497542426,
+ "p95_seconds": 0.18059012497542426,
+ "p99_seconds": 0.18059012497542426,
+ "maximum_seconds": 0.18059012497542426,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 109819,
+ "Questions": 60
+ }
+ ]
+ },
+ "preparation_cell_filter": {
+ "samples_seconds": [
+ 0.09584383299807087
+ ],
+ "p50_seconds": 0.09584383299807087,
+ "p95_seconds": 0.09584383299807087,
+ "p99_seconds": 0.09584383299807087,
+ "maximum_seconds": 0.09584383299807087,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 103280,
+ "Questions": 53
+ }
+ ]
+ },
+ "preparation_hierarchical_filter": {
+ "samples_seconds": [
+ 0.10649495804682374
+ ],
+ "p50_seconds": 0.10649495804682374,
+ "p95_seconds": 0.10649495804682374,
+ "p99_seconds": 0.10649495804682374,
+ "maximum_seconds": 0.10649495804682374,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 103280,
+ "Questions": 53
+ }
+ ]
+ },
+ "first10_revision_preflight": {
+ "samples_seconds": [
+ 0.007762708992231637,
+ 0.0066079170210286975,
+ 0.006354750017635524,
+ 0.006577583029866219,
+ 0.006067375012207776
+ ],
+ "p50_seconds": 0.006577583029866219,
+ "p95_seconds": 0.007762708992231637,
+ "p99_seconds": 0.007762708992231637,
+ "maximum_seconds": 0.007762708992231637,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15364,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15365,
+ "Questions": 4
+ }
+ ]
+ },
+ "first10_save": {
+ "samples_seconds": [
+ 0.039669749967288226,
+ 0.017622290994040668,
+ 0.016710209019947797,
+ 0.01661975000752136,
+ 0.018321750045288354
+ ],
+ "p50_seconds": 0.017622290994040668,
+ "p95_seconds": 0.039669749967288226,
+ "p99_seconds": 0.039669749967288226,
+ "maximum_seconds": 0.039669749967288226,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 18600,
+ "Questions": 36
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12424,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12455,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12455,
+ "Questions": 29
+ }
+ ]
+ },
+ "second10_revision_preflight": {
+ "samples_seconds": [
+ 0.006107332999818027,
+ 0.0058263749815523624,
+ 0.006256791006308049,
+ 0.00606333400355652,
+ 0.005791375006083399
+ ],
+ "p50_seconds": 0.00606333400355652,
+ "p95_seconds": 0.006256791006308049,
+ "p99_seconds": 0.006256791006308049,
+ "maximum_seconds": 0.006256791006308049,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15382,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15383,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15383,
+ "Questions": 4
+ }
+ ]
+ },
+ "second10_save": {
+ "samples_seconds": [
+ 0.018488208996132016,
+ 0.014466083026491106,
+ 0.014097124978434294,
+ 0.01865829201415181,
+ 0.016184374981094152
+ ],
+ "p50_seconds": 0.016184374981094152,
+ "p95_seconds": 0.01865829201415181,
+ "p99_seconds": 0.01865829201415181,
+ "maximum_seconds": 0.01865829201415181,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12436,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12467,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12467,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter20": {
+ "samples_seconds": [
+ 0.12463008402846754,
+ 0.12353041704045609,
+ 0.11796570901060477,
+ 0.1180352499941364,
+ 0.11921920796157792
+ ],
+ "p50_seconds": 0.11921920796157792,
+ "p95_seconds": 0.12463008402846754,
+ "p99_seconds": 0.12463008402846754,
+ "maximum_seconds": 0.12463008402846754,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 132628,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 132628,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 132568,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 132629,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 132629,
+ "Questions": 58
+ }
+ ]
+ },
+ "cell_save": {
+ "samples_seconds": [
+ 0.00858916697325185,
+ 0.0077745000016875565,
+ 0.007230332994367927,
+ 0.007809958013240248,
+ 0.00800329097546637
+ ],
+ "p50_seconds": 0.007809958013240248,
+ "p95_seconds": 0.00858916697325185,
+ "p99_seconds": 0.00858916697325185,
+ "maximum_seconds": 0.00858916697325185,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4166,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4170,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4170,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_cell100": {
+ "samples_seconds": [
+ 0.12916150002274662,
+ 0.1315588749712333,
+ 0.1377441250369884,
+ 0.12866404198575765,
+ 0.12982887495309114
+ ],
+ "p50_seconds": 0.12982887495309114,
+ "p95_seconds": 0.1377441250369884,
+ "p99_seconds": 0.1377441250369884,
+ "maximum_seconds": 0.1377441250369884,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 160880,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 160880,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 160877,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 160881,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 160881,
+ "Questions": 58
+ }
+ ]
+ },
+ "filter_cell_and_epoch10": {
+ "samples_seconds": [
+ 0.10371437499998137,
+ 0.10619708400918171,
+ 0.10680883401073515,
+ 0.10880116699263453,
+ 0.10374879103619605
+ ],
+ "p50_seconds": 0.10619708400918171,
+ "p95_seconds": 0.10880116699263453,
+ "p99_seconds": 0.10880116699263453,
+ "maximum_seconds": 0.10880116699263453,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 115278,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115278,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115278,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115279,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115279,
+ "Questions": 56
+ }
+ ]
+ },
+ "remove_first10_revision_preflight": {
+ "samples_seconds": [
+ 0.005952542007435113,
+ 0.0060099170077592134,
+ 0.005995250015985221,
+ 0.00734366598771885,
+ 0.00649937498383224
+ ],
+ "p50_seconds": 0.0060099170077592134,
+ "p95_seconds": 0.00734366598771885,
+ "p99_seconds": 0.00734366598771885,
+ "maximum_seconds": 0.00734366598771885,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_first10_save": {
+ "samples_seconds": [
+ 0.015223000023979694,
+ 0.015721040952485055,
+ 0.016107500006910414,
+ 0.017456292000133544,
+ 0.015779166016727686
+ ],
+ "p50_seconds": 0.015779166016727686,
+ "p95_seconds": 0.017456292000133544,
+ "p99_seconds": 0.017456292000133544,
+ "maximum_seconds": 0.017456292000133544,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12424,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12455,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12455,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_hierarchy_after_epoch_remove": {
+ "samples_seconds": [
+ 0.10807566699804738,
+ 0.10471912502544001,
+ 0.10708120901836082,
+ 0.10962279204977676,
+ 0.11332000000402331
+ ],
+ "p50_seconds": 0.10807566699804738,
+ "p95_seconds": 0.11332000000402331,
+ "p99_seconds": 0.11332000000402331,
+ "maximum_seconds": 0.11332000000402331,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 107609,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107609,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107579,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107610,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107610,
+ "Questions": 55
+ }
+ ]
+ },
+ "filter_remaining10": {
+ "samples_seconds": [
+ 0.10187620803480968,
+ 0.10207675001583993,
+ 0.10108137503266335,
+ 0.10448374995030463,
+ 0.10275412502232939
+ ],
+ "p50_seconds": 0.10207675001583993,
+ "p95_seconds": 0.10448374995030463,
+ "p99_seconds": 0.10448374995030463,
+ "maximum_seconds": 0.10448374995030463,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 115270,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115270,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115270,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115271,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115271,
+ "Questions": 56
+ }
+ ]
+ },
+ "remove_second10_revision_preflight": {
+ "samples_seconds": [
+ 0.006092499999795109,
+ 0.006038458028342575,
+ 0.006276375032030046,
+ 0.005982792004942894,
+ 0.006343833985738456
+ ],
+ "p50_seconds": 0.006092499999795109,
+ "p95_seconds": 0.006343833985738456,
+ "p99_seconds": 0.006343833985738456,
+ "maximum_seconds": 0.006343833985738456,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15803,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15803,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_second10_save": {
+ "samples_seconds": [
+ 0.015518374973908067,
+ 0.01628666598116979,
+ 0.015142541960813105,
+ 0.014857334026601166,
+ 0.01613141701091081
+ ],
+ "p50_seconds": 0.015518374973908067,
+ "p95_seconds": 0.01628666598116979,
+ "p99_seconds": 0.01628666598116979,
+ "maximum_seconds": 0.01628666598116979,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12436,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12467,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12467,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_after_all_epoch_removals": {
+ "samples_seconds": [
+ 0.1011254169861786,
+ 0.10424191696802154,
+ 0.10198108403710648,
+ 0.10569516703253612,
+ 0.10762633400736377
+ ],
+ "p50_seconds": 0.10424191696802154,
+ "p95_seconds": 0.10762633400736377,
+ "p99_seconds": 0.10762633400736377,
+ "maximum_seconds": 0.10762633400736377,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 107612,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107612,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107582,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107613,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107613,
+ "Questions": 55
+ }
+ ]
+ },
+ "remove_cell_save": {
+ "samples_seconds": [
+ 0.009642708988394588,
+ 0.007798207981977612,
+ 0.00767266598995775,
+ 0.007379374990705401,
+ 0.007634042005520314
+ ],
+ "p50_seconds": 0.00767266598995775,
+ "p95_seconds": 0.009642708988394588,
+ "p99_seconds": 0.009642708988394588,
+ "maximum_seconds": 0.009642708988394588,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4166,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4170,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4170,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_after_cell_removal": {
+ "samples_seconds": [
+ 0.10253337502945215,
+ 0.10327741602668539,
+ 0.10210616700351238,
+ 0.10115041601238772,
+ 0.1018838330055587
+ ],
+ "p50_seconds": 0.10210616700351238,
+ "p95_seconds": 0.10327741602668539,
+ "p99_seconds": 0.10327741602668539,
+ "maximum_seconds": 0.10327741602668539,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 105000,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 105000,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 104997,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 105001,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 105001,
+ "Questions": 55
+ }
+ ]
+ },
+ "tag10_then10_filter20_http_total": {
+ "samples_seconds": [
+ 0.19665808498393744,
+ 0.16805308306356892,
+ 0.16138458403293043,
+ 0.1659542090492323,
+ 0.1655840830062516
+ ],
+ "p50_seconds": 0.1659542090492323,
+ "p95_seconds": 0.19665808498393744,
+ "p99_seconds": 0.19665808498393744,
+ "maximum_seconds": 0.19665808498393744,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "samples_seconds": [
+ 0.13775066699599847,
+ 0.13933337497292086,
+ 0.14497445803135633,
+ 0.1364739999989979,
+ 0.13783216592855752
+ ],
+ "p50_seconds": 0.13783216592855752,
+ "p95_seconds": 0.14497445803135633,
+ "p99_seconds": 0.14497445803135633,
+ "maximum_seconds": 0.14497445803135633,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "first10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.04743245895951986,
+ 0.024230208015069366,
+ 0.02306495903758332,
+ 0.02319733303738758,
+ 0.02438912505749613
+ ],
+ "p50_seconds": 0.024230208015069366,
+ "p95_seconds": 0.04743245895951986,
+ "p99_seconds": 0.04743245895951986,
+ "maximum_seconds": 0.04743245895951986,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "second10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.024595541995950043,
+ 0.020292458008043468,
+ 0.020353915984742343,
+ 0.02472162601770833,
+ 0.02197574998717755
+ ],
+ "p50_seconds": 0.02197574998717755,
+ "p95_seconds": 0.02472162601770833,
+ "p99_seconds": 0.02472162601770833,
+ "maximum_seconds": 0.02472162601770833,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "same_server_reopened_filter20": {
+ "samples_seconds": [
+ 0.10664900002302602
+ ],
+ "p50_seconds": 0.10664900002302602,
+ "p95_seconds": 0.10664900002302602,
+ "p99_seconds": 0.10664900002302602,
+ "maximum_seconds": 0.10664900002302602,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 124941,
+ "Questions": 56
+ }
+ ]
+ }
+ },
+ "checks": [
+ {
+ "name": "preparation_exact_empty_memberships",
+ "passed": true
+ },
+ {
+ "name": "same_server_checkpoint_restores_exact_twenty_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "page_cells": true,
+ "target_specification": "tag10_then10_filter20_and_cell_epoch_hierarchy",
+ "baseline_harness_sha256": "272fe050c8762ed4ea45bd013e236e8723d8fe46f5e7e293c58e2edf1fd52ddc",
+ "mysql_version": "8.4.2",
+ "seed_seconds": 47.2170950419968,
+ "seed_checkpoint_seconds": 13.237889167037793,
+ "selected_epoch_uuids": {
+ "first10": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5"
+ ],
+ "second10": [
+ "ad573668-6512-6e55-649e-cb23ae1d4888",
+ "16dc368a-89b4-28b2-4854-84313ba67a39",
+ "37834f2f-2576-2f23-e1f7-4a531cbe445d",
+ "454f63ac-30c8-3229-97ef-025edff6abd2",
+ "5ef6fdf3-2513-aa7c-d11f-72beccf132b9",
+ "1253e937-3e78-1b75-0026-6caa55150e08",
+ "482d9673-cfee-5de3-91f9-7fde4d1c84f9",
+ "3346f2bb-f6c3-4bd2-dbe2-8bd1bb657d0e",
+ "9537f32e-c759-9e1a-e953-af6c9f929fe7",
+ "0fd42b3f-73c4-48b3-4940-b339f87d07ad"
+ ],
+ "cell": "3bb78535-cc95-55ff-19fe-3556aaa41c78",
+ "cell_children": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5",
+ "4a44dc15-3642-04a8-0fe8-0e9039455cc1",
+ "4fc82b26-aecb-47d2-868c-4efbe3581732",
+ "6b51d431-df5d-7f14-1cbe-ceccf79edf3d",
+ "3fdba35f-04dc-8c46-2986-c992bcf87554",
+ "8527a891-e224-1369-50ff-32ca212b45bc",
+ "e629fa65-98d7-3276-8f7c-726b4b621285",
+ "b17ef6d1-9c7a-5b1e-e83b-907c595526dc",
+ "4523540f-1504-cd17-100c-4835e85b7eef",
+ "4ec9599f-c203-d176-a301-536c2e091a19",
+ "9400f1b2-1cb5-27d7-fa3d-3eabba93557a",
+ "f5ca38f7-48a1-d6ea-f726-b8a42fb575c3",
+ "6f4b6612-125f-b3a0-daec-d2799dfd6c9c",
+ "785f3ec7-eb32-f30b-90cd-0fcf3657d388",
+ "535fa30d-7e25-dd8a-49f1-536779734ec8",
+ "c2356069-e9d1-e79c-a924-378153cfbbfb",
+ "b7a56873-cd77-1f2c-446d-369b649430b6",
+ "5f9c4ab0-8cac-7457-e911-1a30e4664920",
+ "670671cd-9740-4156-226e-507973f2ab83",
+ "59e19706-d51d-39f6-6711-c2653cd7eb12",
+ "35135aaa-6cc2-3891-b40c-b3f378c53a17",
+ "624b60c5-8c9d-8bfb-6ff1-886c2fd605d2",
+ "eb1e33e8-a81b-697b-7585-5af6bfcdbcbf",
+ "e29c9c18-0c62-79b0-b02a-bd6a1801c7c0",
+ "c6f3ac57-944a-5314-90cd-39902d0f7777",
+ "86e50149-6586-6131-2a9e-0b35558d84f6",
+ "9f14025a-f006-5b30-e47e-23ebb3b491d3",
+ "76a50887-d8f1-c2e9-3017-55428990ad81",
+ "7a61b537-01be-fdae-0eee-ffaecc73f14e",
+ "aea92132-c4cb-eb26-3e6a-c2bf6c183b5d",
+ "0b918943-df09-62bc-7a18-24c0555a3893",
+ "d59eced1-ded0-7f84-c145-592f65bdf854",
+ "3d914f93-48c9-cc0f-f8a7-9716700b9fcd",
+ "73475cb4-0a56-8e8d-a8a0-45ced110137e",
+ "44cb730c-4204-80a0-477b-505ae68af508",
+ "71ee45a3-c0db-9a98-65f7-313dd3372cf6",
+ "811786ad-1ae7-4adf-dd20-dd0372abaaeb",
+ "25fc0e70-96fc-6537-1820-2dc30b0c580b",
+ "31489056-e091-6d59-fe3a-dd79e63f095a",
+ "98010bd9-270f-9b10-0b62-14a21754fd33",
+ "0e17daca-5f3e-175f-448b-acace3bc0da4",
+ "1a656259-0ef1-9d10-45d0-6c4055742d38",
+ "031b4af5-197e-c30a-926f-48cf40e11a7d",
+ "41cfc0d1-f2d1-27b0-4555-b7246d84019b",
+ "2858dcd1-057d-3eae-7f7d-5f782167e24b",
+ "2fca346d-b656-1871-02ce-806ac732e06a",
+ "02d20bbd-7e39-4ad5-999a-4cebabac9619",
+ "7688b6ef-5255-5962-d008-fff894223582",
+ "c837649c-ce43-f272-9138-e72cc3152070",
+ "6208ef0f-7750-c111-548c-f90b6ea1d0d0",
+ "3e1e967e-9b79-3e90-8f8e-ae83c74dba9b",
+ "39fa9ec1-90ee-e7b6-f4df-f1100d6343e1",
+ "d029fa3a-95e1-74a1-9934-857f535eb942",
+ "81b8a03f-97e8-787c-53fe-1a86bda042b6",
+ "da4ea2a5-506f-2693-eae1-90d9360a1f31",
+ "a68b412c-4282-555f-1554-6cf6e1fc4289",
+ "108c995b-953c-8a35-5611-03e2014cf828",
+ "3ada92f2-8b4c-eda3-8562-ebf047c6ff05",
+ "49d180ec-f561-3281-9571-bf39d9b7b342",
+ "a21855da-08cb-102d-1d21-7c53dc5824a3",
+ "c75cb66a-e28d-8ebc-6ede-d002c28a8ba0",
+ "ff5a1ae0-12af-a5d4-c889-c50ad427aaf5",
+ "7f2253d7-e228-b22a-08bd-a1f09c516f6f",
+ "87226162-0421-7edd-b39e-7df969e0698a",
+ "96061e92-f58e-4bdc-dee7-3df36183fe3a",
+ "eb624dbe-56eb-6620-ae62-080c10a273ca",
+ "f369cb89-fc62-7e66-8987-007d121ed1ea",
+ "f74efabe-f12e-a619-e30b-79bddef89cff",
+ "a88a7902-cb4e-f697-ba0b-6759c50e8c10",
+ "349c4120-1b62-db85-1192-665c504b350f",
+ "98a3ab7c-340e-8a03-3e7b-37b6ef942875",
+ "48449a14-a4ff-7d79-bb7a-1b6f3d488eba",
+ "5316ca1c-5ddc-a8e6-cecc-fce58f3b8540",
+ "a46e3763-2fa6-ca51-a13f-e39a567b3c23",
+ "bbb965ab-0c80-d653-8cf2-184babad2a56",
+ "44c8031c-b036-a735-0d8b-9b8603af662a",
+ "b4944c6f-f08d-c6f4-3da2-e9c824669b7d",
+ "434c9b5a-e514-646b-bd91-b50032ca579e",
+ "bdd2d3af-3a5a-1213-497d-4f1f7bfcda89",
+ "8b940be7-fb78-aaa6-b656-7dd7a3987996",
+ "cd70bea0-23f7-52a0-564a-bb6ed08d42c1",
+ "69f59c27-3b6e-669a-c32a-6dd5e1b2cb63",
+ "1da51b8d-8ff9-8f6a-48f8-0ae79fe3ca6c",
+ "82416496-09f8-8ccd-2a0a-5b233a07a538",
+ "6e400187-1c0c-f27c-7634-ef1dc478408f",
+ "e3d6c4d4-599e-0088-2384-ca981ee287ed",
+ "ad48ff99-415b-2f00-7dc3-5b7eb553fd1e",
+ "7b1a278f-5abe-8e9d-a907-fc9c29dfd432",
+ "d6d824ab-ba4a-fde8-1129-c71dea75b810",
+ "29db0c67-82db-d500-0559-ef4d9e953e30",
+ "8c1f1046-219d-dd21-6a02-3f792356ddf1"
+ ]
+ },
+ "backup_receipts": [
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 1,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 2,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 3,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 4,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 5,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 6,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 7,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 8,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 9,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 10,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 11,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 12,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 13,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 14,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 15,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 16,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 17,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 18,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 19,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 20,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 21,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 22,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 23,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 24,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 25,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 26,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 27,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 28,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 29,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 15,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:36.900006+00:00",
+ "pending": true,
+ "requested_sequence": 30,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:42.385285+00:00",
+ "pending": true,
+ "requested_sequence": 31,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 30,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:42.385285+00:00",
+ "pending": true,
+ "requested_sequence": 32,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_first10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:43.295000+00:00",
+ "pending": true,
+ "requested_sequence": 33,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_second10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T13:58:43.295000+00:00",
+ "pending": true,
+ "requested_sequence": 34,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ }
+ ],
+ "project_annotation_preparation": {
+ "status": "ready",
+ "phases": {
+ "indexing_shared_annotations": {
+ "seconds": 10.521187
+ },
+ "preparing_tag_filters": {
+ "seconds": 0.56534
+ },
+ "preparing_tag_summary": {
+ "seconds": 2.921402
+ },
+ "preparing_tag_suggestions": {
+ "seconds": 8.017821
+ },
+ "indexing_dataset_tags": {
+ "seconds": 5.766529
+ },
+ "preparing_protocol_reads": {
+ "seconds": 0.063411
+ }
+ },
+ "project_uuid": "4d228f00-c675-479b-ba5d-62b35548d894",
+ "epochs": 100000,
+ "cells": 1000,
+ "metadata_generation": "tag-workflow-fixture-v1",
+ "phase": "complete",
+ "shared_index": {
+ "full_builds": 2,
+ "delta_updates": 0,
+ "rows_loaded": 303000,
+ "rows_indexed": 303000
+ },
+ "dataset_index": {
+ "full_builds": 2,
+ "delta_updates": 0,
+ "rows_loaded": 200000,
+ "rows_indexed": 200000
+ },
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "seconds": 27.950389,
+ "checkpoint": {
+ "saved": true,
+ "rows_indexed": 303000,
+ "sha256": "cc085ef0588b788e43bd88453badcab780e5617d1959ef7d80bf9cbf3d71c554"
+ }
+ },
+ "preparation_index_seconds": 29.26563633303158,
+ "bundle_definitions": {
+ "tag10_then10_filter20_http_total": [
+ "first10_revision_preflight",
+ "first10_save",
+ "second10_revision_preflight",
+ "second10_save",
+ "filter20"
+ ],
+ "cell_tag_then_cell_filter_http_total": [
+ "cell_save",
+ "filter_cell100"
+ ],
+ "first10_selected_action_http_total": [
+ "first10_revision_preflight",
+ "first10_save"
+ ],
+ "second10_selected_action_http_total": [
+ "second10_revision_preflight",
+ "second10_save"
+ ]
+ },
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T13:58:45.653602+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "filter20_cpu_profile": {
+ "top_functions": [
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py",
+ "line": 278,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.6660000000000005e-06,
+ "cumulative_seconds": 0.282614083
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py",
+ "line": 142,
+ "function": "request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.7920000000000003e-06,
+ "cumulative_seconds": 0.282586167
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1163,
+ "function": "get",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.583000000000001e-06,
+ "cumulative_seconds": 0.281764
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/testing.py",
+ "line": 204,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.2749000000000001e-05,
+ "cumulative_seconds": 0.28175841700000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1062,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.877e-06,
+ "cumulative_seconds": 0.281471917
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 984,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0665e-05,
+ "cumulative_seconds": 0.28145820800000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1230,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.584e-06,
+ "cumulative_seconds": 0.281259292
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1529,
+ "function": "__call__",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.542e-06,
+ "cumulative_seconds": 0.28123375
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1479,
+ "function": "wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.075e-05,
+ "cumulative_seconds": 0.281227208
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 904,
+ "function": "full_dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.542000000000001e-06,
+ "cumulative_seconds": 0.2809805
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 879,
+ "function": "dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.208400000000001e-05,
+ "cumulative_seconds": 0.28077287500000003
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 716,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.778900000000001e-05,
+ "cumulative_seconds": 0.28067695800000003
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 29,
+ "calls": 40,
+ "self_seconds": 3.1875e-05,
+ "cumulative_seconds": 0.147531625
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 249,
+ "function": "native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.4000000000000001e-05,
+ "cumulative_seconds": 0.14745691700000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 268,
+ "function": "_native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.6958e-05,
+ "cumulative_seconds": 0.130499791
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 311,
+ "function": "native_context",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.7876e-05,
+ "cumulative_seconds": 0.130427959
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 207,
+ "function": "_metadata_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.006905002,
+ "cumulative_seconds": 0.126975417
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 22,
+ "calls": 4058,
+ "self_seconds": 0.001279868,
+ "cumulative_seconds": 0.120352708
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 212,
+ "function": "",
+ "primitive_calls": 18,
+ "calls": 18,
+ "self_seconds": 2.1623000000000003e-05,
+ "cumulative_seconds": 0.120062709
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 55,
+ "function": "_epoch_query_equal",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.09017179600000001,
+ "cumulative_seconds": 0.11442895800000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 748,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.000880874,
+ "cumulative_seconds": 0.101276875
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 778,
+ "function": "_epoch_page_identities",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.007281405,
+ "cumulative_seconds": 0.10036154200000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 132,
+ "function": "__enter__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 2.666e-06,
+ "cumulative_seconds": 0.07400258400000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 261,
+ "function": "filter_epoch_ids",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.04e-06,
+ "cumulative_seconds": 0.07370808300000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 141,
+ "function": "__exit__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 7.5840000000000006e-06,
+ "cumulative_seconds": 0.07346029200000001
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 38,
+ "function": "guarded",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.3126e-05,
+ "cumulative_seconds": 0.045590418
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 241,
+ "function": "_membership_index",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.4708e-05,
+ "cumulative_seconds": 0.041489709
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 511,
+ "function": "matching",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.003632624,
+ "cumulative_seconds": 0.032158833000000005
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 597,
+ "calls": 597,
+ "self_seconds": 0.004275627000000001,
+ "cumulative_seconds": 0.029153744000000002
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 216,
+ "function": "_scope",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.004648167,
+ "cumulative_seconds": 0.028180708000000002
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py",
+ "line": 273,
+ "function": "traced_query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.000167627,
+ "cumulative_seconds": 0.024282670000000003
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 245,
+ "function": "",
+ "primitive_calls": 100001,
+ "calls": 100001,
+ "self_seconds": 0.024083332000000002,
+ "cumulative_seconds": 0.024083332000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 412,
+ "function": "query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.000123866,
+ "cumulative_seconds": 0.023745250000000002
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 217,
+ "function": "",
+ "primitive_calls": 100001,
+ "calls": 100001,
+ "self_seconds": 0.023532541,
+ "cumulative_seconds": 0.023532541
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 402,
+ "function": "_execute_query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.00012742500000000002,
+ "cumulative_seconds": 0.023432875000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 150,
+ "function": "execute",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 6.496400000000001e-05,
+ "cumulative_seconds": 0.023018335
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 332,
+ "function": "_query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 8.0695e-05,
+ "cumulative_seconds": 0.022529162000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 575,
+ "function": "query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 8.708e-05,
+ "cumulative_seconds": 0.022006708
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 282,
+ "function": "for_epochs",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 9.7942e-05,
+ "cumulative_seconds": 0.021031625
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 851,
+ "function": "_read_query_result",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 5.8592000000000004e-05,
+ "cumulative_seconds": 0.02083888
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1288,
+ "function": "read",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 8.074300000000001e-05,
+ "cumulative_seconds": 0.020757581
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 101,
+ "function": "_rows",
+ "primitive_calls": 47,
+ "calls": 47,
+ "self_seconds": 5.8371e-05,
+ "cumulative_seconds": 0.01820867
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 197,
+ "function": "response_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.2791000000000003e-05,
+ "cumulative_seconds": 0.016897041
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 233,
+ "function": "_attest_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 5.3959e-05,
+ "cumulative_seconds": 0.016869708
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 435319,
+ "calls": 435319,
+ "self_seconds": 0.016598086,
+ "cumulative_seconds": 0.016598086
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 763,
+ "function": "_read_packet",
+ "primitive_calls": 789,
+ "calls": 789,
+ "self_seconds": 0.0012617280000000002,
+ "cumulative_seconds": 0.015930683
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 128,
+ "function": "deepcopy",
+ "primitive_calls": 80,
+ "calls": 17204,
+ "self_seconds": 0.009242237,
+ "cumulative_seconds": 0.01577659
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 201,
+ "function": "_deepcopy_list",
+ "primitive_calls": 62,
+ "calls": 102,
+ "self_seconds": 0.00029622000000000003,
+ "cumulative_seconds": 0.015576707
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 227,
+ "function": "_deepcopy_dict",
+ "primitive_calls": 1244,
+ "calls": 1286,
+ "self_seconds": 0.002795505,
+ "cumulative_seconds": 0.0136529
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 129,
+ "function": "refresh",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00020363300000000002,
+ "cumulative_seconds": 0.013397542
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 813,
+ "function": "_read_bytes",
+ "primitive_calls": 1578,
+ "calls": 1578,
+ "self_seconds": 0.000573644,
+ "cumulative_seconds": 0.012624044000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 1578,
+ "calls": 1578,
+ "self_seconds": 0.00021573000000000002,
+ "cumulative_seconds": 0.011968982000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/socket.py",
+ "line": 704,
+ "function": "readinto",
+ "primitive_calls": 68,
+ "calls": 68,
+ "self_seconds": 8.204000000000001e-05,
+ "cumulative_seconds": 0.011753252
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1299,
+ "function": "recv_into",
+ "primitive_calls": 68,
+ "calls": 68,
+ "self_seconds": 8.5711e-05,
+ "cumulative_seconds": 0.011628085000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1157,
+ "function": "read",
+ "primitive_calls": 68,
+ "calls": 68,
+ "self_seconds": 4.8955e-05,
+ "cumulative_seconds": 0.011532369
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 68,
+ "calls": 68,
+ "self_seconds": 0.011478791,
+ "cumulative_seconds": 0.011478791
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 134,
+ "function": "_active_ddl_guard",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 3.3373e-05,
+ "cumulative_seconds": 0.010089582000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 869,
+ "function": "to_dicts",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 2.8163e-05,
+ "cumulative_seconds": 0.00942579
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 797,
+ "function": "",
+ "primitive_calls": 100001,
+ "calls": 100001,
+ "self_seconds": 0.009360598000000001,
+ "cumulative_seconds": 0.009360598000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 34,
+ "function": "_exact_equal",
+ "primitive_calls": 22,
+ "calls": 14072,
+ "self_seconds": 0.0036603200000000003,
+ "cumulative_seconds": 0.009319462
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 845,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.009172375,
+ "cumulative_seconds": 0.009172375
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1370,
+ "function": "_read_result_packet",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 6.8911e-05,
+ "cumulative_seconds": 0.008562127000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/__init__.py",
+ "line": 138,
+ "function": "jsonify",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.0041000000000001e-05,
+ "cumulative_seconds": 0.007163875
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 189,
+ "function": "response",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.45e-05,
+ "cumulative_seconds": 0.007149417000000001
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 61,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.6335000000000004e-05,
+ "cumulative_seconds": 0.007092542
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 1231,
+ "function": "cursor",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 2.0376000000000003e-05,
+ "cumulative_seconds": 0.006754127
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 42,
+ "function": "",
+ "primitive_calls": 6012,
+ "calls": 16026,
+ "self_seconds": 0.003282557,
+ "cumulative_seconds": 0.006243895
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 799,
+ "calls": 799,
+ "self_seconds": 0.005567064,
+ "cumulative_seconds": 0.005899364000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 206318,
+ "calls": 206320,
+ "self_seconds": 0.005704319,
+ "cumulative_seconds": 0.00570486
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 62,
+ "function": "browser_safe",
+ "primitive_calls": 1,
+ "calls": 9641,
+ "self_seconds": 0.0031553510000000002,
+ "cumulative_seconds": 0.005591416
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 66,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1363,
+ "self_seconds": 0.0013690260000000002,
+ "cumulative_seconds": 0.005589625
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 2,
+ "calls": 102,
+ "self_seconds": 0.00018567600000000002,
+ "cumulative_seconds": 0.0055809580000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1416,
+ "function": "_read_rowdata_packet",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.00035660600000000003,
+ "cumulative_seconds": 0.005455832000000001
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 327,
+ "function": "_update_summary",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.4039e-05,
+ "cumulative_seconds": 0.004595458
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 305,
+ "function": "_replace_summary_cell",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.000145517,
+ "cumulative_seconds": 0.004489292000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 173,
+ "function": "_rows",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.0584e-05,
+ "cumulative_seconds": 0.004346542
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 125,
+ "function": "_observer_status",
+ "primitive_calls": 8,
+ "calls": 8,
+ "self_seconds": 2.7333e-05,
+ "cumulative_seconds": 0.0037800420000000004
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 2.5165e-05,
+ "cumulative_seconds": 0.0037324990000000002
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 44,
+ "function": "",
+ "primitive_calls": 2040,
+ "calls": 2040,
+ "self_seconds": 0.00045358700000000003,
+ "cumulative_seconds": 0.0035938600000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1429,
+ "function": "_read_row_from_packet",
+ "primitive_calls": 438,
+ "calls": 438,
+ "self_seconds": 0.001002374,
+ "cumulative_seconds": 0.0033145550000000003
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1448,
+ "function": "_get_descriptions",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.00034725900000000004,
+ "cumulative_seconds": 0.003013874
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 107,
+ "function": "_ddl",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 2.8708e-05,
+ "cumulative_seconds": 0.00267225
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 163,
+ "function": "read_length_coded_string",
+ "primitive_calls": 2954,
+ "calls": 2954,
+ "self_seconds": 0.000889178,
+ "cumulative_seconds": 0.0026469410000000003
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 898,
+ "function": "",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 6.6454e-05,
+ "cumulative_seconds": 0.0026390420000000003
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 115,
+ "function": "_triggers",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.041e-06,
+ "cumulative_seconds": 0.0023985
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 210,
+ "function": "_index_records",
+ "primitive_calls": 21,
+ "calls": 21,
+ "self_seconds": 1.1210000000000001e-05,
+ "cumulative_seconds": 0.002288376
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 899,
+ "function": "",
+ "primitive_calls": 106,
+ "calls": 106,
+ "self_seconds": 0.000433452,
+ "cumulative_seconds": 0.002266297
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 188,
+ "function": "_validated_tags",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 8.5983e-05,
+ "cumulative_seconds": 0.002170832
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 101,
+ "function": "selected",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.5583e-05,
+ "cumulative_seconds": 0.002097583
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 185,
+ "function": "_metadata",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.2084e-05,
+ "cumulative_seconds": 0.002027876
+ },
+ {
+ "file": "python/workspace_curation.py",
+ "line": 170,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.584000000000001e-06,
+ "cumulative_seconds": 0.001985417
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 282,
+ "function": "__and__",
+ "primitive_calls": 16,
+ "calls": 16,
+ "self_seconds": 1.2912000000000001e-05,
+ "cumulative_seconds": 0.001876915
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 178,
+ "function": "restrict",
+ "primitive_calls": 16,
+ "calls": 16,
+ "self_seconds": 0.00010284000000000001,
+ "cumulative_seconds": 0.0018640030000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 195,
+ "function": "_chips",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 3.8133e-05,
+ "cumulative_seconds": 0.001712214
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 183,
+ "function": "dumps",
+ "primitive_calls": 12,
+ "calls": 12,
+ "self_seconds": 3.5957e-05,
+ "cumulative_seconds": 0.001695957
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 232,
+ "function": "__init__",
+ "primitive_calls": 180,
+ "calls": 180,
+ "self_seconds": 9.493400000000001e-05,
+ "cumulative_seconds": 0.0016856750000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 183,
+ "function": "encode",
+ "primitive_calls": 12,
+ "calls": 12,
+ "self_seconds": 2.7797e-05,
+ "cumulative_seconds": 0.001647334
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 205,
+ "function": "iterencode",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 0.0016145810000000001,
+ "cumulative_seconds": 0.0016145810000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 236,
+ "function": "_parse_field_descriptor",
+ "primitive_calls": 180,
+ "calls": 180,
+ "self_seconds": 0.000354618,
+ "cumulative_seconds": 0.001565664
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 223,
+ "function": "attributes",
+ "primitive_calls": 1283,
+ "calls": 1283,
+ "self_seconds": 0.000465347,
+ "cumulative_seconds": 0.0015109540000000001
+ }
+ ],
+ "native_sql_calls": [
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0008165829931385815
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004690829664468765
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00019691599300131202
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004064170061610639
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00022895901929587126
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00041312503162771463
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00036708300467580557
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00013633299386128783
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00024399999529123306
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00017770903650671244
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s)",
+ "seconds": 0.0004562499816529453
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0011884580017067492
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003949590027332306
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00017270800890401006
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00036383303813636303
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00019633298506960273
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003802090068347752
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0002929170150309801
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014900002861395478
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0002043339773081243
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00039837497752159834
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00017920800019055605
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00017920898972079158
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00022587500279769301
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0002990829525515437
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.0005216670106165111
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT target_kind,target_uuid,profile_uuid,last_generation,deleted FROM `recording_workspace`.`app_annotation_changes` WHERE project_uuid=%s AND last_generation>%s ORDER BY last_generation LIMIT %s",
+ "seconds": 0.0009875830146484077
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( ( (`target_kind`='?')AND(`target_uuid`='?')AND(`p",
+ "seconds": 0.0010088339913636446
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`epoch_uuid`,`included`,`tags`,`review_state`,`revision`,`metadata_fingerprint` FROM `recording_workspace`.`curation` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))AND( ( (`epoch_uuid`='?') OR (",
+ "seconds": 0.0010201659752056003
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`dataset_uuid`,`created_at`,`artifact_sha256` FROM `recording_workspace`.`dataset_revision` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00026108301244676113
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.0016577920177951455
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.0005168340285308659
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0003957920125685632
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00028662499971687794
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.0002727499813772738
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00037712499033659697
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0008582909940741956
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0005020410171709955
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.0002589999930933118
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00041020900243893266
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002365419641137123
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00042104098247364163
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00041141698602586985
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014849996659904718
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00022270798217505217
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.0001907499972730875
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s)",
+ "seconds": 0.00046195898903533816
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0012037919950671494
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004356250283308327
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00019483303185552359
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0003831670037470758
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00022945896489545703
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0006534999702125788
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003117920132353902
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014479196397587657
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00024395901709794998
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004634169745258987
+ }
+ ],
+ "limits": "Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap."
+ },
+ "same_server_checkpoint_reopen": {
+ "seconds": 2.0839288330171257,
+ "checkpoint": {
+ "status": "ready",
+ "phases": {
+ "indexing_shared_annotations": {
+ "seconds": 0.024348
+ },
+ "preparing_tag_filters": {
+ "seconds": 0.029559
+ },
+ "preparing_tag_summary": {
+ "seconds": 0.028029
+ },
+ "preparing_tag_suggestions": {
+ "seconds": 0.024101
+ },
+ "indexing_dataset_tags": {
+ "seconds": 0.157558
+ },
+ "preparing_protocol_reads": {
+ "seconds": 0.052877
+ }
+ },
+ "project_uuid": "4d228f00-c675-479b-ba5d-62b35548d894",
+ "epochs": 100000,
+ "cells": 1000,
+ "metadata_generation": "tag-workflow-fixture-v1",
+ "phase": "complete",
+ "shared_index": {
+ "full_builds": 2,
+ "delta_updates": 26,
+ "rows_loaded": 303230,
+ "rows_indexed": 303000
+ },
+ "dataset_index": {
+ "full_builds": 2,
+ "delta_updates": 1,
+ "rows_loaded": 200000,
+ "rows_indexed": 200000
+ },
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "seconds": 0.408185,
+ "checkpoint": {
+ "saved": true,
+ "rows_indexed": 303000,
+ "sha256": "7f703614b2ae1783f0d787a7c75451067b1bb56c216c91ac3b52e563da1dcecc"
+ }
+ },
+ "reopened": {
+ "status": "ready",
+ "phases": {
+ "indexing_shared_annotations": {
+ "seconds": 0.027161
+ },
+ "preparing_tag_filters": {
+ "seconds": 0.070612
+ },
+ "preparing_tag_summary": {
+ "seconds": 0.031916
+ },
+ "preparing_tag_suggestions": {
+ "seconds": 0.022668
+ },
+ "indexing_dataset_tags": {
+ "seconds": 0.035821
+ },
+ "preparing_protocol_reads": {
+ "seconds": 0.052704
+ }
+ },
+ "project_uuid": "4d228f00-c675-479b-ba5d-62b35548d894",
+ "epochs": 100000,
+ "cells": 1000,
+ "metadata_generation": "tag-workflow-fixture-v1",
+ "phase": "complete",
+ "shared_index": {
+ "full_builds": 2,
+ "delta_updates": 26,
+ "rows_loaded": 303230,
+ "rows_indexed": 303000
+ },
+ "dataset_index": {
+ "full_builds": 2,
+ "delta_updates": 1,
+ "rows_loaded": 200000,
+ "rows_indexed": 200000
+ },
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "seconds": 0.330798,
+ "checkpoint_restore": {
+ "restored": true,
+ "rows_indexed": 303000,
+ "sha256": "7f703614b2ae1783f0d787a7c75451067b1bb56c216c91ac3b52e563da1dcecc"
+ }
+ },
+ "full_builds_before": 2,
+ "full_builds_after": 2,
+ "scope": "Shared derived index closed and reopened within the same native server; not a native process restart."
+ },
+ "workflow_peak_rss_before_durable_oracle_bytes": 999522304,
+ "durable_snapshot_verify_seconds": 7.310168291965965,
+ "loaded_product_modules": {
+ "workspace_tree": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree.py",
+ "workspace_predicates": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_predicates.py",
+ "workspace_cache_lifecycle": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_cache_lifecycle.py",
+ "workspace_metadata_objects": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_metadata_objects.py",
+ "workspace_disk_index": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py",
+ "workspace_catalog_identity": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_catalog_identity.py",
+ "recording_workspace": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/recording_workspace.py",
+ "workspace_recipes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recipes.py",
+ "workspace_tree_code": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree_code.py",
+ "workspace_service": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py",
+ "workspace_tree_pages": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree_pages.py",
+ "workspace_projects": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_projects.py",
+ "workspace_mysql_profile": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mysql_profile.py",
+ "workspace_native_mysql": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_native_mysql.py",
+ "workspace_mysql_runtime": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mysql_runtime.py",
+ "workspace_storage": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_storage.py",
+ "workspace_diff": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_diff.py",
+ "workspace_protocol_identity": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_protocol_identity.py",
+ "workspace_import_check": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_import_check.py",
+ "workspace_audit": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_audit.py",
+ "workspace_api": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_api.py",
+ "workspace_state_snapshot": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_snapshot.py",
+ "workspace_desktop_paths": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_desktop_paths.py",
+ "workspace_startup_registry": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_startup_registry.py",
+ "workspace_curation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_curation.py",
+ "workspace_explorer": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_explorer.py",
+ "workspace_datastores": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_datastores.py",
+ "workspace_suggestions": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_suggestions.py",
+ "workspace_tag_predicates": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tag_predicates.py",
+ "workspace_export_folder": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_export_folder.py",
+ "workspace_import_progress": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_import_progress.py",
+ "workspace_author_preferences": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_author_preferences.py",
+ "workspace_annotations": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotations.py",
+ "workspace_project_preferences": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_project_preferences.py",
+ "workspace_protocol_state": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_protocol_state.py",
+ "workspace_state_generation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_generation.py",
+ "workspace_project_servers": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_project_servers.py",
+ "workspace_launcher": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_launcher.py",
+ "workspace_app_routes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_app_routes.py",
+ "workspace_folder_browser": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_folder_browser.py",
+ "workspace_h5_inbox": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_h5_inbox.py",
+ "workspace_tag_exchange": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tag_exchange.py",
+ "workspace_external_tags": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_external_tags.py",
+ "workspace_qc": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_qc.py",
+ "workspace_search": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_search.py",
+ "workspace_matlab_masks": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab_masks.py",
+ "workspace_matlab_routes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab_routes.py",
+ "workspace_mask_refresh": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mask_refresh.py",
+ "workspace_matching_epochs": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matching_epochs.py",
+ "workspace_candidate_exports": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_candidate_exports.py",
+ "workspace_backup_scheduler": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_backup_scheduler.py",
+ "workspace_recovery_store": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recovery_store.py",
+ "workspace_recovery_generation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recovery_generation.py",
+ "workspace_shared_tag_index": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_shared_tag_index.py",
+ "workspace_annotation_preparation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotation_preparation.py",
+ "workspace_annotation_checkpoint": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotation_checkpoint.py",
+ "workspace_shared_vocabulary": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_shared_vocabulary.py",
+ "workspace_curation_vocabulary": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_curation_vocabulary.py",
+ "workspace_lifecycle": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_lifecycle.py"
+ },
+ "owned_native_runtime_stopped": true,
+ "source_inventory_after": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "db14aaa6c1cf1920a52f340028407e0d66222c439feecad4af9c21faabf095be",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7afc6c297770800fb90e148ffeb4ad3d5c24c8e84f5a788094f3246f59f97782",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "49769c7f0b356e085d7e076abe17350e546b59444936d9caecea8236d60699ad",
+ "python/workspace_annotations.py": "a2eb2b9282a226006dab89691b099b73ff9fd12e15985bd548c9a4ef4ab68ee2",
+ "python/workspace_api.py": "b1b429181053bcc3981793ca70b191b00dfd5c0631ed65b94c12aa5bd34ef992",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "29c2d2cadce647b67d085febd30532d5cac726056a76ebf42c70ea40abdc112a",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "0443e20a6fc78387b6a3925b7a588045181ad4d1033fed439ba23f8c19a75706",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "fa7c39178b4e7876bdf48d5d076b4afa71906aebfcd80c552566a5a1f1fe2c2e",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "4b83f86efbfea8c3fe1194cf59c3e810970059e3ec1bf579adc56472c28c8d09",
+ "python/workspace_installation.py": "8f0787584d7650b58c422f6980592f06411c4071a1c0405cf36eb7bebd05761b",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "b3d0a1f2c3505e14f249e674574dec5e81594b2049ca48e760dbf5d6d45fa894",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "35a10441d0ba59e43c59ad564879b2b73eccc29cd19a2558e3eaf8db1a9f5782",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "f5b26e0f77797e9ffae3a26188c1ea0d75f0fe1698ff8d5085c7c892eec7693c",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "source_unchanged": true,
+ "harness_unchanged": true,
+ "seconds": 150.22804466704838,
+ "finished_at": "2026-09-30T13:58:53.988030+00:00",
+ "peak_rss_bytes": 1032306688,
+ "passed": true
+}
diff --git a/docs/dev/scale-audit-2026-09-29/tag-sequence-after-500.json b/docs/dev/scale-audit-2026-09-29/tag-sequence-after-500.json
new file mode 100644
index 0000000..c1499f4
--- /dev/null
+++ b/docs/dev/scale-audit-2026-09-29/tag-sequence-after-500.json
@@ -0,0 +1,3265 @@
+{
+ "scope": "Native tag-ten, tag-another-ten, filter-twenty and hierarchical cell/epoch workflow.\n\nEvery project/database is disposable. Acquisition metadata is synthetic; actual\nMySQL, triggers, Flask routes, derived indexes and each version's save hooks\nare used. Native SQL commits are checked immediately; any background recovery\nmirror is explicitly flushed before its final oracle. HTTP times include Flask serialization/client JSON decoding, exclude\noracle work, and do not model browser concurrency/rendering. Sequential bundle\ntimes are sums of their separately measured requests. Profiles are separate.\n",
+ "epochs": 500,
+ "profiles": 3,
+ "tags_per_annotation": 5,
+ "protocols": 2,
+ "samples": 5,
+ "source_root": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI",
+ "source_inventory_before": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "db14aaa6c1cf1920a52f340028407e0d66222c439feecad4af9c21faabf095be",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7afc6c297770800fb90e148ffeb4ad3d5c24c8e84f5a788094f3246f59f97782",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "49769c7f0b356e085d7e076abe17350e546b59444936d9caecea8236d60699ad",
+ "python/workspace_annotations.py": "a2eb2b9282a226006dab89691b099b73ff9fd12e15985bd548c9a4ef4ab68ee2",
+ "python/workspace_api.py": "b1b429181053bcc3981793ca70b191b00dfd5c0631ed65b94c12aa5bd34ef992",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "29c2d2cadce647b67d085febd30532d5cac726056a76ebf42c70ea40abdc112a",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "0443e20a6fc78387b6a3925b7a588045181ad4d1033fed439ba23f8c19a75706",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "fa7c39178b4e7876bdf48d5d076b4afa71906aebfcd80c552566a5a1f1fe2c2e",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "4b83f86efbfea8c3fe1194cf59c3e810970059e3ec1bf579adc56472c28c8d09",
+ "python/workspace_installation.py": "8f0787584d7650b58c422f6980592f06411c4071a1c0405cf36eb7bebd05761b",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "b3d0a1f2c3505e14f249e674574dec5e81594b2049ca48e760dbf5d6d45fa894",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "35a10441d0ba59e43c59ad564879b2b73eccc29cd19a2558e3eaf8db1a9f5782",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "f5b26e0f77797e9ffae3a26188c1ea0d75f0fe1698ff8d5085c7c892eec7693c",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "fixture_module": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/docs/dev/scale-audit-2026-09-29/benchmark_service_scale.py",
+ "harness_sha256": "290deaf5e34be5a5bc1554ae072d633f005758265b5e6e32f48a4f1b4ff4450e",
+ "started_at": "2026-09-30T13:55:40.227699+00:00",
+ "release_ready": false,
+ "operations": {
+ "preparation_epoch_filter": {
+ "samples_seconds": [
+ 0.01644916698569432
+ ],
+ "p50_seconds": 0.01644916698569432,
+ "p95_seconds": 0.01644916698569432,
+ "p99_seconds": 0.01644916698569432,
+ "maximum_seconds": 0.01644916698569432,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 109807,
+ "Questions": 60
+ }
+ ]
+ },
+ "preparation_cell_filter": {
+ "samples_seconds": [
+ 0.010143166000489146
+ ],
+ "p50_seconds": 0.010143166000489146,
+ "p95_seconds": 0.010143166000489146,
+ "p99_seconds": 0.010143166000489146,
+ "maximum_seconds": 0.010143166000489146,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 103268,
+ "Questions": 53
+ }
+ ]
+ },
+ "preparation_hierarchical_filter": {
+ "samples_seconds": [
+ 0.010237874987069517
+ ],
+ "p50_seconds": 0.010237874987069517,
+ "p95_seconds": 0.010237874987069517,
+ "p99_seconds": 0.010237874987069517,
+ "maximum_seconds": 0.010237874987069517,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 103268,
+ "Questions": 53
+ }
+ ]
+ },
+ "first10_revision_preflight": {
+ "samples_seconds": [
+ 0.00580725003965199,
+ 0.005715915991459042,
+ 0.005966249969787896,
+ 0.005814249976538122,
+ 0.005724208022002131
+ ],
+ "p50_seconds": 0.00580725003965199,
+ "p95_seconds": 0.005966249969787896,
+ "p99_seconds": 0.005966249969787896,
+ "maximum_seconds": 0.005966249969787896,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15362,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15363,
+ "Questions": 4
+ }
+ ]
+ },
+ "first10_save": {
+ "samples_seconds": [
+ 0.029821583011653274,
+ 0.014536290953401476,
+ 0.013769957993645221,
+ 0.016117041988763958,
+ 0.015186292002908885
+ ],
+ "p50_seconds": 0.015186292002908885,
+ "p95_seconds": 0.029821583011653274,
+ "p99_seconds": 0.029821583011653274,
+ "maximum_seconds": 0.029821583011653274,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 18598,
+ "Questions": 36
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12423,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ }
+ ]
+ },
+ "second10_revision_preflight": {
+ "samples_seconds": [
+ 0.005632625019643456,
+ 0.005842083017341793,
+ 0.005539792007766664,
+ 0.005961292015854269,
+ 0.005695416999515146
+ ],
+ "p50_seconds": 0.005695416999515146,
+ "p95_seconds": 0.005961292015854269,
+ "p99_seconds": 0.005961292015854269,
+ "maximum_seconds": 0.005961292015854269,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15380,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15381,
+ "Questions": 4
+ }
+ ]
+ },
+ "second10_save": {
+ "samples_seconds": [
+ 0.012998457998037338,
+ 0.014681166969239712,
+ 0.013435167027637362,
+ 0.016471499984618276,
+ 0.013383999990765005
+ ],
+ "p50_seconds": 0.013435167027637362,
+ "p95_seconds": 0.016471499984618276,
+ "p99_seconds": 0.016471499984618276,
+ "maximum_seconds": 0.016471499984618276,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12464,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12435,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter20": {
+ "samples_seconds": [
+ 0.03058974997838959,
+ 0.032823665998876095,
+ 0.03138233401114121,
+ 0.031089458032511175,
+ 0.030213333026040345
+ ],
+ "p50_seconds": 0.031089458032511175,
+ "p95_seconds": 0.032823665998876095,
+ "p99_seconds": 0.032823665998876095,
+ "maximum_seconds": 0.032823665998876095,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 132576,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 132577,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 132517,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 132577,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 132577,
+ "Questions": 58
+ }
+ ]
+ },
+ "cell_save": {
+ "samples_seconds": [
+ 0.006776041002012789,
+ 0.0072352500283159316,
+ 0.0067298339563421905,
+ 0.00659679202362895,
+ 0.006482040975242853
+ ],
+ "p50_seconds": 0.0067298339563421905,
+ "p95_seconds": 0.0072352500283159316,
+ "p99_seconds": 0.0072352500283159316,
+ "maximum_seconds": 0.0072352500283159316,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4167,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4165,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_cell100": {
+ "samples_seconds": [
+ 0.04069062497001141,
+ 0.04411966697080061,
+ 0.04147870797896758,
+ 0.041231665993109345,
+ 0.041584583988878876
+ ],
+ "p50_seconds": 0.04147870797896758,
+ "p95_seconds": 0.04411966697080061,
+ "p99_seconds": 0.04411966697080061,
+ "maximum_seconds": 0.04411966697080061,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 160866,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 160867,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 160864,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 160867,
+ "Questions": 58
+ },
+ {
+ "Bytes_sent": 160867,
+ "Questions": 58
+ }
+ ]
+ },
+ "filter_cell_and_epoch10": {
+ "samples_seconds": [
+ 0.017229959019459784,
+ 0.01879908301634714,
+ 0.018503208993934095,
+ 0.017395791015587747,
+ 0.017116750008426607
+ ],
+ "p50_seconds": 0.017395791015587747,
+ "p95_seconds": 0.01879908301634714,
+ "p99_seconds": 0.01879908301634714,
+ "maximum_seconds": 0.01879908301634714,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 115266,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115267,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115267,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115267,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115267,
+ "Questions": 56
+ }
+ ]
+ },
+ "remove_first10_revision_preflight": {
+ "samples_seconds": [
+ 0.00589170801686123,
+ 0.005995291983708739,
+ 0.005814332980662584,
+ 0.0059766669874079525,
+ 0.0059321249718777835
+ ],
+ "p50_seconds": 0.0059321249718777835,
+ "p95_seconds": 0.005995291983708739,
+ "p99_seconds": 0.005995291983708739,
+ "maximum_seconds": 0.005995291983708739,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15800,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_first10_save": {
+ "samples_seconds": [
+ 0.013581749983131886,
+ 0.013324790983460844,
+ 0.013831000018399209,
+ 0.014046875003259629,
+ 0.01337204099399969
+ ],
+ "p50_seconds": 0.013581749983131886,
+ "p95_seconds": 0.014046875003259629,
+ "p99_seconds": 0.014046875003259629,
+ "maximum_seconds": 0.014046875003259629,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12423,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12453,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12454,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_hierarchy_after_epoch_remove": {
+ "samples_seconds": [
+ 0.017217750020790845,
+ 0.016979708976577967,
+ 0.01686954201431945,
+ 0.017657916992902756,
+ 0.017042708001099527
+ ],
+ "p50_seconds": 0.017042708001099527,
+ "p95_seconds": 0.017657916992902756,
+ "p99_seconds": 0.017657916992902756,
+ "maximum_seconds": 0.017657916992902756,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 107578,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107578,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107548,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107578,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107579,
+ "Questions": 55
+ }
+ ]
+ },
+ "filter_remaining10": {
+ "samples_seconds": [
+ 0.01703533303225413,
+ 0.018365875002928078,
+ 0.017072249960619956,
+ 0.017453874985221773,
+ 0.017128000035881996
+ ],
+ "p50_seconds": 0.017128000035881996,
+ "p95_seconds": 0.018365875002928078,
+ "p99_seconds": 0.018365875002928078,
+ "maximum_seconds": 0.018365875002928078,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 115259,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115259,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115259,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115259,
+ "Questions": 56
+ },
+ {
+ "Bytes_sent": 115260,
+ "Questions": 56
+ }
+ ]
+ },
+ "remove_second10_revision_preflight": {
+ "samples_seconds": [
+ 0.006649749993812293,
+ 0.005857624986674637,
+ 0.005839625024236739,
+ 0.0058952500112354755,
+ 0.005945166980382055
+ ],
+ "p50_seconds": 0.0058952500112354755,
+ "p95_seconds": 0.006649749993812293,
+ "p99_seconds": 0.006649749993812293,
+ "maximum_seconds": 0.006649749993812293,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15801,
+ "Questions": 4
+ },
+ {
+ "Bytes_sent": 15802,
+ "Questions": 4
+ }
+ ]
+ },
+ "remove_second10_save": {
+ "samples_seconds": [
+ 0.016080375004094094,
+ 0.014161500032059848,
+ 0.013747874996624887,
+ 0.01386341598117724,
+ 0.013907541986554861
+ ],
+ "p50_seconds": 0.013907541986554861,
+ "p95_seconds": 0.016080375004094094,
+ "p99_seconds": 0.016080375004094094,
+ "maximum_seconds": 0.016080375004094094,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12435,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12465,
+ "Questions": 29
+ },
+ {
+ "Bytes_sent": 12466,
+ "Questions": 29
+ }
+ ]
+ },
+ "filter_after_all_epoch_removals": {
+ "samples_seconds": [
+ 0.018500833015423268,
+ 0.017200375034008175,
+ 0.01669512502849102,
+ 0.017199666996020824,
+ 0.016872207983396947
+ ],
+ "p50_seconds": 0.017199666996020824,
+ "p95_seconds": 0.018500833015423268,
+ "p99_seconds": 0.018500833015423268,
+ "maximum_seconds": 0.018500833015423268,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 107581,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107581,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107551,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107581,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 107582,
+ "Questions": 55
+ }
+ ]
+ },
+ "remove_cell_save": {
+ "samples_seconds": [
+ 0.007180082960985601,
+ 0.006386167020536959,
+ 0.00700258300639689,
+ 0.006299041968304664,
+ 0.007117417000699788
+ ],
+ "p50_seconds": 0.00700258300639689,
+ "p95_seconds": 0.007180082960985601,
+ "p99_seconds": 0.007180082960985601,
+ "maximum_seconds": 0.007180082960985601,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4165,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4168,
+ "Questions": 11
+ },
+ {
+ "Bytes_sent": 4169,
+ "Questions": 11
+ }
+ ]
+ },
+ "filter_after_cell_removal": {
+ "samples_seconds": [
+ 0.016276916954666376,
+ 0.014714459015522152,
+ 0.01603037497261539,
+ 0.014659375010523945,
+ 0.01551475003361702
+ ],
+ "p50_seconds": 0.01551475003361702,
+ "p95_seconds": 0.016276916954666376,
+ "p99_seconds": 0.016276916954666376,
+ "maximum_seconds": 0.016276916954666376,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 104987,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 104987,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 104984,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 104987,
+ "Questions": 55
+ },
+ {
+ "Bytes_sent": 104988,
+ "Questions": 55
+ }
+ ]
+ },
+ "tag10_then10_filter20_http_total": {
+ "samples_seconds": [
+ 0.08484966604737565,
+ 0.07359912293031812,
+ 0.07009350100997835,
+ 0.0754535419982858,
+ 0.07020325004123151
+ ],
+ "p50_seconds": 0.07359912293031812,
+ "p95_seconds": 0.08484966604737565,
+ "p99_seconds": 0.08484966604737565,
+ "maximum_seconds": 0.08484966604737565,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "cell_tag_then_cell_filter_http_total": {
+ "samples_seconds": [
+ 0.0474666659720242,
+ 0.05135491699911654,
+ 0.04820854193530977,
+ 0.047828458016738296,
+ 0.04806662496412173
+ ],
+ "p50_seconds": 0.04806662496412173,
+ "p95_seconds": 0.05135491699911654,
+ "p99_seconds": 0.05135491699911654,
+ "maximum_seconds": 0.05135491699911654,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "first10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.035628833051305264,
+ 0.020252206944860518,
+ 0.019736207963433117,
+ 0.02193129196530208,
+ 0.020910500024911016
+ ],
+ "p50_seconds": 0.020910500024911016,
+ "p95_seconds": 0.035628833051305264,
+ "p99_seconds": 0.035628833051305264,
+ "maximum_seconds": 0.035628833051305264,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "second10_selected_action_http_total": {
+ "samples_seconds": [
+ 0.018631083017680794,
+ 0.020523249986581504,
+ 0.018974959035404027,
+ 0.022432792000472546,
+ 0.01907941699028015
+ ],
+ "p50_seconds": 0.01907941699028015,
+ "p95_seconds": 0.022432792000472546,
+ "p99_seconds": 0.022432792000472546,
+ "maximum_seconds": 0.022432792000472546,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": []
+ },
+ "same_server_reopened_filter20": {
+ "samples_seconds": [
+ 0.020636458008084446
+ ],
+ "p50_seconds": 0.020636458008084446,
+ "p95_seconds": 0.020636458008084446,
+ "p99_seconds": 0.020636458008084446,
+ "maximum_seconds": 0.020636458008084446,
+ "quantile_method": "nearest_rank",
+ "sql_session_deltas": [
+ {
+ "Bytes_sent": 124930,
+ "Questions": 56
+ }
+ ]
+ }
+ },
+ "checks": [
+ {
+ "name": "preparation_exact_empty_memberships",
+ "passed": true
+ },
+ {
+ "name": "same_server_checkpoint_restores_exact_twenty_without_rebuild",
+ "passed": true
+ },
+ {
+ "name": "canonical_final_markers_removed",
+ "passed": true
+ },
+ {
+ "name": "flushed_recovery_mirror_exact_revisions_and_removal",
+ "passed": true,
+ "records": 63
+ },
+ {
+ "name": "one_canonical_cell_record_no_child_copies",
+ "passed": true,
+ "child_epochs": 100
+ },
+ {
+ "name": "independent_protocol_curation_untouched",
+ "passed": true
+ },
+ {
+ "name": "all_product_modules_loaded_from_selected_source",
+ "passed": true
+ }
+ ],
+ "page_cells": true,
+ "target_specification": "tag10_then10_filter20_and_cell_epoch_hierarchy",
+ "baseline_harness_sha256": "272fe050c8762ed4ea45bd013e236e8723d8fe46f5e7e293c58e2edf1fd52ddc",
+ "mysql_version": "8.4.2",
+ "seed_seconds": 0.24151587497908622,
+ "seed_checkpoint_seconds": 0.188359624997247,
+ "selected_epoch_uuids": {
+ "first10": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5"
+ ],
+ "second10": [
+ "ad573668-6512-6e55-649e-cb23ae1d4888",
+ "16dc368a-89b4-28b2-4854-84313ba67a39",
+ "37834f2f-2576-2f23-e1f7-4a531cbe445d",
+ "454f63ac-30c8-3229-97ef-025edff6abd2",
+ "5ef6fdf3-2513-aa7c-d11f-72beccf132b9",
+ "1253e937-3e78-1b75-0026-6caa55150e08",
+ "482d9673-cfee-5de3-91f9-7fde4d1c84f9",
+ "3346f2bb-f6c3-4bd2-dbe2-8bd1bb657d0e",
+ "9537f32e-c759-9e1a-e953-af6c9f929fe7",
+ "0fd42b3f-73c4-48b3-4940-b339f87d07ad"
+ ],
+ "cell": "0604cd31-38fe-ed20-2ef2-93e062da2f47",
+ "cell_children": [
+ "5feceb66-ffc8-6f38-d952-786c6d696c79",
+ "6b86b273-ff34-fce1-9d6b-804eff5a3f57",
+ "d4735e3a-265e-16ee-e03f-59718b9b5d03",
+ "4e074085-62be-db8b-60ce-05c1decfe3ad",
+ "4b227777-d4dd-1fc6-1c6f-884f48641d02",
+ "ef2d127d-e37b-942b-aad0-6145e54b0c61",
+ "e7f6c011-776e-8db7-cd33-0b54174fd76f",
+ "7902699b-e42c-8a8e-46fb-bb4501726517",
+ "2c624232-cdd2-2177-1294-dfbb310aca00",
+ "19581e27-de7c-ed00-ff1c-e50b2047e7a5",
+ "4a44dc15-3642-04a8-0fe8-0e9039455cc1",
+ "4fc82b26-aecb-47d2-868c-4efbe3581732",
+ "6b51d431-df5d-7f14-1cbe-ceccf79edf3d",
+ "3fdba35f-04dc-8c46-2986-c992bcf87554",
+ "8527a891-e224-1369-50ff-32ca212b45bc",
+ "e629fa65-98d7-3276-8f7c-726b4b621285",
+ "b17ef6d1-9c7a-5b1e-e83b-907c595526dc",
+ "4523540f-1504-cd17-100c-4835e85b7eef",
+ "4ec9599f-c203-d176-a301-536c2e091a19",
+ "9400f1b2-1cb5-27d7-fa3d-3eabba93557a",
+ "f5ca38f7-48a1-d6ea-f726-b8a42fb575c3",
+ "6f4b6612-125f-b3a0-daec-d2799dfd6c9c",
+ "785f3ec7-eb32-f30b-90cd-0fcf3657d388",
+ "535fa30d-7e25-dd8a-49f1-536779734ec8",
+ "c2356069-e9d1-e79c-a924-378153cfbbfb",
+ "b7a56873-cd77-1f2c-446d-369b649430b6",
+ "5f9c4ab0-8cac-7457-e911-1a30e4664920",
+ "670671cd-9740-4156-226e-507973f2ab83",
+ "59e19706-d51d-39f6-6711-c2653cd7eb12",
+ "35135aaa-6cc2-3891-b40c-b3f378c53a17",
+ "624b60c5-8c9d-8bfb-6ff1-886c2fd605d2",
+ "eb1e33e8-a81b-697b-7585-5af6bfcdbcbf",
+ "e29c9c18-0c62-79b0-b02a-bd6a1801c7c0",
+ "c6f3ac57-944a-5314-90cd-39902d0f7777",
+ "86e50149-6586-6131-2a9e-0b35558d84f6",
+ "9f14025a-f006-5b30-e47e-23ebb3b491d3",
+ "76a50887-d8f1-c2e9-3017-55428990ad81",
+ "7a61b537-01be-fdae-0eee-ffaecc73f14e",
+ "aea92132-c4cb-eb26-3e6a-c2bf6c183b5d",
+ "0b918943-df09-62bc-7a18-24c0555a3893",
+ "d59eced1-ded0-7f84-c145-592f65bdf854",
+ "3d914f93-48c9-cc0f-f8a7-9716700b9fcd",
+ "73475cb4-0a56-8e8d-a8a0-45ced110137e",
+ "44cb730c-4204-80a0-477b-505ae68af508",
+ "71ee45a3-c0db-9a98-65f7-313dd3372cf6",
+ "811786ad-1ae7-4adf-dd20-dd0372abaaeb",
+ "25fc0e70-96fc-6537-1820-2dc30b0c580b",
+ "31489056-e091-6d59-fe3a-dd79e63f095a",
+ "98010bd9-270f-9b10-0b62-14a21754fd33",
+ "0e17daca-5f3e-175f-448b-acace3bc0da4",
+ "1a656259-0ef1-9d10-45d0-6c4055742d38",
+ "031b4af5-197e-c30a-926f-48cf40e11a7d",
+ "41cfc0d1-f2d1-27b0-4555-b7246d84019b",
+ "2858dcd1-057d-3eae-7f7d-5f782167e24b",
+ "2fca346d-b656-1871-02ce-806ac732e06a",
+ "02d20bbd-7e39-4ad5-999a-4cebabac9619",
+ "7688b6ef-5255-5962-d008-fff894223582",
+ "c837649c-ce43-f272-9138-e72cc3152070",
+ "6208ef0f-7750-c111-548c-f90b6ea1d0d0",
+ "3e1e967e-9b79-3e90-8f8e-ae83c74dba9b",
+ "39fa9ec1-90ee-e7b6-f4df-f1100d6343e1",
+ "d029fa3a-95e1-74a1-9934-857f535eb942",
+ "81b8a03f-97e8-787c-53fe-1a86bda042b6",
+ "da4ea2a5-506f-2693-eae1-90d9360a1f31",
+ "a68b412c-4282-555f-1554-6cf6e1fc4289",
+ "108c995b-953c-8a35-5611-03e2014cf828",
+ "3ada92f2-8b4c-eda3-8562-ebf047c6ff05",
+ "49d180ec-f561-3281-9571-bf39d9b7b342",
+ "a21855da-08cb-102d-1d21-7c53dc5824a3",
+ "c75cb66a-e28d-8ebc-6ede-d002c28a8ba0",
+ "ff5a1ae0-12af-a5d4-c889-c50ad427aaf5",
+ "7f2253d7-e228-b22a-08bd-a1f09c516f6f",
+ "87226162-0421-7edd-b39e-7df969e0698a",
+ "96061e92-f58e-4bdc-dee7-3df36183fe3a",
+ "eb624dbe-56eb-6620-ae62-080c10a273ca",
+ "f369cb89-fc62-7e66-8987-007d121ed1ea",
+ "f74efabe-f12e-a619-e30b-79bddef89cff",
+ "a88a7902-cb4e-f697-ba0b-6759c50e8c10",
+ "349c4120-1b62-db85-1192-665c504b350f",
+ "98a3ab7c-340e-8a03-3e7b-37b6ef942875",
+ "48449a14-a4ff-7d79-bb7a-1b6f3d488eba",
+ "5316ca1c-5ddc-a8e6-cecc-fce58f3b8540",
+ "a46e3763-2fa6-ca51-a13f-e39a567b3c23",
+ "bbb965ab-0c80-d653-8cf2-184babad2a56",
+ "44c8031c-b036-a735-0d8b-9b8603af662a",
+ "b4944c6f-f08d-c6f4-3da2-e9c824669b7d",
+ "434c9b5a-e514-646b-bd91-b50032ca579e",
+ "bdd2d3af-3a5a-1213-497d-4f1f7bfcda89",
+ "8b940be7-fb78-aaa6-b656-7dd7a3987996",
+ "cd70bea0-23f7-52a0-564a-bb6ed08d42c1",
+ "69f59c27-3b6e-669a-c32a-6dd5e1b2cb63",
+ "1da51b8d-8ff9-8f6a-48f8-0ae79fe3ca6c",
+ "82416496-09f8-8ccd-2a0a-5b233a07a538",
+ "6e400187-1c0c-f27c-7634-ef1dc478408f",
+ "e3d6c4d4-599e-0088-2384-ca981ee287ed",
+ "ad48ff99-415b-2f00-7dc3-5b7eb553fd1e",
+ "7b1a278f-5abe-8e9d-a907-fc9c29dfd432",
+ "d6d824ab-ba4a-fde8-1129-c71dea75b810",
+ "29db0c67-82db-d500-0559-ef4d9e953e30",
+ "8c1f1046-219d-dd21-6a02-3f792356ddf1"
+ ]
+ },
+ "backup_receipts": [
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 1,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 2,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 3,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 4,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 5,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 6,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 7,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 8,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 9,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 10,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 11,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 12,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 13,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 14,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 15,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 16,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 17,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 18,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 19,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 20,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 21,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 22,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 23,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 24,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 25,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 26,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 27,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 28,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 29,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "remove_cell_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 30,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_first10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 31,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_second10_save",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 0,
+ "last_error": null,
+ "last_success": null,
+ "pending": true,
+ "requested_sequence": 32,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_first10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T13:55:48.579032+00:00",
+ "pending": true,
+ "requested_sequence": 33,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ },
+ {
+ "operation": "profile_remove_second10",
+ "persistence": {
+ "backup": {
+ "completed_sequence": 32,
+ "last_error": null,
+ "last_success": "2026-09-30T13:55:48.579032+00:00",
+ "pending": true,
+ "requested_sequence": 34,
+ "running": false,
+ "status": "pending"
+ },
+ "database": "committed"
+ }
+ }
+ ],
+ "project_annotation_preparation": {
+ "status": "ready",
+ "phases": {
+ "indexing_shared_annotations": {
+ "seconds": 0.158166
+ },
+ "preparing_tag_filters": {
+ "seconds": 0.009537
+ },
+ "preparing_tag_summary": {
+ "seconds": 0.010038
+ },
+ "preparing_tag_suggestions": {
+ "seconds": 0.051574
+ },
+ "indexing_dataset_tags": {
+ "seconds": 0.129739
+ },
+ "preparing_protocol_reads": {
+ "seconds": 0.01974
+ }
+ },
+ "project_uuid": "e615364e-4a3d-48cc-a519-6bd387785153",
+ "epochs": 500,
+ "cells": 5,
+ "metadata_generation": "tag-workflow-fixture-v1",
+ "phase": "complete",
+ "shared_index": {
+ "full_builds": 1,
+ "delta_updates": 1,
+ "rows_loaded": 1515,
+ "rows_indexed": 1515
+ },
+ "dataset_index": {
+ "full_builds": 1,
+ "delta_updates": 1,
+ "rows_loaded": 1000,
+ "rows_indexed": 1000
+ },
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "seconds": 0.397062,
+ "checkpoint": {
+ "saved": true,
+ "rows_indexed": 1515,
+ "sha256": "a0f6f97217f4f78ae7eb62c5a74ac3c9e44d2bc99b2a99cd0570725c40e2530b"
+ }
+ },
+ "preparation_index_seconds": 0.581109166960232,
+ "bundle_definitions": {
+ "tag10_then10_filter20_http_total": [
+ "first10_revision_preflight",
+ "first10_save",
+ "second10_revision_preflight",
+ "second10_save",
+ "filter20"
+ ],
+ "cell_tag_then_cell_filter_http_total": [
+ "cell_save",
+ "filter_cell100"
+ ],
+ "first10_selected_action_http_total": [
+ "first10_revision_preflight",
+ "first10_save"
+ ],
+ "second10_selected_action_http_total": [
+ "second10_revision_preflight",
+ "second10_save"
+ ]
+ },
+ "final_backup_status": {
+ "status": "current",
+ "pending": false,
+ "running": false,
+ "last_success": "2026-09-30T13:55:49.114033+00:00",
+ "last_error": null,
+ "requested_sequence": 34,
+ "completed_sequence": 34
+ },
+ "filter20_cpu_profile": {
+ "top_functions": [
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py",
+ "line": 278,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.958e-06,
+ "cumulative_seconds": 0.067037208
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py",
+ "line": 142,
+ "function": "request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 3.541e-06,
+ "cumulative_seconds": 0.067012666
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1163,
+ "function": "get",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.292e-06,
+ "cumulative_seconds": 0.0661805
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/testing.py",
+ "line": 204,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.3918000000000001e-05,
+ "cumulative_seconds": 0.066175208
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1062,
+ "function": "open",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.9170000000000005e-06,
+ "cumulative_seconds": 0.065905208
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 984,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.071e-05,
+ "cumulative_seconds": 0.06589212500000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/werkzeug/test.py",
+ "line": 1230,
+ "function": "run_wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 6.25e-06,
+ "cumulative_seconds": 0.06570416600000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1529,
+ "function": "__call__",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.6660000000000005e-06,
+ "cumulative_seconds": 0.065677833
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 1479,
+ "function": "wsgi_app",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.1335e-05,
+ "cumulative_seconds": 0.065672167
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 904,
+ "function": "full_dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.5e-06,
+ "cumulative_seconds": 0.065439208
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/app.py",
+ "line": 879,
+ "function": "dispatch_request",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.6666e-05,
+ "cumulative_seconds": 0.065225
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 716,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.6295e-05,
+ "cumulative_seconds": 0.065144542
+ },
+ {
+ "file": "docs/dev/scale-audit-2026-09-29/benchmark_tag_sequence.py",
+ "line": 273,
+ "function": "traced_query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.000147159,
+ "cumulative_seconds": 0.022082337
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 282,
+ "function": "for_epochs",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00010470500000000001,
+ "cumulative_seconds": 0.021718333000000003
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 412,
+ "function": "query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.00010362,
+ "cumulative_seconds": 0.021588791000000003
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/connection.py",
+ "line": 402,
+ "function": "_execute_query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.00011263100000000001,
+ "cumulative_seconds": 0.021325254000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 150,
+ "function": "execute",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 5.9574e-05,
+ "cumulative_seconds": 0.020936163
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/cursors.py",
+ "line": 332,
+ "function": "_query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 6.8911e-05,
+ "cumulative_seconds": 0.020477085000000003
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 575,
+ "function": "query",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 7.4955e-05,
+ "cumulative_seconds": 0.019985668
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 29,
+ "calls": 40,
+ "self_seconds": 3.2416e-05,
+ "cumulative_seconds": 0.019406122
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 249,
+ "function": "native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.1000000000000001e-05,
+ "cumulative_seconds": 0.019327792
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 851,
+ "function": "_read_query_result",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 5.4591000000000006e-05,
+ "cumulative_seconds": 0.019011337
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1288,
+ "function": "read",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 6.941e-05,
+ "cumulative_seconds": 0.018931954
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 128,
+ "function": "deepcopy",
+ "primitive_calls": 80,
+ "calls": 17204,
+ "self_seconds": 0.009455024000000001,
+ "cumulative_seconds": 0.016235503000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 101,
+ "function": "_rows",
+ "primitive_calls": 47,
+ "calls": 47,
+ "self_seconds": 5.5001000000000004e-05,
+ "cumulative_seconds": 0.016185416
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 201,
+ "function": "_deepcopy_list",
+ "primitive_calls": 62,
+ "calls": 102,
+ "self_seconds": 0.000303706,
+ "cumulative_seconds": 0.016048256
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 197,
+ "function": "response_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.2749000000000002e-05,
+ "cumulative_seconds": 0.015181208000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 233,
+ "function": "_attest_contract",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 4.9919e-05,
+ "cumulative_seconds": 0.015154834
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/copy.py",
+ "line": 227,
+ "function": "_deepcopy_dict",
+ "primitive_calls": 1244,
+ "calls": 1286,
+ "self_seconds": 0.002927858,
+ "cumulative_seconds": 0.01413496
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 763,
+ "function": "_read_packet",
+ "primitive_calls": 789,
+ "calls": 789,
+ "self_seconds": 0.0012180140000000001,
+ "cumulative_seconds": 0.014054029000000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 748,
+ "function": "epoch_page",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 2.2208e-05,
+ "cumulative_seconds": 0.012778959000000001
+ },
+ {
+ "file": "python/workspace_service.py",
+ "line": 778,
+ "function": "_epoch_page_identities",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.8595000000000004e-05,
+ "cumulative_seconds": 0.012732667000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 261,
+ "function": "filter_epoch_ids",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.583e-06,
+ "cumulative_seconds": 0.012090208000000002
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 38,
+ "function": "guarded",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.875e-05,
+ "cumulative_seconds": 0.011907166
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 241,
+ "function": "_membership_index",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 5.71e-06,
+ "cumulative_seconds": 0.011763209
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 129,
+ "function": "refresh",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 0.00020048600000000002,
+ "cumulative_seconds": 0.011606417
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 813,
+ "function": "_read_bytes",
+ "primitive_calls": 1578,
+ "calls": 1578,
+ "self_seconds": 0.000562847,
+ "cumulative_seconds": 0.010834381
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 1578,
+ "calls": 1578,
+ "self_seconds": 0.00021465,
+ "cumulative_seconds": 0.010194027000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/socket.py",
+ "line": 704,
+ "function": "readinto",
+ "primitive_calls": 68,
+ "calls": 68,
+ "self_seconds": 7.5758e-05,
+ "cumulative_seconds": 0.009979377000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 132,
+ "function": "__enter__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 2.125e-06,
+ "cumulative_seconds": 0.009972292
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1299,
+ "function": "recv_into",
+ "primitive_calls": 68,
+ "calls": 68,
+ "self_seconds": 8.0783e-05,
+ "cumulative_seconds": 0.009865915000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/ssl.py",
+ "line": 1157,
+ "function": "read",
+ "primitive_calls": 68,
+ "calls": 68,
+ "self_seconds": 4.2368000000000005e-05,
+ "cumulative_seconds": 0.009774166
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 68,
+ "calls": 68,
+ "self_seconds": 0.009726712,
+ "cumulative_seconds": 0.009726712
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/contextlib.py",
+ "line": 141,
+ "function": "__exit__",
+ "primitive_calls": 1,
+ "calls": 4,
+ "self_seconds": 5.8750000000000005e-06,
+ "cumulative_seconds": 0.009363125
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 869,
+ "function": "to_dicts",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 2.3745e-05,
+ "cumulative_seconds": 0.009301708
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 134,
+ "function": "_active_ddl_guard",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 3.5748000000000004e-05,
+ "cumulative_seconds": 0.009012833000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1370,
+ "function": "_read_result_packet",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 5.8379000000000005e-05,
+ "cumulative_seconds": 0.008588792000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/__init__.py",
+ "line": 138,
+ "function": "jsonify",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 8.166000000000001e-06,
+ "cumulative_seconds": 0.007373917000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 189,
+ "function": "response",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.8835000000000002e-05,
+ "cumulative_seconds": 0.007361042000000001
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 61,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 4.5415000000000005e-05,
+ "cumulative_seconds": 0.0072890410000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 1231,
+ "function": "cursor",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 1.7666e-05,
+ "cumulative_seconds": 0.006534542000000001
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 62,
+ "function": "browser_safe",
+ "primitive_calls": 1,
+ "calls": 9641,
+ "self_seconds": 0.003177726,
+ "cumulative_seconds": 0.0057137920000000005
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 66,
+ "function": "",
+ "primitive_calls": 1,
+ "calls": 1363,
+ "self_seconds": 0.001429546,
+ "cumulative_seconds": 0.005712
+ },
+ {
+ "file": "python/workspace_api.py",
+ "line": 68,
+ "function": "",
+ "primitive_calls": 2,
+ "calls": 102,
+ "self_seconds": 0.000199669,
+ "cumulative_seconds": 0.0057019160000000004
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1416,
+ "function": "_read_rowdata_packet",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.000349005,
+ "cumulative_seconds": 0.005556293
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 799,
+ "calls": 799,
+ "self_seconds": 0.005008702,
+ "cumulative_seconds": 0.005354585
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 173,
+ "function": "_rows",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.0627e-05,
+ "cumulative_seconds": 0.00450025
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 327,
+ "function": "_update_summary",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.5377000000000002e-05,
+ "cumulative_seconds": 0.004307959
+ },
+ {
+ "file": "python/workspace_shared_tag_index.py",
+ "line": 305,
+ "function": "_replace_summary_cell",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 0.00015071300000000002,
+ "cumulative_seconds": 0.004222375
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 268,
+ "function": "_native_read",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 2.8914e-05,
+ "cumulative_seconds": 0.004093291000000001
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 311,
+ "function": "native_context",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 3.4795e-05,
+ "cumulative_seconds": 0.004037625
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1429,
+ "function": "_read_row_from_packet",
+ "primitive_calls": 438,
+ "calls": 438,
+ "self_seconds": 0.0010305000000000002,
+ "cumulative_seconds": 0.003435224
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/connections.py",
+ "line": 1448,
+ "function": "_get_descriptions",
+ "primitive_calls": 57,
+ "calls": 57,
+ "self_seconds": 0.000323589,
+ "cumulative_seconds": 0.0029532900000000003
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 125,
+ "function": "_observer_status",
+ "primitive_calls": 8,
+ "calls": 8,
+ "self_seconds": 2.7752e-05,
+ "cumulative_seconds": 0.002938459
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 898,
+ "function": "",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 6.927800000000001e-05,
+ "cumulative_seconds": 0.0027388340000000003
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 163,
+ "function": "read_length_coded_string",
+ "primitive_calls": 2954,
+ "calls": 2954,
+ "self_seconds": 0.000897393,
+ "cumulative_seconds": 0.002704488
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 899,
+ "function": "",
+ "primitive_calls": 106,
+ "calls": 106,
+ "self_seconds": 0.00044013,
+ "cumulative_seconds": 0.002357042
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 188,
+ "function": "_validated_tags",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 9.058300000000001e-05,
+ "cumulative_seconds": 0.002353705
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 115,
+ "function": "_triggers",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.001e-06,
+ "cumulative_seconds": 0.0023289170000000002
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 210,
+ "function": "_index_records",
+ "primitive_calls": 21,
+ "calls": 21,
+ "self_seconds": 9.875000000000001e-06,
+ "cumulative_seconds": 0.0022447500000000002
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 107,
+ "function": "_ddl",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 2.3668e-05,
+ "cumulative_seconds": 0.0021440820000000003
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 101,
+ "function": "selected",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 1.4539000000000001e-05,
+ "cumulative_seconds": 0.002080916
+ },
+ {
+ "file": "python/workspace_curation.py",
+ "line": 170,
+ "function": "_rows",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.208e-06,
+ "cumulative_seconds": 0.001973
+ },
+ {
+ "file": "python/workspace_protocol_state.py",
+ "line": 185,
+ "function": "_metadata",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.9583000000000002e-05,
+ "cumulative_seconds": 0.0019508750000000001
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 195,
+ "function": "_chips",
+ "primitive_calls": 66,
+ "calls": 66,
+ "self_seconds": 4.0998e-05,
+ "cumulative_seconds": 0.0018830820000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 282,
+ "function": "__and__",
+ "primitive_calls": 16,
+ "calls": 16,
+ "self_seconds": 1.2001e-05,
+ "cumulative_seconds": 0.001850499
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/expression.py",
+ "line": 178,
+ "function": "restrict",
+ "primitive_calls": 16,
+ "calls": 16,
+ "self_seconds": 9.476600000000001e-05,
+ "cumulative_seconds": 0.001838498
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 183,
+ "function": "dumps",
+ "primitive_calls": 12,
+ "calls": 12,
+ "self_seconds": 2.5710000000000003e-05,
+ "cumulative_seconds": 0.0017090010000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 183,
+ "function": "encode",
+ "primitive_calls": 12,
+ "calls": 12,
+ "self_seconds": 2.3246e-05,
+ "cumulative_seconds": 0.001673584
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 232,
+ "function": "__init__",
+ "primitive_calls": 180,
+ "calls": 180,
+ "self_seconds": 9.029100000000001e-05,
+ "cumulative_seconds": 0.0016574640000000002
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/encoder.py",
+ "line": 205,
+ "function": "iterencode",
+ "primitive_calls": 10,
+ "calls": 10,
+ "self_seconds": 0.0016462080000000001,
+ "cumulative_seconds": 0.0016462080000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 37319,
+ "calls": 37319,
+ "self_seconds": 0.0016019430000000002,
+ "cumulative_seconds": 0.0016019430000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 236,
+ "function": "_parse_field_descriptor",
+ "primitive_calls": 180,
+ "calls": 180,
+ "self_seconds": 0.00034088600000000004,
+ "cumulative_seconds": 0.0015439700000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/flask/json/provider.py",
+ "line": 166,
+ "function": "dumps",
+ "primitive_calls": 1,
+ "calls": 1,
+ "self_seconds": 7.209000000000001e-06,
+ "cumulative_seconds": 0.001529834
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 223,
+ "function": "attributes",
+ "primitive_calls": 1283,
+ "calls": 1283,
+ "self_seconds": 0.000487523,
+ "cumulative_seconds": 0.0015139060000000002
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/condition.py",
+ "line": 278,
+ "function": "make_condition",
+ "primitive_calls": 16,
+ "calls": 78,
+ "self_seconds": 0.000350976,
+ "cumulative_seconds": 0.001469749
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 34,
+ "function": "text",
+ "primitive_calls": 556,
+ "calls": 556,
+ "self_seconds": 0.00035400300000000003,
+ "cumulative_seconds": 0.0014572340000000002
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 40,
+ "function": "tags",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 9.286e-05,
+ "cumulative_seconds": 0.0013511250000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/heading.py",
+ "line": 285,
+ "function": "__getitem__",
+ "primitive_calls": 966,
+ "calls": 966,
+ "self_seconds": 0.000188833,
+ "cumulative_seconds": 0.0013092280000000002
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 597,
+ "calls": 597,
+ "self_seconds": 0.000341934,
+ "cumulative_seconds": 0.001279032
+ },
+ {
+ "file": "python/workspace_annotations.py",
+ "line": 42,
+ "function": "",
+ "primitive_calls": 86,
+ "calls": 86,
+ "self_seconds": 0.000151275,
+ "cumulative_seconds": 0.0012389220000000002
+ },
+ {
+ "file": "python/workspace_explorer.py",
+ "line": 60,
+ "function": "protocol_binding_header",
+ "primitive_calls": 3,
+ "calls": 3,
+ "self_seconds": 1.1916e-05,
+ "cumulative_seconds": 0.0012270410000000001
+ },
+ {
+ "file": "~",
+ "line": 0,
+ "function": "",
+ "primitive_calls": 22,
+ "calls": 22,
+ "self_seconds": 0.001195543,
+ "cumulative_seconds": 0.001213207
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 344,
+ "function": "token",
+ "primitive_calls": 7,
+ "calls": 7,
+ "self_seconds": 2.2667e-05,
+ "cumulative_seconds": 0.0011970000000000001
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/__init__.py",
+ "line": 299,
+ "function": "loads",
+ "primitive_calls": 112,
+ "calls": 112,
+ "self_seconds": 7.344400000000001e-05,
+ "cumulative_seconds": 0.0011902990000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/pymysql/protocol.py",
+ "line": 61,
+ "function": "read",
+ "primitive_calls": 2954,
+ "calls": 2954,
+ "self_seconds": 0.001006857,
+ "cumulative_seconds": 0.001134061
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 356,
+ "function": "_fresh_token",
+ "primitive_calls": 2,
+ "calls": 2,
+ "self_seconds": 1.5918000000000002e-05,
+ "cumulative_seconds": 0.001131958
+ },
+ {
+ "file": "python/workspace_state_generation.py",
+ "line": 308,
+ "function": "_scope",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 9.457000000000001e-06,
+ "cumulative_seconds": 0.001090084
+ },
+ {
+ "file": "/Users/maxwellsdm/.local/share/uv/python/cpython-3.11.13-macos-aarch64-none/lib/python3.11/json/decoder.py",
+ "line": 332,
+ "function": "decode",
+ "primitive_calls": 112,
+ "calls": 112,
+ "self_seconds": 0.000109259,
+ "cumulative_seconds": 0.0010673310000000001
+ },
+ {
+ "file": ".rieke-runtime/venv/lib/python3.11/site-packages/datajoint/condition.py",
+ "line": 450,
+ "function": "",
+ "primitive_calls": 4,
+ "calls": 4,
+ "self_seconds": 2.2419e-05,
+ "cumulative_seconds": 0.001064625
+ }
+ ],
+ "native_sql_calls": [
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0008224169723689556
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004177499795332551
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.0002244589850306511
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004324170295149088
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002460000105202198
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00034125003730878234
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003159170155413449
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00013554200995713472
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00021170801483094692
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.00017387501429766417
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s)",
+ "seconds": 0.0004391249967738986
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0011619580327533185
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00034908298403024673
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00017716700676828623
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00039437500527128577
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.0002222080365754664
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003476249985396862
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.00029154104413464665
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014645897317677736
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00022441701730713248
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004016250022687018
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0002443749690428376
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00019220798276364803
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00023870798759162426
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00031420798040926456
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.0003042079624719918
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT target_kind,target_uuid,profile_uuid,last_generation,deleted FROM `recording_workspace`.`app_annotation_changes` WHERE project_uuid=%s AND last_generation>%s ORDER BY last_generation LIMIT %s",
+ "seconds": 0.0006496249698102474
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( ( (`target_kind`='?')AND(`target_uuid`='?')AND(`p",
+ "seconds": 0.0010487079853191972
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`epoch_uuid`,`included`,`tags`,`review_state`,`revision`,`metadata_fingerprint` FROM `recording_workspace`.`curation` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))AND( ( (`epoch_uuid`='?') OR (",
+ "seconds": 0.0009846669854596257
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`dataset_uuid`,`created_at`,`artifact_sha256` FROM `recording_workspace`.`dataset_revision` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.00029666698537766933
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.001681207970250398
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`target_kind`,`target_uuid`,`profile_uuid`,`tags`,`author_name`,`revision`,`updated_at` FROM `recording_workspace`.`shared_annotation` WHERE ( (`project_uuid`='?'))AND( (`target_kind`='?'))AND( ( (`target_uuid`='?') OR",
+ "seconds": 0.0005199589650146663
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.0004214160144329071
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT scope_epoch,generation FROM `recording_workspace`.`app_state_generation` WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s",
+ "seconds": 0.00019158399663865566
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`protocol_uuid`,`revision_uuid`,`version`,`bound_at` FROM `recording_workspace`.`protocol_binding` WHERE ( (`project_uuid`='?')AND(`protocol_uuid`='?'))",
+ "seconds": 0.00025212502805516124
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT `project_uuid`,`source_sha256`,`version`,`frozen`,`archived`,`query_excluded`,`updated_at`,`actor`,`actor_kind`,`server_actor`,`reason` FROM `recording_workspace`.`data_store_state` WHERE ( (`project_uuid`='?'))",
+ "seconds": 0.0003397500258870423
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0004516249755397439
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00033424998400732875
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00019454199355095625
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.0004230000195093453
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00022062502102926373
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.000354000017978251
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0003399999695830047
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00014575000386685133
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.00023795902961865067
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id",
+ "seconds": 0.0002008330193348229
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s,%s,%s)",
+ "seconds": 0.00044020795030519366
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s",
+ "seconds": 0.0011610409710556269
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.0003744580317288637
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('?','?','?')",
+ "seconds": 0.00017733301501721144
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
+ "seconds": 0.00036791700404137373
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads WHERE TYPE='?' AND PROCESSLIST_ID IS NOT NULL",
+ "seconds": 0.00020820798818022013
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00031454197596758604
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1",
+ "seconds": 0.0002621659659780562
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1",
+ "seconds": 0.00012816698290407658
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current WHERE EVENT_NAME IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) AND END_EVENT_ID IS NULL LIMIT 1",
+ "seconds": 0.0001967499847523868
+ },
+ {
+ "thread": "filter",
+ "sql_shape": "SHOW GLOBAL STATUS WHERE Variable_name IN ('?','?','?','?','?','?','?','?','?','?','?','?','?','?')",
+ "seconds": 0.00034550001146271825
+ }
+ ],
+ "limits": "Separate instrumented filter; background mirror flushed before profile. CPU cumulative times overlap."
+ },
+ "same_server_checkpoint_reopen": {
+ "seconds": 0.4417761249933392,
+ "checkpoint": {
+ "status": "ready",
+ "phases": {
+ "indexing_shared_annotations": {
+ "seconds": 0.008816
+ },
+ "preparing_tag_filters": {
+ "seconds": 0.008892
+ },
+ "preparing_tag_summary": {
+ "seconds": 0.008728
+ },
+ "preparing_tag_suggestions": {
+ "seconds": 0.018307
+ },
+ "indexing_dataset_tags": {
+ "seconds": 0.066259
+ },
+ "preparing_protocol_reads": {
+ "seconds": 0.019723
+ }
+ },
+ "project_uuid": "e615364e-4a3d-48cc-a519-6bd387785153",
+ "epochs": 500,
+ "cells": 5,
+ "metadata_generation": "tag-workflow-fixture-v1",
+ "phase": "complete",
+ "shared_index": {
+ "full_builds": 1,
+ "delta_updates": 27,
+ "rows_loaded": 1745,
+ "rows_indexed": 1515
+ },
+ "dataset_index": {
+ "full_builds": 1,
+ "delta_updates": 2,
+ "rows_loaded": 1000,
+ "rows_indexed": 1000
+ },
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "seconds": 0.150287,
+ "checkpoint": {
+ "saved": true,
+ "rows_indexed": 1515,
+ "sha256": "9a9ca20661b02f53848719b37ddc2bf9cc5a4cc2c9f23e81450d3b5b172c5885"
+ }
+ },
+ "reopened": {
+ "status": "ready",
+ "phases": {
+ "indexing_shared_annotations": {
+ "seconds": 0.007812
+ },
+ "preparing_tag_filters": {
+ "seconds": 0.008267
+ },
+ "preparing_tag_summary": {
+ "seconds": 0.008579
+ },
+ "preparing_tag_suggestions": {
+ "seconds": 0.016176
+ },
+ "indexing_dataset_tags": {
+ "seconds": 0.029758
+ },
+ "preparing_protocol_reads": {
+ "seconds": 0.017077
+ }
+ },
+ "project_uuid": "e615364e-4a3d-48cc-a519-6bd387785153",
+ "epochs": 500,
+ "cells": 5,
+ "metadata_generation": "tag-workflow-fixture-v1",
+ "phase": "complete",
+ "shared_index": {
+ "full_builds": 1,
+ "delta_updates": 27,
+ "rows_loaded": 1745,
+ "rows_indexed": 1515
+ },
+ "dataset_index": {
+ "full_builds": 1,
+ "delta_updates": 2,
+ "rows_loaded": 1000,
+ "rows_indexed": 1000
+ },
+ "protocols_prepared": 2,
+ "protocols_deferred": 0,
+ "seconds": 0.105382,
+ "checkpoint_restore": {
+ "restored": true,
+ "rows_indexed": 1515,
+ "sha256": "9a9ca20661b02f53848719b37ddc2bf9cc5a4cc2c9f23e81450d3b5b172c5885"
+ }
+ },
+ "full_builds_before": 1,
+ "full_builds_after": 1,
+ "scope": "Shared derived index closed and reopened within the same native server; not a native process restart."
+ },
+ "workflow_peak_rss_before_durable_oracle_bytes": 171819008,
+ "durable_snapshot_verify_seconds": 0.031360624998342246,
+ "loaded_product_modules": {
+ "workspace_tree": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree.py",
+ "workspace_predicates": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_predicates.py",
+ "workspace_cache_lifecycle": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_cache_lifecycle.py",
+ "workspace_metadata_objects": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_metadata_objects.py",
+ "workspace_disk_index": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_disk_index.py",
+ "workspace_catalog_identity": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_catalog_identity.py",
+ "recording_workspace": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/recording_workspace.py",
+ "workspace_recipes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recipes.py",
+ "workspace_tree_code": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree_code.py",
+ "workspace_service": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_service.py",
+ "workspace_tree_pages": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tree_pages.py",
+ "workspace_projects": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_projects.py",
+ "workspace_mysql_profile": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mysql_profile.py",
+ "workspace_native_mysql": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_native_mysql.py",
+ "workspace_mysql_runtime": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mysql_runtime.py",
+ "workspace_storage": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_storage.py",
+ "workspace_diff": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_diff.py",
+ "workspace_protocol_identity": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_protocol_identity.py",
+ "workspace_import_check": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_import_check.py",
+ "workspace_audit": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_audit.py",
+ "workspace_api": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_api.py",
+ "workspace_state_snapshot": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_snapshot.py",
+ "workspace_desktop_paths": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_desktop_paths.py",
+ "workspace_startup_registry": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_startup_registry.py",
+ "workspace_curation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_curation.py",
+ "workspace_explorer": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_explorer.py",
+ "workspace_datastores": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_datastores.py",
+ "workspace_suggestions": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_suggestions.py",
+ "workspace_tag_predicates": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tag_predicates.py",
+ "workspace_export_folder": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_export_folder.py",
+ "workspace_import_progress": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_import_progress.py",
+ "workspace_author_preferences": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_author_preferences.py",
+ "workspace_annotations": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotations.py",
+ "workspace_project_preferences": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_project_preferences.py",
+ "workspace_protocol_state": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_protocol_state.py",
+ "workspace_state_generation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_state_generation.py",
+ "workspace_project_servers": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_project_servers.py",
+ "workspace_launcher": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_launcher.py",
+ "workspace_app_routes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_app_routes.py",
+ "workspace_folder_browser": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_folder_browser.py",
+ "workspace_h5_inbox": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_h5_inbox.py",
+ "workspace_tag_exchange": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_tag_exchange.py",
+ "workspace_external_tags": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_external_tags.py",
+ "workspace_qc": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_qc.py",
+ "workspace_search": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_search.py",
+ "workspace_matlab_masks": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab_masks.py",
+ "workspace_matlab_routes": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matlab_routes.py",
+ "workspace_mask_refresh": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_mask_refresh.py",
+ "workspace_matching_epochs": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_matching_epochs.py",
+ "workspace_candidate_exports": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_candidate_exports.py",
+ "workspace_backup_scheduler": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_backup_scheduler.py",
+ "workspace_recovery_store": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recovery_store.py",
+ "workspace_recovery_generation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_recovery_generation.py",
+ "workspace_shared_tag_index": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_shared_tag_index.py",
+ "workspace_annotation_preparation": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotation_preparation.py",
+ "workspace_annotation_checkpoint": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_annotation_checkpoint.py",
+ "workspace_shared_vocabulary": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_shared_vocabulary.py",
+ "workspace_curation_vocabulary": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_curation_vocabulary.py",
+ "workspace_lifecycle": "/Users/maxwellsdm/Documents/GitHub/epicTreeGUI/python/workspace_lifecycle.py"
+ },
+ "owned_native_runtime_stopped": true,
+ "source_inventory_after": {
+ "python/__init__.py": "6631251543e036e4b9bb74daabaacd0421877388e0900e12da92c464fd1127cb",
+ "python/benchmark_workspace_metadata.py": "34396f00dbf9125ee040c30b9be86ca42b03a0f9f0d8280335b352ad73a9a0c0",
+ "python/export_mat.py": "bb7179baefb9f5d09d593769747fe91b3e62702833cd46790d1b15bad4b16fe2",
+ "python/field_mapper.py": "7d02cd79bbb73f84e513ddda4436bb9fa4601877b7794f8fbf2a694d3321609b",
+ "python/import_ugm.py": "84efca3868208c062710acc1bbc426ee9512c7eb0a90ea5acbf463db07d06f6c",
+ "python/manage_workspace_storage.py": "3a9a30b0ae971031b6b6c082676cd801bbb0218c6045452b25d4ef9ca68d5ebe",
+ "python/query_protocol_workspace.py": "a09054aa63228effbd54a60cb479dd4431d16b46650d844ec92e5775347d329f",
+ "python/query_workspace_export.py": "9397c79664375f32a438619ca6d3654225aec5dc03a9847ce37afa99e475aad6",
+ "python/recording_workspace.py": "ad7adbf587069f60d06779def9f5f6b5e2848a6d43750daaee587c8fdaa1aa9f",
+ "python/snapshot_protocol_workspace.py": "4e0b01ba212b8862f6f63e54064144e8108a8fe76978945b9842cb65d70052b9",
+ "python/tests/__init__.py": "814e202d031e0f73ca168aa5c712a3b7e0f1a8f2cd8ddc9a64e3d7ec987b9ac2",
+ "python/tests/test_desktop_release.py": "d35ae42a5b649ae02cd71474de2361a4ff51c251987cae2d65e9e09d9ea42e0f",
+ "python/tests/test_desktop_runtime_audit.py": "60b96a141e90e6f61f36b6c038cf8bf3464a643a92170964304d3cb78d8b5ef4",
+ "python/tests/test_desktop_runtime_packaging.py": "e2023fd3f7175e442968b9632845b38fa9d1f5659662286c0c52ae752de64876",
+ "python/tests/test_desktop_test_release.py": "912c946581e5668c69c44a1c7824bef1b2fdd7304b50d42a227c1b25b4e03e08",
+ "python/tests/test_export.py": "0814aa8279dbbb4a0adbaa7a624c70066699236867c422169351f36bc28b655f",
+ "python/tests/test_recording_workspace.py": "e1caa26a9b3d54193d6594f89f9abd0b04b47afdfa5bae494a260e4120188cfa",
+ "python/tests/test_workspace_annotation_density.py": "66d66d68054719f5b9e14cb2024146862d5883388cc7afececc9050ccac96af4",
+ "python/tests/test_workspace_annotation_preparation.py": "e8b62c2d3dfda9969ccc7e1697338a3141209a06ae4b18633772091967b795cd",
+ "python/tests/test_workspace_annotations.py": "0c7a411dcc2a54c4ae2742afcc61aa6e8c0ed43c4c83d3d568b8a5803201764f",
+ "python/tests/test_workspace_api.py": "31c2647b0bebe8e62b37a939d8d53cfbb19398c25f578c371ffef41c763fb935",
+ "python/tests/test_workspace_app_routes.py": "884434fedf25b1b82a94944f84c00e9fad9b81ddb4c56fcdaab8ac22b7a216a5",
+ "python/tests/test_workspace_audit.py": "2366c63b57422b0e5a77cb4e01aab752a368b668568576c2a539138c3b425a27",
+ "python/tests/test_workspace_author_preferences.py": "eb28fccb9e18d769c18635797d229227717b617cb31e30d12de499b7538e2b5c",
+ "python/tests/test_workspace_backend_responsiveness.py": "352017c0ea1a90be0597758578340847fe736c7f6d2c906b2d5bc6a57f118656",
+ "python/tests/test_workspace_backup_scheduler.py": "47d06824147bafb64b4b9c6741cc1465a421e6be7c6f058732e63c733a6a7a9f",
+ "python/tests/test_workspace_bootstrap.py": "a781c66702903781dcb733abfdf6aa9348fe0994e8ba92e8ef9c863d1f568b38",
+ "python/tests/test_workspace_cache_lifecycle.py": "2d50c23f856a365b1beaee8ee7d2aaa563e5ecb3e8dfbe121e8838e0fcd90494",
+ "python/tests/test_workspace_cache_publication.py": "cf3194e38d97fa89c351654e9ecddd69990eac7c5c82f9fb85631ff680e5f558",
+ "python/tests/test_workspace_candidate_exports.py": "0ca17b614df4ba8258e1fc8ae111d13358316debd6b2e13122a8361ff401a904",
+ "python/tests/test_workspace_catalog_collision.py": "6f9639e8e083399b30d0db61f5d784c21c295e4cf18dbb195776a478be157251",
+ "python/tests/test_workspace_catalog_identity.py": "8c5cfceeb6a6d7dffb34650a0a0061ea0c5efac33aa615a298d038afffa5463b",
+ "python/tests/test_workspace_catalog_optimization.py": "c5fdfdeb629d9f31a94fc04a49f27bee36e03a0a87c3f02d9cf370232de2d8b4",
+ "python/tests/test_workspace_combinations.py": "8be5d4ba2aedde78c1b01fe7a0495b5ce22862a4cdaf8a6f7ef39bb564a8bf26",
+ "python/tests/test_workspace_compact_explorer.py": "9073fab984393fe9b33af6971f7164ce287ab335e6cfaae58816fa667796073b",
+ "python/tests/test_workspace_curation.py": "9bff9716ec32af3a43176cfac0d38640469b9c88da17e2f97ccaedbea2fd209f",
+ "python/tests/test_workspace_curation_batch.py": "33c4b53abbac16401914f649ca5d9dab16d2b9bf89424253e8f9eeac7669dd13",
+ "python/tests/test_workspace_datastore_migration.py": "232f95f1c177b768643d73a9e5dd332344928b381374a374e663b6672eddc888",
+ "python/tests/test_workspace_datastores.py": "2db26628554f0a61af9122fef079099d65c9700400d928402026f29eb15ffd1c",
+ "python/tests/test_workspace_desktop.py": "db14aaa6c1cf1920a52f340028407e0d66222c439feecad4af9c21faabf095be",
+ "python/tests/test_workspace_desktop_paths.py": "be12fb413ea6e2f3777f440c158e29480719991c6987433e53b14ac5865d08d6",
+ "python/tests/test_workspace_diff.py": "ece3af5b165588361c6d23be32c2706f5a96c95242db0f7542a4579b46e3c2f7",
+ "python/tests/test_workspace_disk_index.py": "ae4c272cc0faa24885f9a4bb7282e43a3c6a892da04cf8d096136010b49d6736",
+ "python/tests/test_workspace_disk_index_crash.py": "14f8fb2ae81180f888aef4246893c04f4528c7870c1167835f6b7911ea79f5f8",
+ "python/tests/test_workspace_epoch_page_performance.py": "e21ee24baee1f473273503e51d1e92b80e95083dd463a3967860e90cadff4462",
+ "python/tests/test_workspace_export_reader.py": "ee260e95c19c4184c07f66633876bbd889abea14c8065cc17e8e8f253b408bbd",
+ "python/tests/test_workspace_external_tags.py": "25389f6868db35877a9a7564c9be32f786684c1cdac88ed43f8e1a2881abc807",
+ "python/tests/test_workspace_folder_browser.py": "1f111e4e2938b402be70ecd05849002cf2def5e7fb9c2470aa54928e635ccf9b",
+ "python/tests/test_workspace_frontend.py": "086ce8b19abaca47511f123f482072d40f48862ebe0c5361b1d48bc016f1f3e0",
+ "python/tests/test_workspace_generation_portability.py": "31cf7f52d275a9892366aa9e54d66ff12e0be2f70967722d3c179ef4b7f0f3b2",
+ "python/tests/test_workspace_h5_inbox.py": "ebb20a6daf67e245cfdd89c8abff75634bc6aa82f4bc859636caea1b66b918cc",
+ "python/tests/test_workspace_handoffs.py": "595b55c8343db1cee429914a329c6fb0d16e1b8edc8e4037c651d4e2b3250883",
+ "python/tests/test_workspace_identity_conflicts.py": "85aca1903054f632dc26b5f0d018c4b6e1163b848bcef1b740c2735de6eece7a",
+ "python/tests/test_workspace_import_api.py": "96dd94451b324cb157b2c694fac6fa09e449683f6a570e565d12fe483027fc45",
+ "python/tests/test_workspace_import_check.py": "c4b862ed07a803b1a86b22233b93db37c01a6b79d0296f8945960b372e4fc211",
+ "python/tests/test_workspace_import_identities.py": "e62c6c5df029329ae9b42b326a904e75699b068e899627951c7e05a8a5bbf655",
+ "python/tests/test_workspace_import_progress.py": "599a6962aed71928f85edc64daa4383ef07c0e81b1d9aa793a467437c602739d",
+ "python/tests/test_workspace_import_progress_failures.py": "32e838db0652c4c01f557e45ca54813ba34564bf7577f44a3e90b4fdb4bad88d",
+ "python/tests/test_workspace_import_worker_lease.py": "bc790683e5005027e2931e5c825a9fe64ac738f6931214315e68f32527e9b8cb",
+ "python/tests/test_workspace_installation.py": "e455e788040ce4f5f05a2dbf1e3c764f675aa6d6ff0a6ff4b2fc450b3b5e483e",
+ "python/tests/test_workspace_lifecycle.py": "e004025f8124fb8281edd222087c08399927763f8d1638240320b6107b570773",
+ "python/tests/test_workspace_mask_refresh.py": "a16cf823fab7a30b18b31489951d4844d54b4904552a70910f97209e2583c8ff",
+ "python/tests/test_workspace_matching_epochs.py": "162460c2a57676a32a9f703a4da9fe296f6e2be651c61893eed9e2244e562225",
+ "python/tests/test_workspace_matlab.py": "57be7801220f1a5ded314f402992002d78e84ddab608206887655c0018b971a9",
+ "python/tests/test_workspace_matlab_masks.py": "9f8e9819bc4322ed2b59218936a637c56b3aa256eb3dce8b8dc8683cde1dbab1",
+ "python/tests/test_workspace_matlab_routes.py": "740f681ad4fb7967eecfc47d3de8a50daa461b8c093ade901533d5657a6f33ce",
+ "python/tests/test_workspace_metadata_object_contract.py": "3694f9ffdbf19f96f4451efacc704bba837042fc80450d35d96e564dc068c6c1",
+ "python/tests/test_workspace_metadata_objects.py": "59a82e532d17d786a65c6a89d5ad8073920c9549f0429c1dc915c263b1b60f51",
+ "python/tests/test_workspace_metadata_refresh_api.py": "56b0858dbb422a5599411bd7a97c2b99f2368a8cd660f94bf6dd1aac9c0612a5",
+ "python/tests/test_workspace_migration.py": "2cac6f1ec33df1b7d7ef24b58f0ab6fb71730a12c5c6acf28c35f822e6aaed78",
+ "python/tests/test_workspace_mysql_runtime.py": "4b08894222ae6b85bfe39585927dae739bb00445f7d7f3f7e5d79f905a40b32f",
+ "python/tests/test_workspace_native_connections.py": "6d8b6288577772fb126f4c46fb7bf0c0fa1df6ebba4ae6df113e01492db96892",
+ "python/tests/test_workspace_native_crash.py": "f5419b10632abb0fbd19712e246851438e0520c76d09b370662db4ae306f9fa7",
+ "python/tests/test_workspace_native_mysql.py": "eb090deaadc98bfa921bc017b43de780512a6b4c35a909a75fd6b42f19966858",
+ "python/tests/test_workspace_open_folder.py": "900c8c175f88ad404c6201ebcd7b71ca81ebda7059c3c3656eed5a3a88d989ad",
+ "python/tests/test_workspace_portability.py": "e907ea424e151e77191f53de2cef625789bf6a85d92c7e8b57a5c24aafe88f61",
+ "python/tests/test_workspace_portability_app.py": "e4a66a59c5890ab0770ffac6401ac5b202e11081238aa436c09700713f676f7a",
+ "python/tests/test_workspace_predicates.py": "3e00510d5d3cf76f13bfc679c88635bfb917e1077b5c9bd02649a285da2cf7c6",
+ "python/tests/test_workspace_project_preferences.py": "52e7a3574d45efee8c89657e46b3a057879ada4ef895fbc5f0c02856b530dbd2",
+ "python/tests/test_workspace_project_relocation.py": "79882c510136ca6f4c90e81735ed44b8a6e2a61e5c09bc114b04ae4310f0dd89",
+ "python/tests/test_workspace_project_servers.py": "a9aa45c943854e84c2ffcd7ae17d7b1a2f1f084643881e29678432a1dfd72bb5",
+ "python/tests/test_workspace_project_validation.py": "572107975e2240615345bca7e6165f1357f148b11ab3a89e163d55f6d53bd1d1",
+ "python/tests/test_workspace_projection_cache.py": "558a21a3642ee04242252fbc52541f55a81b23bcd912fc2e7ee2f902cdf0ebdb",
+ "python/tests/test_workspace_projection_objects.py": "0bed3c4a8da2321e37da11e40031530d284f9837ed64ff3b0057193b686a9e17",
+ "python/tests/test_workspace_projects.py": "32c2dd9c73d2f40ab09522b717d75bb026ba3648f19d5c191b66b9e9fd945b3e",
+ "python/tests/test_workspace_propagation.py": "d5a7789eebffffe753765886084924073d3ef0fae7ae229415cbe60a5e1e79c6",
+ "python/tests/test_workspace_protocol_binding.py": "7519a925051a7d86c37ff4003e696c55ed384a83ef3f690dbaebbd98aedc59f6",
+ "python/tests/test_workspace_protocol_state.py": "67d57fb3c6f37804573dfeb3d94b7c1f74ffef41536344e1e0f3f16f9f04ce08",
+ "python/tests/test_workspace_qc.py": "ff3875c9233271834a4320d1d23844cf5f0539a6b0973157f03dbf1118c66d39",
+ "python/tests/test_workspace_recipes.py": "a0aa7499dccae4e1d66b700bcfcb222110568af2785ca2a2a3e46edf1d25da58",
+ "python/tests/test_workspace_recovery_generation.py": "0bca8f152148f7039b6863c54f4264359783c7b82cb363c51bc4010c257c656a",
+ "python/tests/test_workspace_recovery_store.py": "520fb79262eb814665db6eb942b38514a147de1a4cf665d5b4fe4d8a12a56ef9",
+ "python/tests/test_workspace_refresh_cache.py": "e0fcc584585ddd7fd047b092c7fdce7e390d2a48d3a322c94752cf6cb1946e95",
+ "python/tests/test_workspace_scale.py": "f749f206748081a810a41388ff4fedbb18118460eaf347624263eaf21e21d275",
+ "python/tests/test_workspace_search.py": "2f966a4b4ade0a8e6d9ded8eedb8cea1f6841e56b0ec050acbbb4c40642e7838",
+ "python/tests/test_workspace_search_presets.py": "5be3998d5c66f44c4dc9463a2d0e951d22ebb7a449c6f181e2a2994ece86dd4e",
+ "python/tests/test_workspace_service.py": "7afc6c297770800fb90e148ffeb4ad3d5c24c8e84f5a788094f3246f59f97782",
+ "python/tests/test_workspace_shared_tag_cache_dependencies.py": "9a6586732efdd855886ef80f26523e60c476a51b0de80f33f5d3f2a0338ae5dd",
+ "python/tests/test_workspace_shared_tag_index.py": "388a786b5e1be76dbdd5daffe649da8fec73045f99e92756c41765674b766b09",
+ "python/tests/test_workspace_shared_tag_threads.py": "40a756caedd2dbf035a966f5711b610bca98546404aba26d53a6492655468f6e",
+ "python/tests/test_workspace_shared_vocabulary.py": "9d3a764074be9b89441279aa23eb6c7d7a5a8d077578ea1d282d0a7c9a8e7892",
+ "python/tests/test_workspace_source_identity.py": "0ddf442ffefd00e16441868e89d5785ee4b302ffbb39ed6d0f17c9825d70c3ab",
+ "python/tests/test_workspace_sqlite.py": "863ac4ce766def86d881f13d3e3cf9456a3bf6e649c90ab486e3ab9b68994433",
+ "python/tests/test_workspace_startup.py": "33aef6fad6f4e3e712e8b25d2738f43705aa9e33cc969bd91f626d7acd4204c2",
+ "python/tests/test_workspace_startup_registry.py": "2a22b884ff273685c8ba7ec39f796bd0181358f152a7022c2ce31e382320dfb8",
+ "python/tests/test_workspace_state_generation.py": "9b242c1e9efbcedf724576bd290a99511992744e0fda2f2119de7a8e48f94485",
+ "python/tests/test_workspace_state_snapshot.py": "07af085d7c1deb6c7ad625af8c52d6a79e48b94a866f12a1f4af4b83dba6b4b0",
+ "python/tests/test_workspace_storage.py": "e4cb1e8aa0f78b22688f67687675b20066385dba98866d79c11f530cb1f960de",
+ "python/tests/test_workspace_suggestions.py": "d74f2520953085c8216c12af4186755d11fe39fd81d328f6f58b04d981240489",
+ "python/tests/test_workspace_tag_exchange.py": "6c4e5109813b93fa9906f34d3108c7d90201b79b9ad108a1a0615bd891d4e3e3",
+ "python/tests/test_workspace_tag_lifecycle.py": "7ab917a93c3b530dbcca70dac535fccc76dfddb5325bec44480051454d5001d1",
+ "python/tests/test_workspace_tags.py": "8eb64c890ccf6c6d2556ef50badf337bbe68f157d306ac39bdb05c4137ddbb80",
+ "python/tests/test_workspace_tree.py": "f137c7fb5a5e9206abde92a866fcae5db8eabde6cb29f8de109b41b1b16d3414",
+ "python/tests/test_workspace_tree_code.py": "c2b64c14234c31108944ac3bc36a1dfecec2a8e64fe6d17f1f3c131ebb563893",
+ "python/tests/test_workspace_tree_layouts.py": "215f3660c23b03103ba8ee4ce6ec1fe41e890ab1c51c1ccc26f6d984a53e3046",
+ "python/tests/test_workspace_tree_pages.py": "3ca10d83d1f7837e8c6c5c12868f8cd9d5c1170237ba9897c039a2c05a730f6c",
+ "python/tests/test_workspace_updates.py": "bca29343f41d275c8d640f7ae55929d5659bbcdebc667d707cfd20389b0f33dd",
+ "python/verify_import_progress.py": "167cf6f5935a023d42239be6d3e7fdf8c75d6ee8d8723bc05b2a5b42c66d5865",
+ "python/verify_sept23_handoffs.py": "e20b9a08dfe47e0b7a1845a934d3023e033e4204d90b60c1ee25f72205d7513c",
+ "python/verify_workspace_matlab.py": "cfd8b0ce3d5b04d2010d3864504cf64f41a2da00457614a4724f702a6f6eb566",
+ "python/workspace_annotation_checkpoint.py": "3ee21f6fe2a1052b0c7d01b3bfc94f958a664bf7c131b076f0272f2692610930",
+ "python/workspace_annotation_preparation.py": "49769c7f0b356e085d7e076abe17350e546b59444936d9caecea8236d60699ad",
+ "python/workspace_annotations.py": "a2eb2b9282a226006dab89691b099b73ff9fd12e15985bd548c9a4ef4ab68ee2",
+ "python/workspace_api.py": "b1b429181053bcc3981793ca70b191b00dfd5c0631ed65b94c12aa5bd34ef992",
+ "python/workspace_app_routes.py": "29e2b03d11c1c5454fbbe270be2117afd21bcfca0edb3e66320ff01203e11684",
+ "python/workspace_audit.py": "4b5b033cb59319f4e0277754beaf66eb22788579d8c4452630c18c4e3e7bcc46",
+ "python/workspace_author_preferences.py": "e36bc7d074c6fbed2f63399538a580adb0f749f1a95024bb39e774671f2ef847",
+ "python/workspace_backup_scheduler.py": "a7b955dad598ac197ebba8f3e8a988289f61ac2adf47495849f96c55914e1cbf",
+ "python/workspace_bootstrap.py": "29c2d2cadce647b67d085febd30532d5cac726056a76ebf42c70ea40abdc112a",
+ "python/workspace_cache_lifecycle.py": "f85876806899228ce4cc79fb3aaee7003bc3e5a55bb51b103db2f17b13d9078a",
+ "python/workspace_candidate_exports.py": "0443e20a6fc78387b6a3925b7a588045181ad4d1033fed439ba23f8c19a75706",
+ "python/workspace_catalog_collision.py": "38989ddb1d528289eeacdaa80834bc53a0a61c416bf8a9df5829dc003eb55b28",
+ "python/workspace_catalog_identity.py": "1a18ce3a8702ba30b60ced08e39ed6a25f26f09255a40c26808818e0c5e707f2",
+ "python/workspace_curation.py": "9048064f3ab40a20d97e7cde974cdf1541cbcb627f776a8a20edd03a5557fb90",
+ "python/workspace_curation_vocabulary.py": "2257ff99d2e4543534e7bb4e83d41914b35fb93963631fde021c32943a0f6a0c",
+ "python/workspace_datastores.py": "afc6139a965bdbfad78643a13ee33ed555339235239c9be938c77b48fd1d7e4a",
+ "python/workspace_desktop.py": "fa7c39178b4e7876bdf48d5d076b4afa71906aebfcd80c552566a5a1f1fe2c2e",
+ "python/workspace_desktop_paths.py": "e32d88c4bf4e41301bd917b8c2cd311e840fd1e45aef28142b8d7f2ece7548e7",
+ "python/workspace_diff.py": "7f5a806bd9a325a0cbeeeac4bf4e975efb703cca94f727f028353c9e67604798",
+ "python/workspace_disk_index.py": "bc38a7d0859a0d41edb1fd31b05048211ff4c94a30092003fc1b79b6fa2425a2",
+ "python/workspace_explorer.py": "13d5cc9e1cf73207b88b011f9a11530049e8ac8b04e0321b8a600d5a3a667d91",
+ "python/workspace_export_folder.py": "ca98d97d2b693245a10267a103b292c707b394b8593e6eddf08c6bd9d72c9a7b",
+ "python/workspace_external_tags.py": "6f7cd83c884c6d756dc89f52064692ba46bc40d9caed13660f32213443591a8f",
+ "python/workspace_folder_browser.py": "efa023840cc73547c1ea653edf077a40979952a7700b6718cd620f316d7d51a0",
+ "python/workspace_frontend.py": "731954b0e82b927e9fa5cf9a6134b61a23252283ad54e64a8fd307f116931757",
+ "python/workspace_h5_inbox.py": "5737740344b377b90f7c9d7d09296ff5d66d2cccb558cc287df0283ee3459b02",
+ "python/workspace_import_check.py": "857cacb94ba3b83454b2aa2c32785930e7b3fcd150c1bca3f25d1cca8c643232",
+ "python/workspace_import_progress.py": "4b83f86efbfea8c3fe1194cf59c3e810970059e3ec1bf579adc56472c28c8d09",
+ "python/workspace_installation.py": "8f0787584d7650b58c422f6980592f06411c4071a1c0405cf36eb7bebd05761b",
+ "python/workspace_launcher.py": "f662aaeaa095b59ecc0459643a7eb924537a8199935fa7b7fd7ade77a24324d1",
+ "python/workspace_lifecycle.py": "077e4533455420268714bfb2ac79081edf65fbd540a5bc40ced33a6565ee669a",
+ "python/workspace_mask_refresh.py": "c42948d361060b6a1522255120ed59aa4c76f4cacc6a734e8f8dd1b5277a8546",
+ "python/workspace_matching_epochs.py": "a2157e20908490c06035a2f765e31ec80f348d5081cd046e1deca58829fe225d",
+ "python/workspace_matlab.py": "3dd168a224a48a9bfd4f32e14cc3c46c3d0966e95be5ea9ea62f715f9ee31d34",
+ "python/workspace_matlab_masks.py": "3f0a22f973605d43ce026519a8086b5dc986f0e917c0d1e0e52c39b4aeffdecc",
+ "python/workspace_matlab_routes.py": "4997dd190e181540ae8217f6aa10e307b2cb4c4f810d17ee92dc8306c54322af",
+ "python/workspace_metadata_objects.py": "8cb62575cf0fc6198a7e2d4d51e20d7ff14ad6172200bc917d13b3b2b9a372cd",
+ "python/workspace_migration.py": "06c463bac69caebdb89a8e394e5a5ae0e7c747d07c40a0587a10e83aeb829775",
+ "python/workspace_mysql_profile.py": "1c06e5a679af3bc8ed842aa6bb6aad38f259bcfa97687c774e21c358f481fed0",
+ "python/workspace_mysql_runtime.py": "d03c5e41f6fe37a8d514f6c1b90e92769a171c1348ebee62683c395edd4b4224",
+ "python/workspace_native_mysql.py": "46e2075bd9aa2bf69d2041f06bfdd41305032be2bd8864dfb8c5d0de774e3905",
+ "python/workspace_portability.py": "582a50eebeabea8f546ac829395e3aef72ab544112147dfb7fa409fd9db39d91",
+ "python/workspace_predicates.py": "c8b7a4b44a8c051b0216eaaa939f4278ea591c41772a7ec9af8e8899756aa2bd",
+ "python/workspace_project_database.py": "bfa69cfcef0ba0f566e520e42e07fd31751c410ce7637662b162bae4a55ccbf3",
+ "python/workspace_project_preferences.py": "418fd4543381a235870ca77a6865c0945fa1a456fe749b7da6997863efd19387",
+ "python/workspace_project_servers.py": "ff7fb1aa76b0c0afef6726a7c9d21f2ac779f99e0a3c41141e990716c5acba18",
+ "python/workspace_project_validation.py": "a8478af122534573145b194c9f7a65934e7c83f39b6fbcbd0eb52275effcfd9f",
+ "python/workspace_projection_cache.py": "89d7a7365de9a64f9b85979e20f0b90b869300c42e62c599c8c436e3bfe88f17",
+ "python/workspace_projects.py": "b3d0a1f2c3505e14f249e674574dec5e81594b2049ca48e760dbf5d6d45fa894",
+ "python/workspace_protocol_identity.py": "9a9e3c8426cc9ed58ebd77f4f06e03db2d2287212969ad80e48990fae3e0a986",
+ "python/workspace_protocol_state.py": "fab53296fc4e06de042c9059c61174e55662de3b20c539667de8679135130c6e",
+ "python/workspace_qc.py": "ae2fddb5d70edb6a66b803bb44587c8693f3c03dd44ea2b9bcf9874e4175159c",
+ "python/workspace_recipes.py": "d20f0d8eaf61e0c2d83cb5077ba9671927283f772069e680f2881cd651c52683",
+ "python/workspace_recording_files.py": "c055b81c518b38da1b632b2ac544f3f2dd277fda4fb4f9d496a41ae9624fa50b",
+ "python/workspace_recovery_generation.py": "2579fefde5230c735be19f7ab76d05ae20c1ed70a32a731cd31b0b69460d0ce1",
+ "python/workspace_recovery_store.py": "769f36f82f8a29a2a4c04f8e9accce9fab162d899704d6966b1ae732e6d61804",
+ "python/workspace_search.py": "b9849cdb0d14ade477df96011c0fc0dadce4e045213e2a206afb82bddcb68a71",
+ "python/workspace_search_presets.py": "8ab5c84a507eac16be284a106af8cb2cc8d006ee9c715d787ef5b2f481ab0420",
+ "python/workspace_service.py": "35a10441d0ba59e43c59ad564879b2b73eccc29cd19a2558e3eaf8db1a9f5782",
+ "python/workspace_shared_tag_index.py": "b8cbef326a67f096527463dc2dc9490a3a6fdfbed01b2b199ebe369f8c0ead33",
+ "python/workspace_shared_vocabulary.py": "c196ccc8fcbc1bfb105db7159c286231be2c86ef0bb73317a76f3542b7072590",
+ "python/workspace_sqlite.py": "49eaf5814a98a808accd03d31f7ef9d652f03c353547570ffa83670daa4393b1",
+ "python/workspace_startup_registry.py": "3e5fcce7a6df8fbddceead8326943d9327e691cc1e706f1aa08ed02151c187d0",
+ "python/workspace_state_generation.py": "f5b26e0f77797e9ffae3a26188c1ea0d75f0fe1698ff8d5085c7c892eec7693c",
+ "python/workspace_state_snapshot.py": "94cad9f90d27dcae1fbf0d2a7029b3afc60151eace9fc4aad3be41770e5249a3",
+ "python/workspace_storage.py": "fb5b3b47f08c934999d6c2a678f435e2f475cced24ca54e60f6b5dc2d1650767",
+ "python/workspace_suggestions.py": "0975ecc4704f218516a7c801f1e057804aa9309bd6c02ebae9eafc6452d6e1d9",
+ "python/workspace_tag_exchange.py": "e897918039370b98585b61ab3ff58dbfe564224f9b025aafbdf0250c55c07ea4",
+ "python/workspace_tag_predicates.py": "c2807d6626619ce19ff7b1b83d56a499bd089f79123d71eb92210489368eaa74",
+ "python/workspace_tree.py": "db25f45e60aa2d0f7091168d983f50db0f353cb6c62e2fba30a571416dcb93ea",
+ "python/workspace_tree_code.py": "84698df192726094117e2d633e6604200cc618460c4c1e51b927fe0614f5936e",
+ "python/workspace_tree_layouts.py": "e804f9039acbf86a38ff9b9592a33cabef5f69794c25bcca9e032006a15fa9e0",
+ "python/workspace_tree_pages.py": "dbaa70e3ffe77be07c01e51ffedecc4d9b04221d08133ae2d1dce9b399e0d332",
+ "python/workspace_updates.py": "770a6486f9dcc830ae808428b83929a51785b16e10e2f2c8074474d38cd82d4a"
+ },
+ "source_unchanged": true,
+ "harness_unchanged": true,
+ "seconds": 9.229930458008312,
+ "finished_at": "2026-09-30T13:55:49.458197+00:00",
+ "peak_rss_bytes": 173015040,
+ "passed": true
+}
diff --git a/docs/dev/ui-browse-h5-audit.jpg b/docs/dev/ui-browse-h5-audit.jpg
new file mode 100644
index 0000000..ee18917
Binary files /dev/null and b/docs/dev/ui-browse-h5-audit.jpg differ
diff --git a/docs/dev/ui-open-exports-audit.jpg b/docs/dev/ui-open-exports-audit.jpg
new file mode 100644
index 0000000..961a790
Binary files /dev/null and b/docs/dev/ui-open-exports-audit.jpg differ
diff --git a/python/recording_workspace.py b/python/recording_workspace.py
index c4e1a31..199daa5 100644
--- a/python/recording_workspace.py
+++ b/python/recording_workspace.py
@@ -20,6 +20,8 @@
import traceback
import uuid
+from workspace_catalog_identity import CatalogIdentityConflict, validate_catalog_identity
+
def now():
return dt.datetime.now(dt.timezone.utc).isoformat()
@@ -39,11 +41,14 @@ def digest(path, progress=None, stage="source_hashing"):
def write_json(path, value):
+ from workspace_state_snapshot import atomic_write
+
path = Path(path)
+ # Finish strict serialization before touching the destination. Atomic
+ # replacement alone does not persist the file or its directory entry.
+ data = (json.dumps(value, indent=2, allow_nan=False, default=json_scalar) + "\n").encode('utf-8')
path.parent.mkdir(parents=True, exist_ok=True)
- temporary = path.with_suffix(path.suffix + "." + str(uuid.uuid4()) + ".tmp")
- temporary.write_text(json.dumps(value, indent=2, allow_nan=False, default=json_scalar) + "\n")
- os.replace(temporary, path)
+ atomic_write(path, data, preserve_permissions=True)
def json_scalar(value):
@@ -73,18 +78,17 @@ def epochs(metadata):
yield cell, group, block, epoch
-def assert_new_catalog_identities(experiment, catalog, batch_size=200):
+def assert_new_catalog_identities(experiment, catalog, batch_size=200, *, lookup=None):
"""Reject reused source hierarchy identities before a new file is populated.
Call only in the new-SHA branch while holding the import advisory lock and
transaction. RetinAnalysis's h5_uuid columns are not unique keys; a later
read-model rejection would be too late to prevent ambiguous catalog rows.
- Animal/preparation and response/stimulus identities are outside this guard.
Matching scientific values or repeated trials are not duplicate identities.
"""
if type(batch_size) is not int or not 1 <= batch_size <= 500:
raise ValueError('Identity lookup batches must contain 1–500 identities')
- identities = {name: set() for name in ('Cell', 'EpochGroup', 'EpochBlock', 'Epoch')}
+ identities = {name: set() for name in ('Animal', 'Preparation', 'Cell', 'EpochGroup', 'EpochBlock', 'Epoch', 'Response', 'Stimulus')}
def record(table, item):
identity = item.get('uuid')
@@ -95,7 +99,9 @@ def record(table, item):
identities[table].add(identity)
for animal in experiment['animals']:
+ record('Animal', animal)
for preparation in animal['preparations']:
+ record('Preparation', preparation)
for cell in preparation['cells']:
record('Cell', cell)
for group in cell['epoch_groups']:
@@ -104,15 +110,301 @@ def record(table, item):
record('EpochBlock', block)
for epoch in block['epochs']:
record('Epoch', epoch)
+ for kind, key in (('Response', 'responses'), ('Stimulus', 'stimuli')):
+ for stream in epoch.get(key, {}).values():
+ record(kind, stream)
for name, values in identities.items():
ordered = sorted(values)
table = getattr(catalog, name)
- for start in range(0, len(ordered), batch_size):
- batch = [{'h5_uuid': identity} for identity in ordered[start:start + batch_size]]
- collision = (table & batch).fetch('h5_uuid', limit=1)
- if len(collision):
- raise ValueError(f'Existing {name} source UUID {collision[0]} belongs to the catalog; '
- 'explicit source/version reconciliation is required before importing this different file')
+ collision = None
+ if lookup is not None:
+ collision = lookup.first_collision(name, ordered, batch_size)
+ else:
+ for start in range(0, len(ordered), batch_size):
+ batch = [{'h5_uuid': identity} for identity in ordered[start:start + batch_size]]
+ found = (table & batch).fetch('h5_uuid', limit=1)
+ if len(found):
+ collision = found[0]
+ break
+ if collision is not None:
+ raise CatalogIdentityConflict('source_uuid_collision',
+ f'Existing {name} source UUID {collision} belongs to the catalog; '
+ 'explicit source/version reconciliation is required before importing this different file',
+ identity=collision)
+
+
+def validate_self_contained_h5(h5):
+ """Inspect links/layouts without reading samples or following external files.
+
+ Walk physical hard-linked objects once: Symphony includes experiment/source
+ backlinks. Inspect every hard-link alias's containing group, and resolve soft
+ links only after external links everywhere in this file have been ruled out.
+ """
+ import h5py
+ pending, seen, soft_links = ['/'], set(), []
+ while pending:
+ obj = h5[pending.pop()]
+ address = h5py.h5o.get_info(obj.id).addr
+ if address in seen:
+ continue
+ seen.add(address)
+ if isinstance(obj, h5py.Dataset):
+ if obj.is_virtual:
+ raise ValueError(f'Virtual dataset dependencies are unsupported: {obj.name}')
+ if obj.external:
+ raise ValueError(f'External dataset storage is unsupported: {obj.name}')
+ elif isinstance(obj, h5py.Group):
+ for name in obj:
+ link = obj.get(name, getlink=True)
+ if isinstance(link, h5py.ExternalLink):
+ raise ValueError(f'External H5 links are unsupported: {obj.name}/{name}')
+ if isinstance(link, h5py.SoftLink):
+ soft_links.append(obj.name.rstrip('/') + '/' + name)
+ elif isinstance(link, h5py.HardLink):
+ pending.append(obj.name.rstrip('/') + '/' + name)
+ else:
+ raise ValueError(f'Unsupported H5 link: {obj.name}/{name}')
+ for path in soft_links:
+ target = resolve_sealed_h5_path(h5, path)
+ if h5py.h5o.get_info(target.id).addr not in seen:
+ raise ValueError(f'H5 link target is outside the sealed source: {path}')
+
+
+def resolve_sealed_h5_path(h5, path):
+ """Resolve one locator without following unsealed links (bounded trace path).
+
+ Inspect each soft-link expansion, including its intermediate components;
+ inspecting only the final object's filename misses external links that lead
+ back into this file, virtual mappings and external raw-storage datasets.
+ """
+ import h5py
+ if not isinstance(path, str) or not path.startswith('/'):
+ raise ValueError('H5 locator must be an absolute path within its source')
+ parts, current, expansions = path.split('/')[1:], h5['/'], 0
+ while parts:
+ name, parts = parts[0], parts[1:]
+ if not name or name == '.':
+ continue
+ if name == '..' or not isinstance(current, h5py.Group):
+ raise ValueError(f'Invalid H5 source locator: {path}')
+ link = current.get(name, getlink=True)
+ if isinstance(link, h5py.ExternalLink):
+ raise ValueError(f'External H5 links are unsupported: {path}')
+ if isinstance(link, h5py.SoftLink):
+ expansions += 1
+ if expansions > 64:
+ raise ValueError(f'Unresolvable or cyclic H5 soft link: {path}')
+ target = link.path
+ if not target.startswith('/'):
+ target = current.name.rstrip('/') + '/' + target
+ parts, current = target.split('/')[1:] + parts, h5['/']
+ continue
+ if not isinstance(link, h5py.HardLink):
+ raise ValueError(f'Missing or unsupported H5 source link: {path}')
+ current = current[name]
+ if current.file.id != h5.id:
+ raise ValueError(f'H5 link target is outside the sealed source: {path}')
+ if isinstance(current, h5py.Dataset):
+ if current.is_virtual:
+ raise ValueError(f'Virtual dataset dependencies are unsupported: {path}')
+ if current.external:
+ raise ValueError(f'External dataset storage is unsupported: {path}')
+ return current
+
+
+def _source_text(value):
+ return value.decode('utf-8') if isinstance(value, bytes) else str(value)
+
+
+def validate_source_identity(h5, document):
+ """Compare parser hierarchy to physical Symphony objects, including empties.
+
+ UUID dictionaries are built only after checking physical multiplicity. The
+ source hierarchy is experiment/sources/animal/sources/preparation/sources/cell;
+ epoch groups reference cells by an in-file source link, not by display label.
+ The pinned parser deliberately drops blocks without epochs, so those may be
+ absent from metadata, but still participate in duplicate-identity checks.
+ """
+ import h5py
+ validate_self_contained_h5(h5)
+ roots = [h5[name] for name in h5 if name.startswith('experiment-')]
+ if len(roots) != 1 or not isinstance(roots[0], h5py.Group):
+ raise ValueError('Expected exactly one Symphony experiment root')
+ root = roots[0]
+
+ def identity(obj):
+ if not isinstance(obj, h5py.Group) or 'uuid' not in obj.attrs:
+ raise ValueError(f'Missing source object UUID: {obj.name}')
+ value = _source_text(obj.attrs['uuid'])
+ if not value or len(value) > 255:
+ raise ValueError(f'Invalid source object UUID: {obj.name}')
+ return value
+
+ if identity(root) != document['uuid']:
+ raise ValueError('Experiment identity differs from H5 source')
+ inventory = {kind: {} for kind in ('animal', 'preparation', 'cell', 'group', 'block', 'epoch', 'stream')}
+ parsed = {kind: set() for kind in inventory}
+
+ def children(obj, collection):
+ if collection not in obj:
+ return []
+ container = obj[collection]
+ if not isinstance(container, h5py.Group):
+ raise ValueError(f'Invalid source {collection} collection: {container.name}')
+ return container.items()
+
+ def record(category, obj, parent, **fields):
+ kind = category
+ value = identity(obj)
+ if value in inventory[kind]:
+ raise ValueError(f'Duplicate source {kind} UUID: {value}')
+ inventory[kind][value] = dict(path=obj.name, address=h5py.h5o.get_info(obj.id).addr, parent=parent, **fields)
+ return value
+
+ for _, animal in children(root, 'sources'):
+ animal_id = record('animal', animal, document['uuid'])
+ for _, prep in children(animal, 'sources'):
+ prep_id = record('preparation', prep, animal_id)
+ for _, cell in children(prep, 'sources'):
+ record('cell', cell, prep_id)
+ for _, group in children(root, 'epochGroups'):
+ if 'source' not in group:
+ raise ValueError('Epoch group has no source cell identity')
+ cell_obj = group['source']
+ cell_id = identity(cell_obj)
+ if cell_id not in inventory['cell'] or inventory['cell'][cell_id]['address'] != h5py.h5o.get_info(cell_obj.id).addr:
+ raise ValueError('Epoch group source cell identity differs from the experiment sources')
+ group_id = record('group', group, cell_id)
+ for _, block in children(group, 'epochBlocks'):
+ raw_epochs = children(block, 'epochs')
+ block_id = record('block', block, group_id, empty=not raw_epochs)
+ for _, epoch in raw_epochs:
+ epoch_id = record('epoch', epoch, block_id)
+ for kind in ('responses', 'stimuli'):
+ for key, stream in children(epoch, kind):
+ # This is Symphony's device-UUID naming convention and the
+ # pinned parser's strip_uuid operation, independently checked.
+ device = '-'.join(key.split('-')[:-5])
+ if not device:
+ raise ValueError('Source stream device identity is missing')
+ record('stream', stream, epoch_id, kind=kind, device=device)
+
+ def check(kind, item, parent):
+ value = item.get('uuid')
+ source = inventory[kind].get(value)
+ if source is None or value in parsed[kind]:
+ if kind == 'block':
+ raise ValueError('Epoch points to a different source block')
+ raise ValueError(f'Parsed {kind} identity or membership differs from H5 source')
+ parsed[kind].add(value)
+ if source['parent'] != parent:
+ if kind == 'group':
+ raise ValueError('Parsed group ownership differs from its source cell')
+ raise ValueError(f'Parsed {kind} parent ownership differs from H5 source')
+ return source
+
+ for animal in document['animals']:
+ check('animal', animal, document['uuid'])
+ for prep in animal['preparations']:
+ check('preparation', prep, animal['uuid'])
+ for cell in prep['cells']:
+ check('cell', cell, prep['uuid'])
+ for group in cell['epoch_groups']:
+ check('group', group, cell['uuid'])
+ for block in group['epoch_blocks']:
+ source_block = h5[check('block', block, group['uuid'])['path']]
+ if _source_text(source_block.attrs.get('protocolID', '')) != block['protocolID']:
+ raise ValueError('Parsed protocol differs from the H5 block')
+ for epoch in block['epochs']:
+ check('epoch', epoch, block['uuid'])
+ for kind in ('responses', 'stimuli'):
+ for device, stream in epoch.get(kind, {}).items():
+ source = check('stream', stream, epoch['uuid'])
+ try:
+ target = h5[stream['h5path']]
+ except (KeyError, TypeError) as error:
+ raise ValueError('Parsed stream locator differs from H5 source') from error
+ if source['address'] != h5py.h5o.get_info(target.id).addr or source['kind'] != kind or source['device'] != device:
+ raise ValueError('Parsed stream identity, kind or device differs from H5 source')
+ # Readers use ancestry of the locator, so aliases must
+ # retain its authoritative epoch/collection context.
+ if (target.parent.name.rsplit('/', 1)[-1] != kind or
+ identity(target.parent.parent) != epoch['uuid']):
+ raise ValueError('Parsed stream locator ownership differs from H5 source')
+ for kind, objects in inventory.items():
+ required = {key for key, value in objects.items() if kind != 'block' or not value['empty']}
+ if not required <= parsed[kind]:
+ raise ValueError(f'Parsed {kind} membership differs from the H5 source')
+ return inventory
+
+
+def restore_empty_blocks(h5, experiment, parser):
+ """Preserve canonical acquisition blocks the pinned parser omits as empty.
+
+ Production callers first validate physical hierarchy and identity. Walk only
+ canonical experiment/epochGroups paths so hard-linked backlinks cannot hide
+ blocks. Never reconstruct a missing populated block or guess its parent.
+ """
+ import h5py
+ validate_self_contained_h5(h5)
+ groups = {}
+ for animal in experiment['animals']:
+ for preparation in animal['preparations']:
+ for cell in preparation['cells']:
+ for group in cell['epoch_groups']:
+ if group['uuid'] in groups:
+ raise ValueError('Duplicate parsed epoch group identity')
+ groups[group['uuid']] = group
+ roots = [h5[name] for name in h5 if name.startswith('experiment-')]
+ if len(roots) != 1 or not isinstance(roots[0], h5py.Group):
+ raise ValueError('Expected exactly one Symphony experiment root')
+ additions, seen = [], set()
+ for source_group in roots[0].get('epochGroups', {}).values():
+ group_uuid = _source_text(source_group.attrs['uuid'])
+ if group_uuid not in groups:
+ raise ValueError('Source epoch group is missing from parsed metadata')
+ group = groups[group_uuid]
+ existing = {block['uuid'] for block in group['epoch_blocks']}
+ for source_block in source_group.get('epochBlocks', {}).values():
+ identity = _source_text(source_block.attrs['uuid'])
+ if identity in seen:
+ raise ValueError('Duplicate source block UUID')
+ seen.add(identity)
+ if identity in existing:
+ continue
+ if len(source_block.get('epochs', {})):
+ raise ValueError('Missing populated block cannot be reconstructed')
+ block = dict(parser.EpochBlockObj(source_block).__dict__)
+ if block.get('uuid') != identity or block.get('epochs') != []:
+ raise ValueError('Empty block parser identity or membership differs from source')
+ # Use the same pinned conversions as ordinary blocks, retaining
+ # protocol parameters, source attributes and acquisition timestamps.
+ block = json.loads(json.dumps(block, cls=getattr(parser, 'NpEncoder', None),
+ default=None if hasattr(parser, 'NpEncoder') else json_scalar,
+ allow_nan=False))
+ additions.append((group, block, group_uuid))
+ for group, block, _ in additions:
+ group['epoch_blocks'].append(block)
+ return [{'code': 'empty_epoch_block_restored', 'uuid': block['uuid'], 'group_uuid': group_uuid}
+ for _, block, group_uuid in additions]
+
+
+def validate_manifest_cell_count(manifest, all_cell_ids, epoch_cell_ids):
+ """Check the recorded count under its explicit or historical convention.
+
+ Before all-source-cells-v1, prepare counted only cells reached by epochs.
+ This affects the count check only: callers must still validate and retain
+ every source cell, including cells with no epochs.
+ """
+ semantics = manifest.get('cell_count_semantics')
+ if 'cell_count_semantics' not in manifest:
+ expected = len(epoch_cell_ids)
+ elif semantics == 'all-source-cells-v1':
+ expected = len(all_cell_ids)
+ else:
+ raise ValueError(f'Unsupported source cell count semantics: {semantics!r}')
+ if manifest['counts']['cells'] != expected:
+ raise ValueError('Source cell count differs from validated manifest')
def prepare(source, project, repository, progress=None, expected_sha256=None):
@@ -140,6 +432,9 @@ def prepare(source, project, repository, progress=None, expected_sha256=None):
if cached.get("parser_sha256") != digest(parser_path):
raise ValueError("Parser version changed; an explicit new parse revision is required")
else:
+ # Reject unsealed dependencies before the parser can read their samples.
+ with h5py.File(source, "r") as h5:
+ validate_self_contained_h5(h5)
with (folder / "parser.log").open("w") as log:
with contextlib.redirect_stdout(log), contextlib.redirect_stderr(log):
with parser.Symphony2Reader(str(source), str(raw_path)) as reader:
@@ -153,15 +448,16 @@ def prepare(source, project, repository, progress=None, expected_sha256=None):
rows, protocol_counts, cell_ids = [], {}, set()
warnings = []
with h5py.File(source, "r") as h5:
- roots = [v for k, v in h5.items() if k.startswith("experiment-")]
+ roots = [k for k in h5 if k.startswith("experiment-")]
if len(roots) != 1:
raise ValueError("Expected exactly one Symphony experiment root")
- root = roots[0]
+ root = resolve_sealed_h5_path(h5, "/" + roots[0])
+ properties = resolve_sealed_h5_path(h5, root.name + "/properties")
# RetinAnalysis currently builds ExperimentObj from the first animal.
# Reuse its conversion while restoring the real experiment-level facts.
experiment = parser.ExperimentObj(
d={"attributes": parser.parse_attributes(root),
- "properties": parser.parse_attributes(root["properties"])},
+ "properties": parser.parse_attributes(properties)},
rig_type=raw["rig_type"],
).__dict__
experiment["label"] = experiment.get("label") or source.stem
@@ -172,10 +468,20 @@ def prepare(source, project, repository, progress=None, expected_sha256=None):
"source_uuid": experiment["uuid"]})
if experiment["rig_type"] != "PATCH":
raise ValueError("This importer currently supports PATCH recordings only")
+ inventory = validate_source_identity(h5, experiment)
+ warnings.extend(restore_empty_blocks(h5, experiment, parser))
+ cell_ids.update(inventory["cell"])
for cell, group, block, epoch in epochs(experiment):
cell_ids.add(cell["uuid"])
streams = []
- parameters_checked = False
+ source_epoch = h5[inventory['epoch'][epoch['uuid']]['path']]
+ source_block = h5[inventory['block'][block['uuid']]['path']]
+ source_parameters = dict(source_block['protocolParameters'].attrs)
+ source_parameters.update(dict(source_epoch['protocolParameters'].attrs))
+ source_parameters = json.loads(json.dumps(source_parameters, cls=parser.NpEncoder, allow_nan=False))
+ for parameter, value in source_parameters.items():
+ if parameter not in epoch['parameters'] or epoch['parameters'][parameter] != value:
+ raise ValueError(f"Source parameter mismatch for epoch {epoch['uuid']}: {parameter}")
for kind in ("responses", "stimuli"):
for device, stream in epoch[kind].items():
obj = h5[stream["h5path"]]
@@ -190,14 +496,6 @@ def prepare(source, project, repository, progress=None, expected_sha256=None):
raise ValueError("Epoch points to a different source block")
if parser.parse_value(source_block.attrs["protocolID"]) != block["protocolID"]:
raise ValueError("Parsed protocol differs from the H5 block")
- if not parameters_checked:
- source_parameters = dict(source_block["protocolParameters"].attrs)
- source_parameters.update(dict(source_epoch["protocolParameters"].attrs))
- source_parameters = json.loads(json.dumps(source_parameters, cls=parser.NpEncoder, allow_nan=False))
- for parameter, value in source_parameters.items():
- if parameter not in epoch["parameters"] or epoch["parameters"][parameter] != value:
- raise ValueError(f"Source parameter mismatch for epoch {epoch['uuid']}: {parameter}")
- parameters_checked = True
data = obj.get("data")
units = None
if kind == "responses":
@@ -244,14 +542,6 @@ def prepare(source, project, repository, progress=None, expected_sha256=None):
"start_time": epoch.get("start_time"),
"parameters": epoch.get("parameters", {}), "streams": streams})
emit("validating_metadata", completed=len(rows), total=total_epochs, unit="epochs")
- raw_epoch_ids = set()
- def visit(name, obj):
- if isinstance(obj, h5py.Group) and name.split("/")[-2:-1] == ["epochs"]:
- raw_epoch_ids.add(parser.parse_value(obj.attrs["uuid"]))
- h5.visititems(visit)
- parsed_ids = [r["epoch_uuid"] for r in rows]
- if len(set(parsed_ids)) != len(parsed_ids) or set(parsed_ids) != raw_epoch_ids:
- raise ValueError("Parsed epoch membership differs from the H5 source")
after = source.stat()
if (stat.st_size, stat.st_mtime_ns) != (after.st_size, after.st_mtime_ns):
raise ValueError("Source changed during parsing/validation")
@@ -270,6 +560,7 @@ def visit(name, obj):
"adapter_version": 1, "adapter_sha256": digest(__file__),
"validated_at": now(), "metadata_path": str(normalized_path),
"metadata_sha256": digest(normalized_path),
+ "cell_count_semantics": "all-source-cells-v1",
"counts": {"cells": len(cell_ids), "epochs": len(rows),
"responses": sum(len(e["responses"]) for *_, e in epochs(experiment)),
"stimuli": sum(len(e["stimuli"]) for *_, e in epochs(experiment))},
@@ -489,7 +780,12 @@ def event(action, payload):
context={"project_uuid": project_id})})
stage = "database_transaction"
emit("writing_catalog", commit_state="unknown", catalog_committed=None)
+ from workspace_catalog_collision import CatalogCollisionLookup
+ identity_lookup = CatalogCollisionLookup(catalog)
try:
+ # Temporary candidate indexes belong to this locked connection and are
+ # created before the transaction: no DDL may commit acquisition writes.
+ identity_lookup.prepare()
with connection.transaction:
Project.insert1({"project_uuid": project_id, "name": project["name"],
"directory": str(project_dir)}, skip_duplicates=True)
@@ -498,14 +794,23 @@ def event(action, payload):
old = existing.fetch1()
if old["project_uuid"] != project_id:
raise ValueError("Source belongs to another project; explicit linking is required")
+ if old['experiment_uuid'] != experiment['uuid']:
+ raise CatalogIdentityConflict('source_registration',
+ 'Registered source experiment UUID differs from these same source bytes',identity=experiment['uuid'])
experiment_id = old["experiment_id"]
outcome = "already_imported"
protocol_types_added = 0
else:
- if catalog.Experiment & [{"h5_uuid": experiment["uuid"]},
- {"exp_name": Path(manifest["source_path"]).stem}]:
- raise ValueError("Existing experiment needs source/version reconciliation")
- assert_new_catalog_identities(experiment, catalog)
+ if catalog.Experiment & {"h5_uuid": experiment["uuid"]}:
+ raise CatalogIdentityConflict('source_revision_conflict',
+ 'This experiment acquisition UUID is already associated with different source bytes',identity=experiment['uuid'])
+ if catalog.Experiment & {"exp_name": Path(manifest["source_path"]).stem}:
+ # RetinAnalysis consumers still select experiments by name.
+ # Do not silently rename scientific identifiers or permit
+ # ambiguous downstream selection while that contract exists.
+ raise CatalogIdentityConflict('experiment_name_collision',
+ 'A different acquisition uses this experiment filename. Matching names do not establish duplicate data; choose an explicitly qualified import filename before retrying',identity=experiment['uuid'])
+ assert_new_catalog_identities(experiment, catalog, lookup=identity_lookup)
protocol_types_added = len(new_project_protocol_types(
catalog, Source, project_id, manifest['protocol_epoch_counts']))
population.configure_tables(catalog)
@@ -517,36 +822,26 @@ def event(action, payload):
Source.insert1({"source_sha256": manifest["source_sha256"],
"project_uuid": project_id, "experiment_uuid": experiment["uuid"],
"experiment_id": experiment_id, "manifest": manifest})
- actual_ids = {str(r["h5_uuid"]) for r in (catalog.Epoch & {"experiment_id": experiment_id}).to_dicts()}
- expected_ids = {e["uuid"] for *_, e in epochs(experiment)}
- if actual_ids != expected_ids:
- raise ValueError("Catalog epoch membership differs from parsed source")
- if len(catalog.Cell & {"experiment_id": experiment_id}) != manifest["counts"]["cells"]:
+ all_cell_ids = {cell['uuid'] for animal in experiment['animals']
+ for preparation in animal['preparations'] for cell in preparation['cells']}
+ epoch_cell_ids = {cell['uuid'] for cell, *_ in epochs(experiment)}
+ validate_manifest_cell_count(manifest, all_cell_ids, epoch_cell_ids)
+ # Catalog population retains all cells under either manifest convention.
+ # Same-SHA rechecks must not compare this total to a legacy active count.
+ counts = validate_catalog_identity(experiment, catalog, experiment_id, emit)
+ if counts['Cell'] != len(all_cell_ids):
raise ValueError("Catalog cell count differs from source")
- db_epochs = (catalog.Epoch & {"experiment_id": experiment_id}).to_dicts()
- expected = {e["uuid"]: e for *_, e in epochs(experiment)}
- response_count = stimulus_count = 0
- emit("verifying_catalog", completed=0, total=len(db_epochs), unit="epochs")
- for epoch_index, row in enumerate(db_epochs, 1):
- epoch = expected[row["h5_uuid"]]
- if row["parameters"] != epoch["parameters"] or row["attributes"] != epoch["attributes"]:
- raise ValueError("Catalog epoch parameters/attributes changed during insertion")
- for table, key in [(catalog.Response, "responses"), (catalog.Stimulus, "stimuli")]:
- actual_streams = (table & {"parent_id": row["id"]}).to_dicts()
- actual = {(r["h5_uuid"], r["device_name"], r["h5path"]) for r in actual_streams}
- wanted = {(s["uuid"], device, s["h5path"]) for device, s in epoch[key].items()}
- if actual != wanted:
- raise ValueError("Catalog stream identity/device/path differs from source")
- if key == "responses":
- response_count += len(actual)
- else:
- stimulus_count += len(actual)
- emit("verifying_catalog", completed=epoch_index, total=len(db_epochs), unit="epochs")
- if (response_count, stimulus_count) != (manifest["counts"]["responses"], manifest["counts"]["stimuli"]):
+ if counts['Epoch'] != manifest['counts']['epochs']:
+ raise ValueError('Catalog epoch count differs from source')
+ if (counts['Response'], counts['Stimulus']) != (manifest["counts"]["responses"], manifest["counts"]["stimuli"]):
raise ValueError("Catalog stream counts differ from source")
event(outcome, {"source_sha256": manifest["source_sha256"],
"experiment_uuid": experiment["uuid"],
"counts": manifest["counts"], "warnings": manifest["warnings"]})
+ # A cleanup failure after commit must report persisted catalog state,
+ # never suggest that the completed acquisition transaction rolled back.
+ stage = "workspace_files"
+ identity_lookup.close()
# Every new Cell/Epoch UUID was checked absent under the import lock,
# then inserted membership and stream counts were verified in the
# transaction above. These are actual additions, not source totals.
@@ -606,6 +901,8 @@ def event(action, payload):
failure = {"source_sha256": manifest["source_sha256"],
"stage": stage, "catalog_committed": error.catalog_committed,
"error_type": type(error).__name__, "error": str(error)}
+ if isinstance(error, CatalogIdentityConflict):
+ failure['identity_conflict'] = error.conflict
try:
event("workspace_finalize_failed" if error.catalog_committed else "import_failed", failure)
except Exception as audit_error:
@@ -613,6 +910,10 @@ def event(action, payload):
write_json(folder / ("failure-" + str(uuid.uuid4()) + ".json"), failure)
raise
finally:
+ # Rollback has already ended before scratch cleanup. Its own fallback
+ # closes the owning connection if temporary-table removal fails.
+ with contextlib.suppress(Exception):
+ identity_lookup.close()
# A lost connection releases its advisory lock server-side. Do not mask
# the original import error with a secondary unlock error.
with contextlib.suppress(Exception):
@@ -696,6 +997,8 @@ def main():
finished_at=now(), error_type=type(error).__name__, error=error_message,
stage=getattr(error, "workflow_stage", reporter.current.get('stage', 'validation')),
catalog_committed=committed)
+ if isinstance(error, CatalogIdentityConflict):
+ job['identity_conflict'] = error.conflict
write_json(job_file, job)
job_file.with_suffix(".traceback.txt").write_text(traceback.format_exc())
reporter.emit('failed', outcome='failed', error_type=type(error).__name__, error=error_message,
diff --git a/python/tests/test_desktop_release.py b/python/tests/test_desktop_release.py
new file mode 100644
index 0000000..85c77d4
--- /dev/null
+++ b/python/tests/test_desktop_release.py
@@ -0,0 +1,43 @@
+import importlib.util
+from pathlib import Path
+import unittest
+
+spec = importlib.util.spec_from_file_location('desktop_release', Path(__file__).resolve().parents[2] / 'tools/desktop_release.py')
+release = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(release)
+
+
+class DesktopReleaseTests(unittest.TestCase):
+ def evidence(self):
+ return {'format': 'rieke-desktop-qualification', 'version': 1, 'application_version': '1.0.0',
+ 'platform': 'darwin', 'architecture': 'arm64', 'source_commit': 'a' * 40,
+ 'artifacts': {'app.zip': {'sha256': 'b' * 64, 'size': 10}},
+ 'requirements': {key: {'passed': True, 'receipts': [{'description': 'Reviewed real-artifact run', 'sha256': 'c' * 64}]} for key in release.REQUIREMENTS}}
+
+ def test_incomplete_qualification_and_different_bytes_rejected(self):
+ evidence = self.evidence()
+ release.validate_evidence(evidence, evidence['artifacts'], '1.0.0')
+ with self.assertRaises(ValueError):
+ release.validate_evidence(evidence, {}, '1.0.0')
+ for requirement in release.REQUIREMENTS:
+ evidence = self.evidence()
+ evidence['requirements'][requirement]['passed'] = False
+ with self.assertRaises(ValueError):
+ release.validate_evidence(evidence, evidence['artifacts'], '1.0.0')
+
+ def test_mock_counts_and_unsigned_receipts_cannot_replace_requirement_evidence(self):
+ evidence = self.evidence()
+ evidence['requirements'] = {'tests_passed': 500}
+ with self.assertRaises(ValueError):
+ release.validate_evidence(evidence, evidence['artifacts'], '1.0.0')
+
+ def test_stable_version_and_canonical_repository_required(self):
+ for value in ('1.0.0-beta', 'v1.0.0', '01.0.0', None):
+ with self.assertRaises(ValueError):
+ release.version(value)
+ with self.assertRaises(ValueError):
+ release.baseline('v1.0.0', 'other/repo')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_desktop_runtime_audit.py b/python/tests/test_desktop_runtime_audit.py
new file mode 100644
index 0000000..4170b3c
--- /dev/null
+++ b/python/tests/test_desktop_runtime_audit.py
@@ -0,0 +1,98 @@
+"""Packaging preflight rejects machine-bound resources without executing them."""
+import importlib.util
+import json
+from pathlib import Path
+import tempfile
+import unittest
+from unittest.mock import patch
+
+spec = importlib.util.spec_from_file_location('desktop_runtime_audit', Path(__file__).resolve().parents[2] / 'tools/desktop_runtime_audit.py')
+audit = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(audit)
+
+
+class RuntimeAuditTests(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp.cleanup)
+ self.root = Path(self.temp.name).resolve() / 'app'
+ self.root.mkdir()
+ self.write('python/workspace-source.json', {'commit': 'a' * 40, 'python': '3.11.13'})
+ self.write('rieke-release.json', {'version': '0.1.0', 'repository': 'maxwellsdm1867/Rieke-OS'})
+ self.write('workspace-app/package.json', {'version': '0.1.0'})
+ self.write('.rieke-runtime/runtime.json', {'python': '.rieke-runtime/python/bin/python',
+ 'retinanalysis': '.rieke-runtime/retinanalysis', 'retinanalysis_commit': 'a' * 40})
+ self.file('.rieke-runtime/python/bin/python', 'raise RuntimeError("must not execute")')
+ self.file('.rieke-runtime/retinanalysis/.git', 'source marker')
+
+ def write(self, name, value):
+ self.file(name, json.dumps(value))
+
+ def file(self, name, value):
+ path = self.root / name
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(value)
+ return path
+
+ def inspect(self):
+ with patch.object(audit.subprocess, 'check_output', return_value='a' * 40) as git:
+ result = audit.audit_source_runtime(self.root)
+ self.assertEqual(git.call_args.args[0][:1], ['git'])
+ self.assertFalse(result['project_code_executed'])
+ self.assertFalse(result['production_ready'])
+ return result
+
+ def test_known_safe_static_layout_never_claims_production_qualification(self):
+ self.assertEqual(self.inspect()['status'], 'no_known_static_blockers')
+
+ def test_external_python_and_editable_import_paths_are_blockers(self):
+ external = Path(self.temp.name) / 'outside-python'
+ external.write_text('not executed')
+ python = self.root / '.rieke-runtime/python/bin/python'
+ python.unlink()
+ python.symlink_to(external)
+ self.file('.rieke-runtime/python/lib/site-packages/parser.pth', '/build/user/parser/src\n')
+ self.file('.rieke-runtime/python/pyvenv.cfg', 'home = /build/python/bin\n')
+ codes = {item['code'] for item in self.inspect()['blockers']}
+ self.assertTrue({'external_runtime_path', 'external_runtime_symlink', 'absolute_python_import_path', 'absolute_venv_home'} <= codes)
+
+ def test_relative_internal_links_are_allowed_but_relative_import_escape_is_not(self):
+ target = self.file('.rieke-runtime/python/bin/python-real', 'bundled')
+ python = self.root / '.rieke-runtime/python/bin/python'
+ python.unlink()
+ python.symlink_to(target.name)
+ self.assertEqual(self.inspect()['status'], 'no_known_static_blockers')
+ self.file('.rieke-runtime/python/lib/site-packages/escape.pth', '../../../../../external\n')
+ self.assertIn('external_python_import_path', {item['code'] for item in self.inspect()['blockers']})
+
+ def test_source_pin_and_generated_parser_config_fail_independently(self):
+ self.write('.rieke-runtime/runtime.json', {'python': '.rieke-runtime/python/bin/python',
+ 'retinanalysis': '.rieke-runtime/retinanalysis', 'retinanalysis_commit': 'b' * 40})
+ self.file('.rieke-runtime/retinanalysis/src/retinanalysis/config/config.ini', '[DEFAULT]\ndata=/build/data\n')
+ codes = {item['code'] for item in self.inspect()['blockers']}
+ self.assertTrue({'parser_receipt_mismatch', 'parser_config_build_paths'} <= codes)
+
+ def test_missing_or_malformed_metadata_does_not_produce_a_pass(self):
+ self.file('.rieke-runtime/runtime.json', '[]')
+ (self.root / 'rieke-release.json').unlink()
+ result = self.inspect()
+ self.assertEqual(result['status'], 'blocked')
+ self.assertIn('metadata_unavailable', {item['code'] for item in result['blockers']})
+
+ def test_external_runtime_directory_is_not_traversed(self):
+ import shutil
+ runtime = self.root / '.rieke-runtime'
+ shutil.rmtree(runtime)
+ external = Path(self.temp.name) / 'outside-runtime'
+ external.mkdir()
+ (external / 'secret.pth').write_text('/external/private\n')
+ runtime.symlink_to(external)
+ with patch.object(audit.subprocess, 'check_output') as git:
+ result = audit.audit_source_runtime(self.root)
+ git.assert_not_called()
+ self.assertEqual(result['examined']['pth_files'], 0)
+ self.assertIn('unsafe_or_missing_runtime', {item['code'] for item in result['blockers']})
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_desktop_runtime_packaging.py b/python/tests/test_desktop_runtime_packaging.py
new file mode 100644
index 0000000..7cb121c
--- /dev/null
+++ b/python/tests/test_desktop_runtime_packaging.py
@@ -0,0 +1,170 @@
+"""Desktop resource fidelity and user-state contracts, using temporary fixtures."""
+import configparser
+import importlib.util
+import hashlib
+import io
+import json
+import os
+from pathlib import Path
+import sys
+import tempfile
+import unittest
+from unittest.mock import patch
+
+import workspace_bootstrap as boot
+
+ROOT = Path(__file__).resolve().parents[2]
+sys.path.insert(0, str(ROOT / 'tools'))
+from desktop_build_runtime import patch_parser, exclude_optional_features, OPTIONAL_PID_ATTACH, copy_matlab_application, MATLAB_EXPORT_HELPERS, refresh_native_license_inventory
+from desktop_runtime_manifest import inventory
+
+
+class DesktopPackagingTests(unittest.TestCase):
+ def setUp(self):
+ environment = patch.dict(os.environ, dict(os.environ))
+ environment.start()
+ self.addCleanup(environment.stop)
+
+ def test_parser_patch_preserves_algorithms_and_config_semantics(self):
+ source = ROOT / '.rieke-runtime/retinanalysis/src/retinanalysis/config/settings.py'
+ if not source.exists():
+ self.skipTest('Pinned parser source not provisioned')
+ with tempfile.TemporaryDirectory() as temporary:
+ root = Path(temporary)
+ config = boot.prepare_desktop_parser_config(root / 'state')
+ staged = root / 'parser/src/retinanalysis/config'
+ staged.mkdir(parents=True)
+ (staged / 'settings.py').write_bytes(source.read_bytes())
+ # The mutation must touch only settings.py and remove generated config.
+ algorithms = root / 'parser/src/retinanalysis/utils/parse_data.py'
+ algorithms.parent.mkdir()
+ original = (source.parents[1] / 'utils/parse_data.py').read_bytes()
+ algorithms.write_bytes(original)
+ receipt = patch_parser(root / 'parser')
+ self.assertNotEqual(receipt['original_sha256'], receipt['patched_sha256'])
+ self.assertEqual(algorithms.read_bytes(), original)
+ def load(path, name):
+ spec = importlib.util.spec_from_file_location(name, path)
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+ # Execute settings in isolation to avoid eager public package/database imports.
+ import types
+ stub = types.ModuleType('retinanalysis')
+ with patch.dict(sys.modules, {'retinanalysis': stub}), \
+ patch.dict(os.environ, {'RIEKE_PARSER_CONFIG': str(config)}), \
+ patch('importlib.resources.files', return_value=config.parent.parent):
+ # Source uses packaged path. Redirect it to the same fixture config.
+ source_copy = root / 'source_settings.py'
+ source_copy.write_text(source.read_text().replace(
+ 'config_path = ir.files(retinanalysis) / os.path.join("config", "config.ini")',
+ 'config_path = ' + repr(str(config))))
+ before = load(source_copy, 'source_settings')
+ after = load(staged / 'settings.py', 'wheel_settings')
+ self.assertEqual(before.mea_config, after.mea_config)
+ for key in ('DATA_DIR', 'RAW_DIR', 'H5_DIR', 'META_DIR', 'TAGS_DIR', 'QUERY_DIR', 'USER'):
+ self.assertEqual(getattr(before, key), getattr(after, key))
+ os.environ['RIEKE_PARSER_CONFIG'] = 'relative/config.ini'
+ with self.assertRaisesRegex(ValueError, 'absolute'):
+ load(staged / 'settings.py', 'bad_settings')
+
+ def test_parser_config_never_modifies_package_and_preserves_user_values(self):
+ with tempfile.TemporaryDirectory() as temporary:
+ root = Path(temporary)
+ runtime = root / 'app/runtime'
+ runtime.mkdir(parents=True)
+ with patch.dict(os.environ, {'RIEKE_DESKTOP_RUNTIME': str(runtime)}):
+ target = boot.prepare_desktop_parser_config(root / 'state')
+ target.write_text('[DEFAULT]\nuser = scientist\n')
+ boot.prepare_desktop_parser_config(root / 'state')
+ self.assertIn('scientist', target.read_text())
+ self.assertEqual(list(runtime.iterdir()), [])
+ with self.assertRaisesRegex(ValueError, 'separate'):
+ boot.prepare_desktop_parser_config(runtime / 'state')
+
+ def test_inventory_rejects_external_and_broken_links_and_hashes_bytes(self):
+ with tempfile.TemporaryDirectory() as temporary:
+ root = Path(temporary)
+ runtime = root / 'runtime'
+ runtime.mkdir()
+ resource = runtime / 'bytes'
+ resource.write_bytes(b'first')
+ first = inventory(runtime)
+ resource.write_bytes(b'after')
+ self.assertNotEqual(first, inventory(runtime))
+ link = runtime / 'link'
+ link.symlink_to('../external')
+ with self.assertRaisesRegex(ValueError, 'escapes'):
+ inventory(runtime)
+ link.unlink()
+ link.symlink_to('missing')
+ with self.assertRaisesRegex(ValueError, 'broken'):
+ inventory(runtime)
+
+ def test_production_omits_only_optional_pid_debugger_helper_and_records_provenance(self):
+ with tempfile.TemporaryDirectory() as temporary:
+ root = Path(temporary)
+ helper = root / OPTIONAL_PID_ATTACH
+ helper.parent.mkdir(parents=True)
+ helper.write_bytes(b'pinned optional debugger helper')
+ retained = helper.parent / 'attach_pydevd.py'
+ retained.write_bytes(b'normal pinned debugger logic')
+ inventory_path = root / 'dependency-inventory.json'
+ inventory_path.write_text('{"python_distributions": [{"name":"debugpy","version":"1.8.22"}]}')
+ features = exclude_optional_features(root)
+ self.assertFalse(helper.exists())
+ self.assertEqual(retained.read_bytes(), b'normal pinned debugger logic')
+ self.assertEqual(features[0]['path'], OPTIONAL_PID_ATTACH)
+ self.assertEqual(len(features[0]['original_sha256']), 64)
+ value = json.loads(inventory_path.read_text())
+ self.assertEqual(value['python_distributions'][0]['version'], '1.8.22')
+ self.assertEqual(value['excluded_optional_features'], features)
+
+ def test_matlab_export_and_gui_source_closure_excludes_recordings_and_examples(self):
+ with tempfile.TemporaryDirectory() as temporary:
+ root, output = Path(temporary) / 'source', Path(temporary) / 'application'
+ names = ['epicTreeGUI.m', 'src/loadEpicTreeData.m', 'src/buildTreeFromEpicData.m',
+ 'src/tree/epicTreeTools.m', 'src/gui/widget.m']
+ names.extend('src/tree/' + name + '.m' for name in MATLAB_EXPORT_HELPERS)
+ for name in names + ['src/private-recording.h5', 'examples/sample.m', 'tests/test_gui.m']:
+ path = root / name
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(name)
+ copy_matlab_application(root, output)
+ for name in names:
+ self.assertEqual((output / name).read_text(), name)
+ self.assertFalse((output / 'src/private-recording.h5').exists())
+ self.assertFalse((output / 'examples').exists())
+ self.assertFalse((output / 'tests').exists())
+ (root / 'src/tree/launchWorkspaceTree.m').unlink()
+ with self.assertRaisesRegex(ValueError, 'incomplete'):
+ copy_matlab_application(root, output)
+
+ def test_native_notice_sources_are_version_and_hash_pinned_with_shared_coverage(self):
+ with tempfile.TemporaryDirectory() as temporary:
+ root, output = Path(temporary) / 'source', Path(temporary) / 'runtime'
+ (root / 'desktop').mkdir(parents=True)
+ output.mkdir()
+ data = b'Pinned native license notice\n'
+ source = {'package': 'mysql-server', 'applies_to': ['mysql-server', 'mysql-client', 'mysql-common'],
+ 'version': '8.4.2', 'file': 'mysql-LICENSE.txt',
+ 'url': 'https://raw.githubusercontent.com/mysql/mysql-server/mysql-8.4.2/LICENSE',
+ 'sha256': hashlib.sha256(data).hexdigest()}
+ (root / 'desktop/native-license-sources.json').write_text(json.dumps(
+ {'format': 'rieke-native-license-sources', 'version': 1, 'sources': [source]}))
+ (output / 'dependency-inventory.json').write_text(json.dumps(
+ {'native_packages': [{'name': name, 'version': '8.4.2'} for name in source['applies_to']],
+ 'native_license_files': []}))
+ with patch('desktop_build_runtime.urlopen', return_value=io.BytesIO(data)):
+ refresh_native_license_inventory(output, root)
+ result = json.loads((output / 'dependency-inventory.json').read_text())
+ self.assertEqual(set(result['native_license_coverage']), set(source['applies_to']))
+ self.assertTrue(all(result['native_license_coverage'].values()))
+ self.assertEqual((output / 'licenses/native/mysql-server/mysql-LICENSE.txt').read_bytes(), data)
+ (root / 'desktop/build/license-cache/mysql-LICENSE.txt').write_bytes(b'corrupted cached notice')
+ with self.assertRaisesRegex(ValueError, 'checksum'):
+ refresh_native_license_inventory(output, root)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_desktop_test_release.py b/python/tests/test_desktop_test_release.py
new file mode 100644
index 0000000..70fe34c
--- /dev/null
+++ b/python/tests/test_desktop_test_release.py
@@ -0,0 +1,32 @@
+"""Public unsigned release descriptor construction, separate from signed promotion."""
+import importlib.util
+import json
+import plistlib
+from pathlib import Path
+import tempfile
+import unittest
+import zipfile
+import hashlib
+
+spec=importlib.util.spec_from_file_location('desktop_test_release',Path(__file__).resolve().parents[2]/'tools/desktop_test_release.py')
+module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
+
+class TestUnsignedReleaseDescriptor(unittest.TestCase):
+ def test_descriptor_binds_actual_archive_and_app_identity(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ root=Path(tmp);app=root/'Rieke OS.app';resources=app/'Contents/Resources';runtime=resources/'runtime';runtime.mkdir(parents=True)
+ manifest={'format':'rieke-desktop-runtime','version':1,'application_version':'0.1.3','platform':'darwin','architecture':'arm64','workspace_formats':[1],'database_compatibility':1,'mysql_version':'8.4.2','minimum_macos_version':'14.0','source_commit':'a'*40,'source_dirty':True}
+ (runtime/'runtime-manifest.json').write_text(json.dumps(manifest))
+ (resources/'app.asar').write_bytes(b'candidate shell fixture')
+ (app/'Contents/Info.plist').write_bytes(plistlib.dumps({'CFBundleIdentifier':'org.riekeos.desktop','CFBundleShortVersionString':'0.1.3'}))
+ archive=root/'Rieke-OS-0.1.3-arm64.zip'
+ with zipfile.ZipFile(archive,'w') as z:
+ for p in app.rglob('*'):
+ if p.is_file():z.write(p,p.relative_to(root))
+ descriptor=module.build_descriptor(app,archive)
+ self.assertEqual(descriptor['channel'],'unsigned-testing')
+ self.assertEqual(descriptor['archive']['sha256'],hashlib.sha256(archive.read_bytes()).hexdigest())
+ self.assertEqual(descriptor['application_version'],'0.1.3')
+ self.assertFalse(descriptor['production_ready'])
+ (resources/'app.asar').write_bytes(b'changed after packaging')
+ with self.assertRaises(ValueError):module.build_descriptor(app,archive)
diff --git a/python/tests/test_recording_workspace.py b/python/tests/test_recording_workspace.py
index 876b4a1..2834025 100644
--- a/python/tests/test_recording_workspace.py
+++ b/python/tests/test_recording_workspace.py
@@ -1,6 +1,10 @@
import copy
import json
+import os
from pathlib import Path
+import stat
+import subprocess
+import sys
import tempfile
import unittest
from unittest.mock import patch
@@ -52,6 +56,87 @@ def test_ticks_keep_exact_integer_precision(self):
workspace.write_json(path, {"ticks": ticks})
self.assertEqual(json.loads(path.read_text())["ticks"], int(ticks))
+ def test_durable_json_flushes_file_before_publish_and_directory_after(self):
+ with tempfile.TemporaryDirectory() as directory:
+ path = Path(directory) / 'metadata.json'
+ events = []
+ real_fsync, real_replace = os.fsync, os.replace
+ def sync(fd):
+ events.append('sync')
+ return real_fsync(fd)
+ def replace(source, destination):
+ events.append('replace')
+ return real_replace(source, destination)
+ with patch('workspace_state_snapshot.os.fsync', side_effect=sync), \
+ patch('workspace_state_snapshot.os.replace', side_effect=replace):
+ workspace.write_json(path, {'revision': 2})
+ self.assertEqual(events, ['sync', 'replace', 'sync'])
+ self.assertEqual(json.loads(path.read_text()), {'revision': 2})
+
+ def test_failed_file_flush_or_replace_preserves_previous_json_and_cleans_temporary(self):
+ for operation in ('fsync', 'replace'):
+ with self.subTest(operation=operation), tempfile.TemporaryDirectory() as directory:
+ path = Path(directory) / 'metadata.json'
+ workspace.write_json(path, {'revision': 1})
+ before = path.read_bytes()
+ with patch('workspace_state_snapshot.os.' + operation, side_effect=OSError('disk fault')):
+ with self.assertRaisesRegex(OSError, 'disk fault'):
+ workspace.write_json(path, {'revision': 2})
+ self.assertEqual(path.read_bytes(), before)
+ self.assertEqual(list(Path(directory).iterdir()), [path])
+
+ def test_directory_flush_failure_is_reported_after_atomic_publication(self):
+ with tempfile.TemporaryDirectory() as directory:
+ path = Path(directory) / 'metadata.json'
+ workspace.write_json(path, {'revision': 1})
+ with patch('workspace_state_snapshot.os.fsync', side_effect=[None, OSError('directory fault')]):
+ with self.assertRaisesRegex(OSError, 'directory fault'):
+ workspace.write_json(path, {'revision': 2})
+ # Publication already happened. Never report success or pretend the
+ # old value was restored after a failed durability confirmation.
+ self.assertEqual(json.loads(path.read_text()), {'revision': 2})
+ self.assertEqual(list(Path(directory).iterdir()), [path])
+
+ def test_general_json_preserves_existing_permissions_but_snapshots_remain_private(self):
+ from workspace_state_snapshot import atomic_write
+ with tempfile.TemporaryDirectory() as directory:
+ path = Path(directory) / 'metadata.json'
+ path.write_text('{"revision":1}\n');path.chmod(0o640)
+ workspace.write_json(path, {'revision':2})
+ self.assertEqual(stat.S_IMODE(path.stat().st_mode),0o640)
+ self.assertEqual(json.loads(path.read_text()),{'revision':2})
+ atomic_write(path,b'{"revision":3}\n')
+ self.assertEqual(stat.S_IMODE(path.stat().st_mode),0o600)
+
+ def test_new_general_json_uses_normal_creation_umask_without_changing_parent_process(self):
+ with tempfile.TemporaryDirectory() as directory:
+ path = Path(directory) / 'metadata.json'
+ code = '''import os,stat,sys
+from pathlib import Path
+from recording_workspace import write_json
+os.umask(0o027)
+path=Path(sys.argv[1])
+write_json(path,{'revision':1})
+assert stat.S_IMODE(path.stat().st_mode)==0o640
+assert os.umask(0o027)==0o027
+'''
+ env={**os.environ,'PYTHONPATH':str(Path(__file__).resolve().parents[1])}
+ result=subprocess.run([sys.executable,'-c',code,str(path)],env=env,
+ capture_output=True,text=True,timeout=30)
+ self.assertEqual(result.returncode,0,result.stdout+result.stderr)
+ self.assertEqual(stat.S_IMODE(path.stat().st_mode),0o640)
+
+ def test_permission_failure_preserves_previous_json_and_cleans_temporary(self):
+ with tempfile.TemporaryDirectory() as directory:
+ path=Path(directory)/'metadata.json'
+ workspace.write_json(path,{'revision':1});path.chmod(0o640)
+ with patch('workspace_state_snapshot.os.fchmod',side_effect=OSError('permission fault')):
+ with self.assertRaisesRegex(OSError,'permission fault'):
+ workspace.write_json(path,{'revision':2})
+ self.assertEqual(json.loads(path.read_text()),{'revision':1})
+ self.assertEqual(stat.S_IMODE(path.stat().st_mode),0o640)
+ self.assertEqual(list(Path(directory).iterdir()),[path])
+
def test_tampered_parser_cache_is_rejected_before_reading_h5(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
diff --git a/python/tests/test_workspace_annotation_density.py b/python/tests/test_workspace_annotation_density.py
new file mode 100644
index 0000000..158b0f5
--- /dev/null
+++ b/python/tests/test_workspace_annotation_density.py
@@ -0,0 +1,91 @@
+"""Dense multi-tag annotation operations retain exact profile and target scope."""
+import copy
+import unittest
+from unittest.mock import patch
+import uuid
+from types import SimpleNamespace
+
+import test_workspace_annotations as fixture
+from workspace_curation import RevisionConflict
+from workspace_tag_predicates import TagPredicates,SHARED_FIELDS
+
+
+class DenseAnnotationSelectionTests(unittest.TestCase):
+ def setUp(self):
+ self.case=fixture.SharedAnnotationTests();self.case.setUp();self.addCleanup(self.case.doCleanups)
+ self.store=self.case.store;self.service=self.case.service;self.table=self.case.records
+ self.author=self.store.default_profile['profile_uuid']
+ original=self.service.rows[self.case.first]
+ self.ids=[str(uuid.UUID(int=n+10000)) for n in range(1003)]
+ for key in self.ids:self.service.rows[key]={**original,'epoch_uuid':key}
+ self.multi_tags=['QC','qc','artifact','é','e\u0301']
+ for key in self.ids:
+ self.table.rows.append({'project_uuid':self.service.project['project_uuid'],
+ 'target_kind':'epoch','target_uuid':key,'profile_uuid':self.author,
+ 'tags':list(self.multi_tags),'author_name':self.store.default_profile['display_name'],
+ 'revision':1,'updated_at':'fixture'})
+ self.other_author=str(uuid.uuid4())
+ self.table.rows.append({**self.table.rows[0],'profile_uuid':self.other_author,'author_name':'Same display name','tags':['QC','other profile']})
+
+ def test_thousand_target_read_uses_four_exact_primary_key_batches_and_preserves_all_profiles(self):
+ selected=self.ids[:1000]
+ result=self.store.read_targets('epoch',selected)
+ self.assertEqual(set(result),set(selected))
+ reads=self.table.read_log
+ self.assertEqual(len(reads),4)
+ self.assertTrue(all(len(read['restrictions'][-1])<=250 for read in reads))
+ requested=[item['target_uuid'] for read in reads for item in read['restrictions'][-1]]
+ self.assertEqual(requested,selected)
+ self.assertEqual(set(result[selected[0]]['revisions']),{self.author,self.other_author})
+ chips=result[selected[0]]['tags']
+ self.assertEqual(len([chip for chip in chips if chip['tag']=='QC']),2)
+ self.assertEqual({chip['tag'] for chip in chips},{*self.multi_tags,'other profile'})
+
+ def test_thousand_target_save_reads_only_exact_target_author_keys(self):
+ selected=self.ids[:1000];before=copy.deepcopy(self.table.rows)
+ operations=[{'target_kind':'epoch','target_uuid':key,'profile_uuid':self.author,
+ 'expected_revision':1,'tags_add':['reviewed'],'tags_remove':['artifact']} for key in selected]
+ with patch.object(self.store,'_rows',side_effect=AssertionError('No full annotation read')):
+ result=self.store.apply_batch(operations,'fixture')
+ self.assertEqual(result['changed'],1000)
+ reads=self.table.read_log
+ self.assertEqual(len(reads),4)
+ requested=[item for read in reads for item in read['restrictions'][-1]]
+ self.assertEqual(requested,[{'target_kind':'epoch','target_uuid':key,'profile_uuid':self.author} for key in selected])
+ self.assertTrue(all(len(read['restrictions'][-1])<=250 for read in reads))
+ for index,row in enumerate(self.table.rows):
+ if row['target_uuid'] in selected and row['profile_uuid']==self.author:
+ self.assertEqual(row['revision'],2)
+ self.assertEqual(set(row['tags']),({*self.multi_tags,'reviewed'}-{'artifact'}))
+ else:self.assertEqual(row,before[index])
+
+ def test_last_target_revision_conflict_rolls_back_entire_multi_tag_batch(self):
+ self.table.rows[999]['revision']=2
+ before=copy.deepcopy(self.table.rows)
+ operations=[{'target_kind':'epoch','target_uuid':key,'profile_uuid':self.author,
+ 'expected_revision':1,'tags_add':['never']} for key in self.ids[:1000]]
+ with self.assertRaises(RevisionConflict):self.store.apply_batch(operations,'fixture')
+ self.assertEqual(self.table.rows,before)
+ self.assertFalse(self.case.case.events.rows)
+
+ def test_verified_index_browse_summary_and_catalog_never_hydrate_snapshot_or_chips(self):
+ rows=[self.service.rows[key] for key in self.ids]
+ expected_summary=self.store.summary(rows)
+ expected_catalog=TagPredicates(self.service).catalog_fields(self.ids)
+ self.store.state_generation=SimpleNamespace(token=lambda:1,shared_changes=lambda token:([],1))
+ # The small table fixture has no native SQL cursor. Supply its canonical
+ # stream at the adapter boundary; native streaming is tested separately.
+ canonical=copy.deepcopy(self.table.rows)
+ with patch.object(self.store,'_index_records',side_effect=lambda keys:iter(canonical)), \
+ patch.object(self.store,'snapshot',side_effect=AssertionError('No evidence snapshot for browsing')), \
+ patch.object(self.store,'for_epochs',side_effect=AssertionError('No full provenance hydration')):
+ actual=self.service._filter_rows(rows,{'tag':'QC'})
+ self.assertEqual({row['epoch_uuid'] for row in actual},set(self.ids))
+ self.assertEqual(self.store.summary(rows),expected_summary)
+ catalog=TagPredicates(self.service).catalog_fields(self.ids)
+ self.assertEqual([field for field in catalog if field['id'] in SHARED_FIELDS],
+ [field for field in expected_catalog if field['id'] in SHARED_FIELDS])
+ self.addCleanup(self.store._shared_tag_index.close)
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_annotation_preparation.py b/python/tests/test_workspace_annotation_preparation.py
new file mode 100644
index 0000000..c2bb09f
--- /dev/null
+++ b/python/tests/test_workspace_annotation_preparation.py
@@ -0,0 +1,130 @@
+"""Small real-SQLite preparation/checkpoint lifecycle, no native server work."""
+import copy
+import json
+from pathlib import Path
+from types import SimpleNamespace
+import unittest
+from unittest.mock import patch
+
+from test_workspace_annotations import SharedAnnotationTests
+from workspace_annotation_preparation import prepare_annotation_indexes,_ordered_rows
+from workspace_annotation_checkpoint import restore_shared_checkpoint,save_shared_checkpoint
+from workspace_disk_index import DiskMetadataIndex
+from workspace_recipes import checksum
+from workspace_shared_tag_index import SharedTagIndex
+
+
+class AnnotationPreparationTests(unittest.TestCase):
+ def setUp(self):
+ self.fixture=SharedAnnotationTests();self.fixture.setUp();self.addCleanup(self.fixture.doCleanups)
+ self.service=self.fixture.service;self.shared=self.fixture.store;self.store=self.fixture.case.store
+ self.service.project_dir=Path(self.service.project_dir).resolve()
+ self.fixture.edit('cell',self.fixture.cell,['whole cell','QC'])
+ self.index=DiskMetadataIndex.build(Path(self.fixture.case.temp.name)/'preparation.sqlite',
+ self.service.rows,self.service.details,self.service.sources,'preparation-source',self.service.project['project_uuid'])
+ self.service.disk_index=self.index
+ self.witness='first witness';self.reads=[]
+ self.tracker=SimpleNamespace(token=lambda:(self.witness,self.proof()['tables']['shared_annotation']['xor']))
+ self.shared.state_generation=self.tracker
+ self.fresh_index()
+ self.proof_patch=patch('workspace_annotation_checkpoint._proof',side_effect=lambda service:self.proof())
+ self.proof_patch.start();self.addCleanup(self.proof_patch.stop)
+
+ def proof(self):
+ return {'project_uuid':self.service.project['project_uuid'],'tables':{
+ 'shared_annotation':{'count':len(self.fixture.records.rows),
+ 'xor':checksum(json.dumps(self.fixture.records.rows,sort_keys=True,default=str))},
+ 'annotation_profile':{'count':len(self.fixture.profiles.rows),
+ 'xor':checksum(json.dumps(self.fixture.profiles.rows,sort_keys=True,default=str))}}}
+
+ def fresh_index(self):
+ previous=getattr(self.shared,'_shared_tag_index',None)
+ if previous is not None:previous.close()
+ def records(keys):
+ self.reads.append(keys)
+ yield from copy.deepcopy(self.fixture.records.rows)
+ index=SharedTagIndex(generation=self.tracker.token,changes=lambda previous:None,
+ records=records,validate=self.shared._index_record_tags)
+ self.shared._shared_tag_index=index;self.shared._shared_tag_index_tracker=self.tracker
+ self.addCleanup(index.close)
+ return index
+
+ def prepare(self):return prepare_annotation_indexes(self.service,self.store,self.shared)
+
+ def test_preparation_reuses_records_and_cell_tags_are_not_copied_to_epochs(self):
+ first=self.prepare();second=self.prepare()
+ self.assertEqual(first['status'],'ready');self.assertEqual(second['status'],'ready')
+ self.assertEqual(len(self.reads),1)
+ self.assertEqual(len(self.fixture.records.rows),1)
+ self.assertEqual(self.fixture.records.rows[0]['target_kind'],'cell')
+ result,_=self.shared._shared_tag_index.matching(_ordered_rows(self.service),{'tag':'whole cell'})
+ self.assertEqual(result,(self.fixture.first,))
+
+ def test_checkpoint_reopens_under_new_witness_only_with_matching_fresh_content(self):
+ self.prepare();saved=save_shared_checkpoint(self.service,self.shared)
+ self.assertTrue(saved['saved'],saved)
+ old=self.shared._shared_tag_index.token
+ index=self.fresh_index();self.witness='new witness after restart';self.reads.clear()
+ restored=restore_shared_checkpoint(self.service,self.shared)
+ self.assertTrue(restored['restored'],restored)
+ self.assertNotEqual(index.token,old)
+ self.assertEqual(self.prepare()['status'],'ready')
+ self.assertEqual(self.reads,[])
+ self.assertEqual(index.summary(_ordered_rows(self.service))['shared_tagged_epochs'],1)
+ self.assertEqual(index.suggestions('whole',30)['tags'][0]['count'],1)
+ self.assertEqual(index.stats['full_builds'],0)
+
+ def test_stale_content_corruption_and_unavailable_proof_rebuild_instead_of_reuse(self):
+ self.prepare();saved=save_shared_checkpoint(self.service,self.shared);self.assertTrue(saved['saved'],saved)
+ self.fresh_index();self.fixture.records.rows[0]['tags']=['new canonical tag']
+ self.assertFalse(restore_shared_checkpoint(self.service,self.shared)['restored'])
+ self.assertEqual(self.prepare()['status'],'ready')
+ current=save_shared_checkpoint(self.service,self.shared);self.assertTrue(current['saved'],current)
+ self.fresh_index()
+ path=self.service.project_dir/'cache'/'annotation-index'/(current['sha256']+'.sqlite')
+ with path.open('ab') as stream:stream.write(b'corrupted derived checkpoint')
+ self.assertFalse(restore_shared_checkpoint(self.service,self.shared)['restored'])
+ self.assertEqual(self.prepare()['status'],'ready')
+ self.fresh_index()
+ with patch('workspace_annotation_checkpoint._proof',return_value=None):
+ self.assertFalse(restore_shared_checkpoint(self.service,self.shared)['restored'])
+
+ def test_edit_during_checkpoint_publication_cannot_publish_stale_membership(self):
+ import workspace_annotation_checkpoint as checkpoint
+ self.prepare()
+ initial=save_shared_checkpoint(self.service,self.shared)
+ self.assertTrue(initial['saved'])
+ pointer=self.service.project_dir/'cache'/'annotation-index'/'current.json'
+ prior=pointer.read_bytes()
+ sha=checkpoint._sha;edited=[]
+ def concurrent_edit(path):
+ result=sha(path)
+ if Path(path).suffix=='.sqlite' and not edited:
+ self.fixture.records.rows[0]['tags']=['changed while sealing']
+ self.fixture.records.rows[0]['revision']+=1
+ edited.append(True)
+ return result
+ with patch.object(checkpoint,'_sha',side_effect=concurrent_edit):
+ saved=save_shared_checkpoint(self.service,self.shared)
+ self.assertTrue(edited)
+ self.assertFalse(saved['saved'])
+ self.assertEqual(pointer.read_bytes(),prior)
+ self.fresh_index()
+ self.assertFalse(restore_shared_checkpoint(self.service,self.shared)['restored'])
+ self.prepare()
+ matches,_=self.shared._shared_tag_index.matching(_ordered_rows(self.service),{'tag':'changed while sealing'})
+ self.assertEqual(matches,(self.fixture.first,))
+ matches,_=self.shared._shared_tag_index.matching(_ordered_rows(self.service),{'tag':'whole cell'})
+ self.assertEqual(matches,())
+
+ def test_changed_source_links_during_preparation_discard_derived_work(self):
+ def progress(stage):
+ if stage=='preparing_tag_suggestions':
+ self.service.rows[self.fixture.first]['cell_uuid']=self.fixture.other_cell
+ with self.assertRaisesRegex(ValueError,'identities changed'):
+ prepare_annotation_indexes(self.service,self.store,self.shared,progress=progress)
+ self.assertEqual(self.service.annotation_preparation['status'],'failed')
+ self.assertIsNone(self.shared._shared_tag_index.connection)
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_annotations.py b/python/tests/test_workspace_annotations.py
index 9dd49b2..f3d9907 100644
--- a/python/tests/test_workspace_annotations.py
+++ b/python/tests/test_workspace_annotations.py
@@ -38,6 +38,17 @@ def edit(self,kind,target,add=(),remove=(),revision=0,author=None):
def epoch(self,key):
return self.store.for_epochs([self.service.rows[key]])[key]
+ def test_small_target_validation_uses_only_requested_identity_lookups(self):
+ class BoundedLookup:
+ def __init__(self,known):self.known=known;self.lookups=[]
+ def __contains__(self,key):self.lookups.append(key);return key in self.known
+ def keys(self):raise AssertionError('Small tag edits must not enumerate project epochs')
+ rows=BoundedLookup({self.first,self.second})
+ with patch.object(self.service,'rows',rows):
+ self.assertEqual(self.store._scope('epoch',[self.first,self.second]),[self.first,self.second])
+ self.assertEqual(rows.lookups,[self.first,self.second])
+ with self.assertRaisesRegex(ValueError,'outside'):self.store._scope('epoch',[str(uuid.uuid4())])
+
def test_predicate_cell_rows_include_cell_tags_outside_epoch_page(self):
self.edit('cell',self.other_cell,['cell badge'])
body={'predicate':{'all':[]},'splits':'cell','summary_only':True}
@@ -119,6 +130,12 @@ def test_project_scope_case_exactness_and_cached_bounded_vocabulary(self):
self.edit('epoch',self.first,['New'],revision=1)
self.assertEqual(self.store.suggestions('new')['tags'][0]['tag'],'New')
+ def test_custom_annotation_reader_keeps_its_autocomplete_policy(self):
+ self.edit('epoch',self.first,['Visible'])
+ with patch.object(self.store,'_rows',return_value=[]), \
+ patch.object(self.store,'_membership_index',side_effect=AssertionError('Do not bypass a custom reader')):
+ self.assertEqual(self.store.suggestions()['tags'],[])
+
def test_cell_direct_effective_and_author_predicates_are_distinct_and_exact(self):
self.edit('cell',self.cell,['cellA']);self.edit('epoch',self.second,['directB'])
def matches(field,value):
@@ -190,8 +207,8 @@ def test_http_save_refreshes_suggestions_and_persists_current_state(self):
read=self.client.get(f'/api/epochs/{self.first}/annotations').get_json()
self.assertIn('Check response',str(read))
events=self.client.get('/api/events?action=shared_annotations_updated&limit=50').get_json()['events']
- self.assertIsNone(receipt['event_uuid'])
- self.assertEqual(events,[])
+ self.assertTrue(receipt['event_uuid'])
+ self.assertEqual([row['event_uuid'] for row in events],[receipt['event_uuid']])
self.assertEqual(self.records.rows[0]['tags'],['Check response'])
def test_bulk_tag_save_targets_selected_epochs_across_cells_only(self):
@@ -206,7 +223,48 @@ def test_bulk_tag_save_targets_selected_epochs_across_cells_only(self):
self.assertEqual([tag['tag'] for tag in self.epoch(key)['epoch_tags']],['Batch review'])
self.assertEqual(self.epoch(key)['cell_tags'],[])
self.assertEqual({row['target_uuid'] for row in self.records.rows},set(ids))
- self.assertEqual(self.case.events.rows,[])
+ self.assertEqual(len(self.case.events.rows),1)
+
+ def test_event_failure_rolls_back_rows_profiles_and_does_not_queue_backup(self):
+ from unittest.mock import Mock
+ self.store.on_commit=Mock()
+ with patch.object(self.case.events,'insert1',side_effect=OSError('Event storage failed')):
+ with self.assertRaisesRegex(OSError,'Event storage failed'):self.edit('epoch',self.first,['durable'])
+ self.assertEqual(self.records.rows,[])
+ self.assertEqual(self.profiles.rows,[])
+ self.store.on_commit.assert_not_called()
+ result=self.edit('epoch',self.first,['durable'])
+ self.store.on_commit.assert_called_once_with()
+ self.assertEqual(result['event_uuid'],self.case.events.rows[0]['event_uuid'])
+
+ def test_http_annotation_ack_queues_backup_and_reports_failure_independently(self):
+ from unittest.mock import Mock
+ save=Mock();self.service.dj.Schema=object()
+ with patch('workspace_state_snapshot.save',save), \
+ patch('workspace_annotation_preparation.prepare_project_annotations',return_value={'status':'unavailable'}):
+ app=create_app(self.case.temp.name,self.case.temp.name,service=self.service,store=self.case.store,
+ explorer_history=self.case.explorer_history,data_stores=self.case.data_stores,
+ protocol_suggestions=self.case.protocol_suggestions,shared_annotations=self.store)
+ scheduler=app.extensions['backup_scheduler'];scheduler.delay=60;scheduler.max_delay=60
+ self.addCleanup(lambda:scheduler.close(flush=False))
+ self.addCleanup(app.extensions['app_state_session_lock'].close)
+ save.reset_mock();client=app.test_client()
+ body={'target_kind':'epoch','target_uuids':[self.first],'profile_uuid':self.author,
+ 'tags_add':['committed'],'expected_revisions':{self.first:0}}
+ response=client.post('/api/annotations',json=body,headers=self.headers)
+ self.assertEqual(response.status_code,200,response.get_json())
+ self.assertEqual(response.get_json()['persistence']['database'],'committed')
+ self.assertEqual(response.get_json()['persistence']['backup']['status'],'pending')
+ save.assert_not_called()
+ self.assertEqual(self.records.rows[0]['tags'],['committed'])
+ self.assertEqual(self.case.events.rows[0]['event_uuid'],response.get_json()['event_uuid'])
+ save.side_effect=OSError('Disk full')
+ with self.assertRaises(OSError):scheduler.flush()
+ self.assertEqual(client.get('/api/backup/status').get_json()['status'],'degraded')
+ self.assertEqual(client.post('/api/annotations',json=body,headers=self.headers).status_code,409)
+ self.assertEqual(scheduler.status()['requested_sequence'],1)
+ save.side_effect=None;scheduler.flush()
+ self.assertEqual(client.get('/api/backup/status').get_json()['status'],'current')
def test_unchanged_blank_operation_writes_no_profile_or_event(self):
result=self.edit('epoch',self.first)
diff --git a/python/tests/test_workspace_api.py b/python/tests/test_workspace_api.py
index d067368..5d2e913 100644
--- a/python/tests/test_workspace_api.py
+++ b/python/tests/test_workspace_api.py
@@ -5,6 +5,7 @@
"""
import hashlib
import json
+import os
from pathlib import Path
import tempfile
import unittest
@@ -124,6 +125,9 @@ def test_browser_metadata_preserves_exact_source_ticks(self):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
+ isolated_index = patch.dict(os.environ, {'RIEKE_PROJECT_INDEX': str(Path(self.temp.name) / 'user-state/project-index.json')})
+ isolated_index.start()
+ self.addCleanup(isolated_index.stop)
self.service = FixtureService(self.temp.name)
self.curation = Table(('project_uuid', 'protocol_uuid', 'epoch_uuid'))
self.datasets = Table(('project_uuid', 'dataset_uuid'))
diff --git a/python/tests/test_workspace_app_routes.py b/python/tests/test_workspace_app_routes.py
index 16898b5..3962a6a 100644
--- a/python/tests/test_workspace_app_routes.py
+++ b/python/tests/test_workspace_app_routes.py
@@ -1,5 +1,6 @@
"""Local update checks and transfer job lifecycle without touching user data."""
import threading
+import os
import time
import types
import unittest
@@ -46,6 +47,49 @@ def test_manual_retry_bypasses_cache_and_install_requires_capability(self):
self.assertEqual(response.status_code, 409)
module.stage_release.assert_not_called()
+ def test_managed_check_downloads_automatically_once_without_blocking(self):
+ finish = threading.Event()
+ started = threading.Event()
+ def stage(**kwargs):
+ started.set()
+ finish.wait(3)
+ return {'version': '0.2.0', 'state': 'staged'}
+ stage_mock = Mock(side_effect=lambda *args, **kwargs: stage(**kwargs))
+ status = {'state': 'update_available', 'available': '0.2.0', 'can_stage': True}
+ module = types.SimpleNamespace(check_for_updates=Mock(return_value=status), stage_release=stage_mock)
+ with patch.dict('sys.modules', workspace_updates=module), patch.dict(os.environ, RIEKE_INSTALLATION_ROOT='/installation'):
+ try:
+ response = self.post('/api/app/updates/check', {})
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(started.wait(1))
+ self.assertEqual(response.json['download']['state'], 'running')
+ self.post('/api/app/updates/check', {})
+ self.assertEqual(stage_mock.call_count, 1)
+ finally:
+ finish.set()
+ for _ in range(100):
+ result = self.client.get('/api/app/updates/download').json
+ if result['state'] != 'running': break
+ time.sleep(.01)
+ self.assertEqual(result['state'], 'complete')
+ self.post('/api/app/updates/check', {})
+ self.assertEqual(stage_mock.call_count, 1)
+
+ def test_automatic_download_requires_managed_trust_and_fresh_unstaged_release(self):
+ base = {'state': 'update_available', 'available': '0.2.0', 'can_stage': True}
+ module = types.SimpleNamespace(check_for_updates=Mock(), stage_release=Mock())
+ with patch.dict('sys.modules', workspace_updates=module), patch.dict(os.environ, RIEKE_INSTALLATION_ROOT='/installation'):
+ for status in ({**base, 'can_stage': False}, {**base, 'check_error': 'Offline'},
+ {**base, 'staged_version': '0.2.0'}, {**base, 'state': 'up_to_date'}):
+ module.check_for_updates.return_value = status
+ self.assertEqual(self.post('/api/app/updates/check', {}).status_code, 200)
+ module.stage_release.assert_not_called()
+ with patch.dict('sys.modules', workspace_updates=module), patch.dict(os.environ):
+ os.environ.pop('RIEKE_INSTALLATION_ROOT', None)
+ module.check_for_updates.return_value = base
+ self.post('/api/app/updates/check', {})
+ module.stage_release.assert_not_called()
+
def test_transfer_serializes_jobs_and_reports_success_only_after_completion(self):
release = threading.Event()
module = types.SimpleNamespace(prepare_project=lambda *args: (release.wait(3), {'verified': True})[1], restore_project=lambda *args: {})
@@ -72,7 +116,7 @@ def test_relocation_requires_local_explicit_paths_and_reports_destination(self):
module = types.SimpleNamespace(relocate_project=move)
path = '/api/projects/relocate'
body = {'directory': '/received/study', 'destination': '/preferred/study'}
- with patch.dict('sys.modules', workspace_portability=module):
+ with patch.dict('sys.modules', workspace_portability=module), patch('workspace_startup_registry.remember_project_path') as remember:
self.assertEqual(self.client.post(path, json=body).status_code, 403)
self.assertEqual(self.client.post(path, json=body, headers={**self.headers, 'Origin': 'https://foreign.test'}).status_code, 403)
self.assertEqual(self.post(path, body, environ_overrides={'REMOTE_ADDR': '192.0.2.1'}).status_code, 403)
@@ -85,6 +129,22 @@ def test_relocation_requires_local_explicit_paths_and_reports_destination(self):
self.assertEqual(result.json['directory'], '/preferred/study')
self.assertTrue(result.json['moved'])
move.assert_called_once_with('/received/study', '/preferred/study')
+ remember.assert_called_once_with('/preferred/study', previous_directory='/received/study')
+
+ def test_verified_restore_registers_exact_destination(self):
+ restored = {'directory': '/independent/restored study', 'verified': True}
+ module = types.SimpleNamespace(restore_project=Mock(return_value=restored), prepare_project=Mock())
+ with patch.dict('sys.modules', workspace_portability=module), patch('workspace_startup_registry.remember_project_path') as remember:
+ response = self.post('/api/projects/restore-transfer', {'directory': '/received/copy',
+ 'destination': '/independent/restored study'})
+ path = '/api/projects/transfers/' + response.json['job_id']
+ for _ in range(100):
+ result = self.client.get(path).json
+ if result['state'] != 'running':
+ break
+ time.sleep(.01)
+ self.assertEqual(result['state'], 'complete')
+ remember.assert_called_once_with('/independent/restored study')
def test_relocation_failure_is_actionable_and_never_reports_success(self):
module = types.SimpleNamespace(relocate_project=Mock(side_effect=ValueError('Close the project before moving its folder')))
@@ -94,6 +154,22 @@ def test_relocation_failure_is_actionable_and_never_reports_success(self):
self.assertIn('Close the project', result.json['error'])
self.assertNotIn('moved', result.json)
+ def test_completed_restore_is_reported_when_local_index_cannot_save(self):
+ restored = {'directory': '/completed/restored study', 'verified': True}
+ module = types.SimpleNamespace(restore_project=Mock(return_value=restored), prepare_project=Mock())
+ with patch.dict('sys.modules', workspace_portability=module), patch('workspace_startup_registry.remember_project_path', side_effect=OSError('Profile disk is full')):
+ response = self.post('/api/projects/restore-transfer', {'directory': '/received/copy',
+ 'destination': restored['directory']})
+ path = '/api/projects/transfers/' + response.json['job_id']
+ for _ in range(100):
+ result = self.client.get(path).json
+ if result['state'] != 'running':
+ break
+ time.sleep(.01)
+ self.assertEqual(result['state'], 'complete')
+ self.assertTrue(result['result']['verified'])
+ self.assertIn('registry_warning', result['result'])
+
def test_transfer_failure_and_invalid_paths_never_report_success(self):
def fail(*args): raise ValueError('Stop the project service first')
module = types.SimpleNamespace(prepare_project=fail, restore_project=fail)
diff --git a/python/tests/test_workspace_backend_responsiveness.py b/python/tests/test_workspace_backend_responsiveness.py
new file mode 100644
index 0000000..e33a713
--- /dev/null
+++ b/python/tests/test_workspace_backend_responsiveness.py
@@ -0,0 +1,294 @@
+"""Bounded read work with immutable-index and durable-mutation safeguards."""
+import copy
+import sys
+from pathlib import Path
+from types import SimpleNamespace
+import unittest
+from unittest.mock import Mock, patch
+
+from workspace_api import create_app
+from workspace_disk_index import DiskMetadataIndex
+from workspace_service import _SourceDetails
+from workspace_tree import joint_id
+from workspace_tree_pages import TreePages, StaleTreePage, _retained_scope_bytes, TREE_SCOPE_CACHE_OVERHEAD
+import test_workspace_api as api_fixture
+
+
+class BackendResponsivenessTests(unittest.TestCase):
+ def setUp(self):
+ self.fixture = api_fixture.WorkspaceAPITests()
+ self.fixture.setUp()
+ self.addCleanup(self.fixture.doCleanups)
+ self.service = self.fixture.service
+ self.index = DiskMetadataIndex.build(Path(self.fixture.temp.name)/'responsive.sqlite',
+ self.service.rows, self.service.details, self.service.sources, 'responsive-generation',
+ self.service.project['project_uuid'])
+ self.service.disk_index = self.index
+ self.service.details = self.index.details
+
+ def test_fast_preview_avoids_scoped_catalog_and_values_but_keeps_exact_membership(self):
+ predicate = {'field':'parameters/example', 'operator':'eq', 'value':0}
+ splits = joint_id(['cell', 'parameters/example'])
+ expected = self.service.explore_preview(predicate, splits, include_tree=False)
+ original_catalog = self.index.catalog
+ def registered_only(*args, **kwargs):
+ self.assertFalse(args or kwargs, 'Fast preview must not summarize a matched scope')
+ return original_catalog()
+ with patch.object(self.index, 'catalog', side_effect=registered_only), \
+ patch.object(self.index, 'values', side_effect=AssertionError('No field projection')):
+ fast = self.service.explore_preview(predicate, splits, include_tree=False, include_catalog_summary=False)
+ for field in ('membership', 'matched_count', 'total_source', 'tree_revision', 'tree', 'source_scope'):
+ self.assertEqual(fast[field], expected[field])
+ self.assertFalse(fast['catalog']['summary_available'])
+ self.assertNotIn('suggestions', fast['catalog'])
+ for field in fast['catalog']['fields']:
+ self.assertNotIn('distinct_count', field)
+ self.assertNotIn('examples', field)
+ response = self.fixture.client.post('/api/explore/preview', json={
+ 'predicate':predicate, 'splits':splits, 'summary_only':True, 'catalog_summary':False}, headers=self.fixture.headers)
+ self.assertEqual(response.status_code, 200, response.get_json())
+ self.assertEqual(response.get_json()['tree_revision'], fast['tree_revision'])
+
+ def test_fast_save_retains_complete_recipe_and_rejects_invalid_projection_options(self):
+ body = {'predicate':{'all':[]}, 'splits':'cell', 'summary_only':True, 'catalog_summary':False}
+ response = self.fixture.client.post('/api/explore/revisions', json=body, headers=self.fixture.headers)
+ self.assertEqual(response.status_code, 201, response.get_json())
+ revision = response.get_json()
+ full = self.fixture.client.get('/api/explore/revisions/'+revision['revision_uuid']).get_json()
+ self.assertEqual({member['uuid'] for member in full['recipe']['epochs']}, set(self.service.rows))
+ for value in (None, 'false', 0):
+ invalid = self.fixture.client.post('/api/explore/preview', json={**body,'catalog_summary':value}, headers=self.fixture.headers)
+ self.assertEqual(invalid.status_code,400)
+ rendered = self.fixture.client.post('/api/explore/preview', json={**body,'summary_only':False}, headers=self.fixture.headers)
+ self.assertEqual(rendered.status_code,400)
+
+ def test_repeated_tree_scope_reuses_projection_and_membership_revision_across_adapters(self):
+ pager = TreePages(self.service)
+ body = {'splits':'cell,parameters/example'}
+ root = pager.page(body)
+ with patch.object(self.service, '_tree_rows', side_effect=AssertionError('No membership rescan')), \
+ patch.object(self.index, 'values', side_effect=AssertionError('No projection reread')), \
+ patch('workspace_tree_pages.selection_revision', side_effect=AssertionError('No membership rehash')):
+ self.assertEqual(TreePages(self.service).page(body), root)
+ child = {**body, 'path':root['branches'][0]['path'], 'revision':root['revision']}
+ expected = pager.page(child)
+ with patch.object(self.index, 'values', side_effect=AssertionError('No projection reread')):
+ self.assertEqual(pager.page(child), expected)
+ self.service._fingerprints[self.service.ids[0]] = 'c'*64
+ with self.assertRaises(StaleTreePage):
+ pager.page({**body, 'revision':root['revision']})
+ # Every hit still checks the sealed file signature.
+ with self.index.path.open('ab') as stream:
+ stream.write(b'changed')
+ with self.assertRaises(ValueError):
+ pager.page(body)
+
+ def test_source_eligibility_and_protocol_binding_invalidate_cached_selection(self):
+ pager = TreePages(self.service)
+ source_root = pager.page({'splits':''})
+ self.service.set_source_state_provider(lambda: {'a'*64:{'query_excluded':True}})
+ with self.assertRaises(StaleTreePage):
+ pager.page({'splits':'', 'revision':source_root['revision']})
+ self.assertEqual(pager.page({'splits':''})['total_epochs'],0)
+ protocol = self.service.protocol_id
+ root = pager.page({'protocol_uuid':protocol, 'splits':''})
+ key = self.service.ids[0]
+ binding = {'version':1, 'revision_uuid':'new-binding', 'recipe':{
+ 'epochs':[{'uuid':key, 'metadata_hash':self.service._fingerprints[key]}],
+ 'source_revisions':['a'*64], 'predicate':{'all':[]}, 'tree_view':{'fields':[]},
+ 'splits':'', 'name':'One epoch'}}
+ self.service.set_binding_provider(lambda identity: binding)
+ with self.assertRaises(StaleTreePage):
+ pager.page({'protocol_uuid':protocol, 'splits':'', 'revision':root['revision']})
+ self.assertEqual(pager.page({'protocol_uuid':protocol, 'splits':''})['total_epochs'],1)
+
+ def test_live_tag_scope_bypasses_cache_and_changes_revision(self):
+ selected = {self.service.ids[0]}
+ self.service.shared_annotations = SimpleNamespace(for_epochs=lambda rows: {
+ row['epoch_uuid']:{'effective_tags':[{'tag':'checked'}] if row['epoch_uuid'] in selected else []}
+ for row in rows})
+ pager = TreePages(self.service)
+ body = {'splits':'cell', 'filters':{'tag':'checked'}}
+ first = pager.page(body)
+ selected.clear()
+ with self.assertRaises(StaleTreePage):
+ pager.page({**body, 'revision':first['revision']})
+ self.assertEqual(pager.page(body)['total_epochs'],0)
+
+ def test_scope_retention_is_bounded_and_row_generation_rebuilds(self):
+ pager = TreePages(self.service)
+ for value in range(12):
+ pager.page({'splits':'', 'predicate':{'field':'parameters/example','operator':'eq','value':value}})
+ cache = self.service._tree_page_scope_cache[1]
+ self.assertLessEqual(len(cache),8)
+ self.assertLessEqual(sum(entry[1] for entry in cache.values()),2_000_000)
+ body = {'splits':'cell'}
+ root = pager.page(body)
+ self.service.rows = copy.deepcopy(self.service.rows)
+ with patch.object(pager, '_build_scope', wraps=pager._build_scope) as rebuild:
+ self.assertEqual(pager.page(body),root)
+ rebuild.assert_called_once()
+
+ def test_detail_dispatch_touches_exactly_the_owning_source(self):
+ rows, sources = {}, {}
+ for number in range(500):
+ identity, source = str(number), 'source-'+str(number)
+ rows[identity] = {'source_sha256':source}
+ sources[source] = Mock()
+ sources[source].__getitem__ = Mock(return_value={'owner':source})
+ details = _SourceDetails(rows,sources)
+ self.assertEqual(details['499'],{'owner':'source-499'})
+ self.assertEqual(list(details),list(rows))
+ self.assertEqual(sum(source.__getitem__.call_count for source in sources.values()),1)
+ with self.assertRaises(KeyError):details['unknown']
+
+ def test_large_vector_scope_serves_exact_values_without_cache_retention(self):
+ vector = [float(value) / 3 for value in range(2000)]
+ projected = {key:{'parameters/example':vector} for key in self.service.rows}
+ pager = TreePages(self.service)
+ body = {'splits':'parameters/example'}
+ # Current catalog discovery excludes very large vectors. Supply a large
+ # projection through the index interface to keep cache safety independent
+ # of that separate discovery policy and future index formats.
+ with patch('workspace_tree_pages.TREE_SCOPE_BYTE_BUDGET',32*1024), \
+ patch.object(self.index,'values',return_value=projected), \
+ patch.object(pager,'_build_scope',wraps=pager._build_scope) as builds:
+ first, second = pager.page(body), pager.page(body)
+ self.assertEqual(first,second)
+ self.assertEqual(first['branches'][0]['value'],vector)
+ self.assertEqual(first['total_epochs'],len(self.service.rows))
+ self.assertEqual(builds.call_count,2)
+ self.assertEqual(self.service._tree_page_scope_cache[1],{})
+
+ def test_aggregate_byte_budget_evicts_individually_cacheable_scopes(self):
+ pager = TreePages(self.service)
+ first, second = {'splits':'cell'}, {'splits':'parameters/example'}
+ sizes = [_retained_scope_bytes(pager._build_scope(body),self.service.rows,1024*1024)
+ for body in (first,second)]
+ # Each scope fits, but the pair cannot remain resident together.
+ budget = sys.getsizeof(self.service._fingerprints) + TREE_SCOPE_CACHE_OVERHEAD + max(sizes) + min(sizes)//2
+ with patch('workspace_tree_pages.TREE_SCOPE_BYTE_BUDGET',budget), \
+ patch.object(pager,'_build_scope',wraps=pager._build_scope) as builds:
+ pager.page(first)
+ pager.page(second)
+ self.assertEqual(len(self.service._tree_page_scope_cache[1]),1)
+ pager.page(second)
+ self.assertEqual(builds.call_count,2)
+ pager.page(first)
+ self.assertEqual(builds.call_count,3)
+ cache = self.service._tree_page_scope_cache[1]
+ self.assertLessEqual(sum(entry[2] for entry in cache.values()),max(sizes))
+
+ def test_byte_estimate_distinguishes_borrowed_rows_from_owned_copies_and_values(self):
+ row = copy.deepcopy(self.service.rows[self.service.ids[0]])
+ vector = [float(number) for number in range(2000)]
+ row['large_metadata'] = vector
+ base = {row['epoch_uuid']:row}
+ def scope(rows, values=None):
+ return (rows,{},values or {},{},[], 'revision')
+ borrowed = _retained_scope_bytes(scope([row]),base,4096)
+ decorated = {**row, 'curation':{'included':True,'tags':[]}}
+ owned = _retained_scope_bytes(scope([decorated]),base,4096)
+ self.assertLess(borrowed,owned) # The new row and curation allocate memory.
+ self.assertLess(owned,4096) # Shared base metadata is not copied.
+ copied = {**decorated,'large_metadata':list(vector)}
+ self.assertGreater(_retained_scope_bytes(scope([copied]),base,4096),4096)
+ projection = {row['epoch_uuid']:{'field':list(vector)}}
+ self.assertGreater(_retained_scope_bytes(scope([row],projection),base,4096),4096)
+
+
+class RecoveryHookTests(unittest.TestCase):
+ def setUp(self):
+ self.fixture = api_fixture.WorkspaceAPITests()
+ self.fixture.setUp()
+ self.addCleanup(self.fixture.doCleanups)
+ self.fixture.service.dj.Schema = object()
+ self.save = Mock()
+ with patch('workspace_annotations.SharedAnnotations', return_value=None), \
+ patch('workspace_state_snapshot.save', self.save):
+ self.app = create_app(self.fixture.temp.name, self.fixture.temp.name,
+ service=self.fixture.service, store=self.fixture.store,
+ explorer_history=self.fixture.explorer_history, data_stores=self.fixture.data_stores,
+ protocol_suggestions=self.fixture.protocol_suggestions)
+ self.addCleanup(self.app.extensions['app_state_session_lock'].close)
+ self.addCleanup(lambda:self.app.extensions['backup_scheduler'].close(flush=False))
+ self.client = self.app.test_client()
+ self.assertEqual(self.save.call_count,1) # Startup still protects state.
+ self.save.reset_mock()
+
+ def test_native_close_refuses_shutdown_when_mandatory_backup_fails(self):
+ self.fixture.service.config['connection']={'credential_provider':{'kind':'native-project'}}
+ with patch('workspace_annotations.SharedAnnotations',return_value=None), \
+ patch('workspace_state_snapshot.save',self.save):
+ app=create_app(self.fixture.temp.name,self.fixture.temp.name,
+ service=self.fixture.service,store=self.fixture.store,
+ explorer_history=self.fixture.explorer_history,data_stores=self.fixture.data_stores,
+ protocol_suggestions=self.fixture.protocol_suggestions)
+ self.addCleanup(app.extensions['app_state_session_lock'].close)
+ scheduler=app.extensions['backup_scheduler']
+ self.addCleanup(lambda:scheduler.close(flush=False))
+ self.save.side_effect=OSError('Disk full')
+ with patch.object(app.extensions['h5_inbox'],'start') as resume, \
+ patch('workspace_native_mysql.stop_native_database') as stop:
+ with self.assertRaisesRegex(OSError,'Disk full'):app.extensions['desktop_stop_database']()
+ stop.assert_not_called();resume.assert_called_once()
+ self.assertFalse(scheduler.stopped)
+ self.assertEqual(scheduler.status()['status'],'degraded')
+
+ def test_real_read_only_posts_do_not_capture_recovery_state(self):
+ body = {'predicate':{'all':[]},'splits':'cell','summary_only':True}
+ preview = self.client.post('/api/explore/preview',json=body,headers=self.fixture.headers)
+ self.assertEqual(preview.status_code,200,preview.get_json())
+ revision = preview.get_json()['tree_revision']
+ for path, content in (('/api/tree-pages',{'splits':'cell'}),
+ ('/api/explore/epochs',{'predicate':{'all':[]},'splits':'cell','revision':revision})):
+ response = self.client.post(path,json=content,headers=self.fixture.headers)
+ self.assertEqual(response.status_code,200,response.get_json())
+ self.save.assert_not_called()
+
+ def test_route_classification_is_conservative_and_noops_remain_checkpointed(self):
+ # Exercise every explicitly exempt endpoint through Flask's actual hook,
+ # and ensure unknown future write routes default to protection.
+ readonly = ('explorer_preview','tree_page','matching_epochs','annotation_batch_read',
+ 'tag_import_preview','preview_source_propagation','resolve_search_preset','compare_protocol_revision')
+ for index, endpoint in enumerate(readonly):
+ self.app.add_url_rule('/api/test-read/'+str(index),endpoint=endpoint,methods=['POST'])
+ self.app.view_functions[endpoint] = lambda: ({'read':True},200)
+ for method in ('POST','PUT','PATCH','DELETE'):
+ self.app.add_url_rule('/api/test-write/'+method,endpoint='new-write-'+method,
+ view_func=lambda: ({'changed':False},200),methods=[method])
+ for index in range(len(readonly)):
+ self.assertEqual(self.client.post('/api/test-read/'+str(index),headers=self.fixture.headers).status_code,200)
+ self.save.assert_not_called()
+ for method in ('POST','PUT','PATCH','DELETE'):
+ self.assertEqual(self.client.open('/api/test-write/'+method,method=method,headers=self.fixture.headers).status_code,200)
+ self.assertEqual(self.save.call_count,4)
+
+ def test_search_run_still_checkpoints_its_recorded_last_run(self):
+ runs = Mock()
+ runs.record_run.return_value = {'epoch_count':2, 'cell_count':2}
+ self.app.extensions['search_presets'] = runs
+ response = self.client.post('/api/explore/run', json={
+ 'predicate':{'all':[]}, 'splits':'cell', 'catalog_summary':False}, headers=self.fixture.headers)
+ self.assertEqual(response.status_code,200,response.get_json())
+ runs.record_run.assert_called_once()
+ self.save.assert_called_once()
+
+ def test_committed_mutation_backup_failure_remains_visible_and_retry_is_protected(self):
+ body = self.fixture.curation_body({'tags_add':['retained']})
+ self.save.side_effect = OSError('full disk')
+ with self.assertLogs(self.app.logger,level='ERROR'):
+ response = self.client.post(self.fixture.base+'/curation',json=body,headers=self.fixture.headers)
+ self.assertEqual(response.status_code,507,response.get_json())
+ self.assertTrue(response.get_json()['saved'])
+ self.assertTrue(self.fixture.curation.rows)
+ self.save.side_effect = None
+ body['expected_revisions'] = {row['epoch_uuid']:row['revision'] for row in self.fixture.curation.rows}
+ body['query_revision'] = self.fixture.revision()
+ response = self.client.post(self.fixture.base+'/curation',json=body,headers=self.fixture.headers)
+ self.assertEqual(response.status_code,200,response.get_json())
+ self.assertEqual(self.save.call_count,2)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_workspace_backup_scheduler.py b/python/tests/test_workspace_backup_scheduler.py
new file mode 100644
index 0000000..203df5a
--- /dev/null
+++ b/python/tests/test_workspace_backup_scheduler.py
@@ -0,0 +1,65 @@
+import threading
+import time
+import unittest
+from unittest.mock import Mock
+
+from workspace_backup_scheduler import BackupScheduler
+
+
+class BackupSchedulerTests(unittest.TestCase):
+ def scheduler(self,capture,**kwargs):
+ value=BackupScheduler(capture,threading.RLock(),**kwargs)
+ self.addCleanup(lambda:value.close(flush=False))
+ return value
+
+ def wait_for(self,predicate):
+ deadline=time.monotonic()+2
+ while not predicate() and time.monotonic()limit}
+
+ def seed_authors(self):
+ for profile,name in ((identity(101),'Current profile one'),(identity(102),'Current profile two')):
+ self.connection.query('INSERT INTO recording_workspace.annotation_profile VALUES (%s,%s,%s)',(self.project_id,profile,name))
+ cases=[('cell',201,101,['QC','Straße','é','İ'],'Cell-era name'),
+ ('epoch',1,101,['QC','STRASSE','e\u0301','i'],'Earlier epoch alias'),
+ ('epoch',1,102,['QC','STRASSE','é'],'Second historical scientist'),
+ ('epoch',2,101,['QC','Straße','é','İ'],'Latest epoch alias'),
+ ('epoch',3,101,['other'],'Later unrelated alias'),
+ ('epoch',4,102,['qc','😀'*255],'Other historical scientist'),
+ ('cell',202,101,['STRASSE'],'Later cell alias')]
+ cases.append(('epoch',5,101,['ß','SS','Σ','σ','ς','ΐ'*255],'Unicode author'))
+ for kind,target,profile,tags,name in cases:self.write(kind,identity(target),identity(profile),tags,name)
+
+ def test_autocomplete_matches_canonical_unicode_counts_and_historical_authors(self):
+ self.seed_authors();self.prepare()
+ with patch.object(self.shared,'_membership_index',side_effect=AssertionError('Native autocomplete must not open SQLite')):
+ for query in ('',' qC ','STRASSE','strass','é','e\u0301','i','i\u0307','😀','ss','σ','ς','ΐ'*255,'no match'):
+ for limit in (1,3,100):
+ with self.subTest(query=query,limit=limit):
+ result=self.shared.suggestions(query,limit);expected=self.oracle(query,limit)
+ self.assertEqual({key:result[key] for key in expected},expected)
+ qc=next(row for row in self.shared.suggestions('',100)['tags'] if row['tag']=='QC')
+ self.assertEqual(qc['count'],3) # One epoch has two profiles, still one target.
+ self.assertEqual(qc['authors'],[{'profile_uuid':identity(101),'display_name':'Latest epoch alias'},
+ {'profile_uuid':identity(102),'display_name':'Second historical scientist'}])
+ self.connection.query('UPDATE recording_workspace.annotation_profile SET display_name=%s WHERE project_uuid=%s',('Renamed current profile',self.project_id))
+ self.assertEqual(self.shared.suggestions('QC',100)['tags'],self.oracle('QC',100)['tags'])
+ self.write('epoch',identity(2),identity(101),['new'],'New saved author')
+ self.assertEqual(self.shared.suggestions('QC',100)['tags'],self.oracle('QC',100)['tags'])
+
+ def test_counts_concurrent_profiles_old_snapshot_rollback_and_duplicate_update(self):
+ import pymysql
+ import threading
+ import time
+ self.prepare()
+ second=NativeConnection(pymysql.connect(**native.connection_parameters(self.root),autocommit=True))
+ self.addCleanup(second._conn.close)
+ target=identity(70);tag='same target'
+ statement=('INSERT INTO recording_workspace.shared_annotation '
+ '(project_uuid,target_kind,target_uuid,profile_uuid,tags,author_name,revision,updated_at) VALUES (%s,%s,%s,%s,%s,%s,1,%s)')
+ def args(profile):return (self.project_id,'epoch',target,profile,json.dumps([tag]),'Writer',dt.datetime(2026,9,30,12))
+ second.query('START TRANSACTION WITH CONSISTENT SNAPSHOT');second.in_transaction=True
+ second.query(f'SELECT COUNT(*) FROM recording_workspace.{TABLE}').fetchone()
+ self.write('epoch',target,identity(101),[tag],'Writer')
+ second.query(statement,args(identity(102)));second._conn.commit();second.in_transaction=False
+ def counts():
+ rows=self.connection.query(f'SELECT tag,target_count,fold_key FROM recording_workspace.{DICTIONARY} WHERE project_uuid=%s',(self.project_id,),as_dict=True).fetchall()
+ return {bytes(row['tag']).decode():row for row in rows}
+ self.assertEqual(counts()[tag]['target_count'],1)
+ self.assertEqual(len(self.shared.suggestions(tag,30)['tags'][0]['authors']),2)
+ before=counts();token=self.shared.generation_token()
+ with self.assertRaises(Exception):
+ self.connection.query('UPDATE recording_workspace.shared_annotation SET tags=%s WHERE project_uuid=%s AND profile_uuid=%s',
+ (json.dumps([tag,tag]),self.project_id,identity(101)))
+ self.assertEqual(counts(),before);self.assertEqual(self.shared.generation_token(),token)
+ with self.assertRaisesRegex(RuntimeError,'rollback'):
+ with self.connection.transaction:
+ self.write('epoch',target,identity(103),[tag,'rolled back'],'Writer')
+ self.assertEqual(counts()[tag]['target_count'],1)
+ raise RuntimeError('rollback')
+ self.assertEqual(counts(),before)
+ # Two writers overlap while the second has its own older snapshot.
+ target=identity(71);errors=[];started=threading.Event()
+ self.connection._conn.begin();self.connection.in_transaction=True
+ self.connection.query(statement,args(identity(101)))
+ def insert_second():
+ try:
+ started.set()
+ with second.transaction:second.query(statement,args(identity(102)))
+ except BaseException as error:errors.append(error)
+ worker=threading.Thread(target=insert_second);worker.start();self.assertTrue(started.wait(1));time.sleep(.03)
+ self.connection._conn.commit();self.connection.in_transaction=False
+ worker.join(5);self.assertFalse(worker.is_alive());self.assertEqual(errors,[])
+ self.assertEqual(counts()[tag]['target_count'],2)
+ fold=counts()[tag]['fold_key']
+ self.connection.query('UPDATE recording_workspace.shared_annotation SET tags=JSON_ARRAY(%s,%s) WHERE project_uuid=%s AND target_uuid=%s AND profile_uuid=%s',
+ (tag,'new tag',self.project_id,target,identity(101)))
+ self.assertEqual(counts()[tag]['fold_key'],fold)
+ self.assertIsNone(counts()['new tag']['fold_key'])
+ self.assertEqual(self.shared.suggestions('NEW TAG',30)['tags'][0]['count'],1)
+ self.assertIsNotNone(counts()['new tag']['fold_key'])
+ self.connection.query('DELETE FROM recording_workspace.shared_annotation WHERE project_uuid=%s',(self.project_id,))
+ self.assertEqual(counts(),{})
+ self.assertEqual(self.connection.query(f'SELECT COUNT(*) FROM recording_workspace.{AUTHORS} WHERE project_uuid=%s',(self.project_id,)).fetchone()[0],0)
+
+ def test_lifecycle_bootstrap_checkpoint_reopen_and_dropped_trigger_rebuild(self):
+ self.write('epoch',identity(1),identity(101),['before'],'Original author')
+ first=self.prepare(reuse=True);self.assertFalse(first['reused'])
+ lookup=self.shared.native_tag_lookup;self.assertEqual(lookup.targets('before'),{('epoch',identity(1))})
+ self.write('epoch',identity(1),identity(101),['after'],'Edited author')
+ self.assertEqual(self.connection.query(f'SELECT dirty FROM recording_workspace.{MARKER} WHERE project_uuid=%s',(self.project_id,)).fetchone()[0],1)
+ second=self.prepare();self.assertTrue(second['reused']);self.assertIs(self.shared.native_tag_lookup,lookup)
+ self.assertEqual(self.connection.query(f'SELECT dirty FROM recording_workspace.{MARKER} WHERE project_uuid=%s',(self.project_id,)).fetchone()[0],0)
+ # A fresh annotation facade simulates reopen without retaining the lookup.
+ self.shared=SharedAnnotations(self.service,tables=(None,None,None))
+ self.shared.state_generation=generation_bootstrap(self.connection,self.project_id)
+ reopened=self.prepare(reuse=True);self.assertTrue(reopened['reused'])
+ original=self.shared.native_tag_lookup
+ trigger=next(name for name in TRIGGER_MANIFEST if name.endswith('_au'))
+ self.connection.query('DROP TRIGGER recording_workspace.'+trigger)
+ self.connection.query('UPDATE recording_workspace.shared_annotation SET tags=%s,revision=revision+1 WHERE project_uuid=%s',
+ (json.dumps(['written during gap']),self.project_id))
+ self.assertNotEqual(self.shared.generation_token().authority,original.authority)
+ rebuilt=self.prepare();self.assertFalse(rebuilt['reused'])
+ self.assertIsNot(self.shared.native_tag_lookup,original)
+ self.assertEqual(self.shared.native_tag_lookup.targets('after'),set())
+ self.assertEqual(self.shared.native_tag_lookup.targets('written during gap'),{('epoch',identity(1))})
+ self.assertTrue(self.shared.native_tag_lookup.validate_current())
+
+ def test_legacy_lookup_migration_and_compact_table_truncation_rebuild(self):
+ from workspace_native_tag_lookup import LEGACY_TRIGGER_MANIFEST
+ self.write('epoch',identity(1),identity(101),['Straße'],'Historical author');self.prepare()
+ for name in TRIGGER_MANIFEST:self.connection.query('DROP TRIGGER recording_workspace.'+name)
+ for table in (DICTIONARY,AUTHORS):self.connection.query('DROP TABLE recording_workspace.'+table)
+ self.connection.query(f'ALTER TABLE recording_workspace.{TABLE} DROP INDEX by_tag,ADD INDEX by_tag(project_uuid,tag,target_kind,target_uuid)')
+ for name,spec in LEGACY_TRIGGER_MANIFEST.items():
+ self.connection.query('CREATE TRIGGER recording_workspace.'+name+' AFTER '+spec['event']+
+ ' ON recording_workspace.shared_annotation FOR EACH ROW '+spec['body'])
+ self.connection.query(f'UPDATE recording_workspace.{MARKER} SET version=1 WHERE project_uuid=%s',(self.project_id,))
+ result=self.prepare();self.assertFalse(result['reused'])
+ self.assertEqual(self.shared.suggestions('STRASSE',30)['tags'],self.oracle('STRASSE',30)['tags'])
+ for table in (DICTIONARY,AUTHORS):
+ old=self.shared.native_tag_lookup;token=self.shared.generation_token()
+ self.connection.query('TRUNCATE TABLE recording_workspace.'+table)
+ self.assertNotEqual(self.shared.generation_token().authority,token.authority)
+ with self.assertRaisesRegex(ValueError,'incarnation'):old.validate_current()
+ result=self.prepare();self.assertFalse(result['reused'])
+ self.assertEqual(self.shared.suggestions('STRASSE',30)['tags'],self.oracle('STRASSE',30)['tags'])
+ self.assertTrue(self.prepare()['reused'])
+
+ def test_protocol_preparation_is_bounded_without_shared_sqlite_and_fences_changes(self):
+ from workspace_protocol_state import STATE_CACHE_SCOPES
+ self.service.protocols={identity(n):{} for n in range(1,STATE_CACHE_SCOPES+4)}
+ warmed=[];phases=[]
+ reader=SimpleNamespace(native_context=lambda key:warmed.append(key) or {})
+ with patch.object(self.shared,'_membership_index',side_effect=AssertionError('Protocol preparation must not warm SQLite')):
+ report=self.prepare(protocol_state=reader,progress=phases.append)
+ self.assertEqual(warmed,list(self.service.protocols)[:STATE_CACHE_SCOPES])
+ self.assertEqual(report['protocols_prepared'],STATE_CACHE_SCOPES)
+ self.assertEqual(report['protocols_deferred'],3)
+ self.assertIn('preparing_protocol_reads',phases)
+ def concurrent_edit(key):
+ if key==next(iter(self.service.protocols)):
+ self.write('epoch',identity(1),identity(101),['during warm'],'Author')
+ return {}
+ reader.native_context=concurrent_edit
+ report=prepare_project_annotations(self.service,self.store,self.shared,protocol_state=reader)
+ self.assertEqual(report['status'],'failed',report)
+ self.assertFalse(self.shared.native_tag_lookup.ready)
+
+ def test_checkpoint_rejects_source_change_after_proof_without_clearing_dirty(self):
+ self.write('epoch',identity(1),identity(101),['start'],'Author');self.prepare()
+ original=NativeTagLookup.checkpoint
+ def race(lookup,proof,**kwargs):
+ self.connection.query('UPDATE recording_workspace.shared_annotation SET tags=%s,revision=revision+1 WHERE project_uuid=%s',
+ (json.dumps(['changed after proof']),self.project_id))
+ return original(lookup,proof,**kwargs)
+ with patch.object(NativeTagLookup,'checkpoint',race):
+ report=prepare_project_annotations(self.service,self.store,self.shared)
+ self.assertEqual(report['status'],'failed',report)
+ marker=self.connection.query(f'SELECT dirty FROM recording_workspace.{MARKER} WHERE project_uuid=%s',(self.project_id,)).fetchone()[0]
+ self.assertEqual(marker,1,'A rejected proof must not clear the durable sticky dirty flag')
+ self.prepare();self.assertEqual(self.shared.native_tag_lookup.targets('changed after proof'),{('epoch',identity(1))})
+
+ def test_utf8_canonical_trigger_delete_uses_ascii_primary_key_with_unrelated_rows(self):
+ from workspace_native_tag_lookup import _delete
+ # Every target has five memberships. Unrelated targets must not turn a
+ # ten-target edit into ten scans of all 7,500 index records.
+ records=[(self.project_id,'epoch',identity(n),identity(101),json.dumps(['one','two','three','four','five']),
+ 'Historical author',1,dt.datetime(2026,9,30,12)) for n in range(1,1501)]
+ cursor=self.connection._conn.cursor()
+ cursor.executemany('INSERT INTO recording_workspace.shared_annotation VALUES (%s,%s,%s,%s,%s,%s,%s,%s)',records)
+ self.prepare()
+ columns=self.connection.query('SELECT TABLE_NAME,CHARACTER_SET_NAME FROM information_schema.COLUMNS '
+ "WHERE TABLE_SCHEMA='recording_workspace' AND TABLE_NAME IN ('shared_annotation','app_shared_tag_lookup') AND COLUMN_NAME='target_uuid'",as_dict=True).fetchall()
+ charsets={row['TABLE_NAME']:row['CHARACTER_SET_NAME'] for row in columns}
+ self.assertEqual(charsets['app_shared_tag_lookup'],'ascii')
+ self.assertIn(charsets['shared_annotation'],('utf8mb3','utf8mb4'))
+ statement=_delete('OLD')
+ fields=('project_uuid','target_kind','target_uuid','profile_uuid')
+ for field in fields:statement=statement.replace('OLD.'+field,'p_'+field)
+ parameters=','.join('IN p_'+field+' VARCHAR(36) CHARACTER SET '+charsets['shared_annotation'] for field in fields)
+ self.connection.query('CREATE PROCEDURE recording_workspace.explain_indexed_tag_delete('+parameters+
+ ') BEGIN EXPLAIN FORMAT=JSON '+statement+' END')
+ plan=json.loads(self.connection.query('CALL recording_workspace.explain_indexed_tag_delete(%s,%s,%s,%s)',
+ (self.project_id,'epoch',identity(1),identity(101))).fetchone()[0])['query_block']['table']
+ self.assertEqual(plan['key'],'PRIMARY',plan)
+ self.assertEqual(plan['access_type'],'range',plan)
+ self.assertEqual(plan['used_key_parts'],list(fields),plan)
+ def scanned():
+ return {key:int(value) for key,value in self.connection.query("SHOW SESSION STATUS WHERE Variable_name IN ('Handler_read_next','Handler_read_rnd_next')").fetchall()}
+ before=scanned()
+ with self.connection.transaction:
+ for number in range(1,11):
+ self.connection.query('UPDATE recording_workspace.shared_annotation SET tags=%s,revision=revision+1 '
+ 'WHERE project_uuid=%s AND target_kind=%s AND target_uuid=%s AND profile_uuid=%s',
+ (json.dumps(['edited']),self.project_id,'epoch',identity(number),identity(101)))
+ after=scanned()
+ self.assertLess(sum(after[key]-before[key] for key in before),1000,'Ten edits must not scan 7,500 unrelated memberships each')
+ self.assertEqual(self.shared.native_tag_lookup.targets('edited'),{('epoch',identity(n)) for n in range(1,11)})
+ self.assertEqual(len(self.shared.native_tag_lookup.targets('one')),1490)
+ from workspace_native_tag_filter import suggestions
+ lookup=self.shared.native_tag_lookup;queries=[];query_reads=[];original=lookup._rows
+ def capture(sql,args=()):
+ queries.append((sql,args));start=scanned();rows=original(sql,args);end=scanned()
+ query_reads.append(sum(end[key]-start[key] for key in start))
+ return rows
+ before=scanned()
+ with patch.object(lookup,'_rows',side_effect=capture):result=suggestions(lookup,'edited',30)
+ after=scanned()
+ self.assertEqual(result['tags'],[{'tag':'edited','count':10,
+ 'authors':[{'profile_uuid':identity(101),'display_name':'Historical author'}]}])
+ self.assertLess(sum(query_reads),1000,'Compact dictionary and point winner queries must avoid unrelated memberships')
+ membership_queries=[(sql,args) for sql,args in queries if f'`{TABLE}`' in sql]
+ self.assertTrue(membership_queries)
+ for sql,args in membership_queries:
+ self.assertIn('LIMIT 1',sql)
+ self.assertNotIn('GROUP BY',sql);self.assertNotIn('COUNT(',sql)
+ block=json.loads(self.connection.query('EXPLAIN FORMAT=JSON '+sql,args).fetchone()[0])['query_block']
+ plan=block.get('table') or block['ordering_operation']['table']
+ self.assertEqual(plan['key'],'by_tag')
+ self.assertEqual(plan['used_key_parts'],['project_uuid','tag','profile_uuid'])
+ source_sql,source_args=next((sql,args) for sql,args in queries if '`shared_annotation`' in sql)
+ source_plan=json.loads(self.connection.query('EXPLAIN FORMAT=JSON '+source_sql,source_args).fetchone()[0])['query_block']['table']
+ self.assertEqual(source_plan['key'],'PRIMARY')
+ self.assertEqual(source_plan['used_key_parts'],list(fields))
+ queries.clear();query_reads.clear()
+ with patch.object(lookup,'_rows',side_effect=capture):suggestions(lookup,'',3)
+ self.assertLess(sum(query_reads),1000)
+ sql,args=next((sql,args) for sql,args in queries if 'ORDER BY target_count DESC' in sql)
+ plan=json.loads(self.connection.query('EXPLAIN FORMAT=JSON '+sql,args).fetchone()[0])
+ self.assertIn('"key": "by_rank"',json.dumps(plan))
+ self.assertNotIn('"using_filesort": true',json.dumps(plan))
+
+ def test_export_omits_only_exact_managed_trigger_contracts(self):
+ self.write('epoch',identity(1),identity(101),['tag'],'Author');self.prepare()
+ report=verify_export_triggers(self.connection,('recording_workspace',))
+ self.assertTrue(report['managed_present']);self.assertTrue(report['safe_to_omit'],report)
+ self.connection.query('CREATE TRIGGER recording_workspace.scientific_custom_audit AFTER INSERT ON recording_workspace.shared_annotation FOR EACH ROW SET @last_scientific_target=NEW.target_uuid')
+ refused=verify_export_triggers(self.connection,('recording_workspace',))
+ self.assertFalse(refused['safe_to_omit'])
+ self.connection.query('DROP TRIGGER recording_workspace.scientific_custom_audit')
+ trigger=next(name for name in TRIGGER_MANIFEST if name.endswith('_ai'))
+ self.connection.query('DROP TRIGGER recording_workspace.'+trigger)
+ self.connection.query('CREATE TRIGGER recording_workspace.'+trigger+' AFTER INSERT ON recording_workspace.shared_annotation FOR EACH ROW SET @altered_lookup=NEW.target_uuid')
+ refused=verify_export_triggers(self.connection,('recording_workspace',))
+ self.assertFalse(refused['safe_to_omit'])
+
+ def test_bootstrap_rejects_source_change_after_refreshed_proof(self):
+ import workspace_native_tag_lookup as module
+ self.write('epoch',identity(1),identity(101),['start'],'Author');self.prepare()
+ self.shared.native_tag_lookup=None
+ original=module.bootstrap
+ def race(*args,**kwargs):
+ provider=kwargs['proof_provider']
+ def changed_after_proof():
+ proof=provider()
+ self.connection.query('UPDATE recording_workspace.shared_annotation SET tags=%s,revision=revision+1 WHERE project_uuid=%s',
+ (json.dumps(['changed before backfill']),self.project_id))
+ return proof
+ kwargs['proof_provider']=changed_after_proof
+ return original(*args,**kwargs)
+ with patch.object(module,'bootstrap',side_effect=race):
+ report=prepare_project_annotations(self.service,self.store,self.shared)
+ self.assertEqual(report['status'],'failed',report)
+ dirty=self.connection.query(f'SELECT dirty FROM recording_workspace.{MARKER} WHERE project_uuid=%s',(self.project_id,)).fetchone()[0]
+ self.assertEqual(dirty,1)
+ self.prepare();self.assertEqual(self.shared.native_tag_lookup.targets('changed before backfill'),{('epoch',identity(1))})
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_native_tag_lookup.py b/python/tests/test_workspace_native_tag_lookup.py
new file mode 100644
index 0000000..d794374
--- /dev/null
+++ b/python/tests/test_workspace_native_tag_lookup.py
@@ -0,0 +1,143 @@
+"""Persistent tag lookup contract, plus opt-in disposable native SQL proof."""
+import contextlib
+import hashlib
+import json
+import os
+from pathlib import Path
+import tempfile
+import unittest
+import uuid
+
+from workspace_native_tag_lookup import NativeTagLookup,bootstrap,TRIGGER_MANIFEST,TABLE,MARKER
+
+
+class TagLookupTests(unittest.TestCase):
+ def test_query_parameters_preserve_exact_utf8_and_bounded_kinds(self):
+ lookup=NativeTagLookup(None,str(uuid.uuid4()));lookup.ready=True
+ sql,args=lookup._target_query('e\u0301',('epoch',))
+ self.assertIn('FORCE INDEX (by_tag)',sql)
+ self.assertEqual(args[-1],'e\u0301'.encode())
+ for tag in ('','x'*256,12):
+ with self.assertRaises(ValueError):lookup._target_query(tag,('cell',))
+ with self.assertRaises(ValueError):lookup._target_query('QC',('unknown',))
+
+
+@unittest.skipUnless(os.environ.get('RIEKE_TEST_NATIVE_MYSQL')=='1','opt-in private native tag lookup')
+class NativeTagLookupTests(unittest.TestCase):
+ def test_migration_exact_unicode_all_writers_rollback_and_reuse(self):
+ import pymysql
+ from workspace_projects import create_project
+ import workspace_native_mysql as native
+ with tempfile.TemporaryDirectory(prefix='rieke-native-tag-lookup-') as directory:
+ root=Path(create_project(Path(directory)/'projects','Native tag lookup test')['path'])
+ raw=None
+ try:
+ native.ensure_native_database(root);raw=pymysql.connect(**native.connection_parameters(root),autocommit=True)
+ class Adapter:
+ in_transaction=False
+ def query(self,sql,args=(),as_dict=False,reconnect=False):
+ cursor=raw.cursor(pymysql.cursors.DictCursor if as_dict else pymysql.cursors.Cursor)
+ cursor.execute(sql,args);return cursor
+ @property
+ @contextlib.contextmanager
+ def transaction(self):
+ raw.begin();self.in_transaction=True
+ try:yield
+ except BaseException:raw.rollback();raise
+ else:raw.commit()
+ finally:self.in_transaction=False
+ conn=Adapter();project=str(uuid.uuid4());profile=str(uuid.uuid4())
+ conn.query('CREATE DATABASE recording_workspace')
+ conn.query('CREATE TABLE recording_workspace.shared_annotation (project_uuid varchar(36),target_kind varchar(8),target_uuid varchar(36),profile_uuid varchar(36),tags JSON NOT NULL,PRIMARY KEY(project_uuid,target_kind,target_uuid,profile_uuid)) ENGINE=InnoDB')
+ conn.query('CREATE TABLE recording_workspace.app_state_generation (project_uuid varchar(36),scope_kind varchar(32),scope_uuid varchar(36),generation BIGINT,PRIMARY KEY(project_uuid,scope_kind,scope_uuid)) ENGINE=InnoDB')
+ conn.query('INSERT INTO recording_workspace.app_state_generation VALUES (%s,%s,%s,0)',(project,'shared_annotations',project))
+ records={}
+ def write(identity,values,kind='epoch',author=profile):
+ conn.query('REPLACE INTO recording_workspace.shared_annotation VALUES (%s,%s,%s,%s,%s)',(project,kind,identity,author,json.dumps(values)))
+ records[(kind,identity,author)]=values
+ def proof():
+ rows=conn.query('SELECT * FROM recording_workspace.shared_annotation ORDER BY project_uuid,target_kind,target_uuid,profile_uuid',as_dict=True).fetchall()
+ return {'project_uuid':project,'tables':{'shared_annotation':{'count':len(rows),'xor':hashlib.sha256(json.dumps(rows,sort_keys=True).encode()).hexdigest()}}}
+ cases=[['QC'],['qc'],['é'],['e\u0301'],['😀'*255],['😀'*254+chr(0x1f600+i) for i in range(100)],
+ ['legal\x7f','inside\u0085value','inside\u3000value'],[]]
+ ids=[str(uuid.uuid4()) for _ in cases]
+ for key,values in zip(ids,cases):write(key,values)
+ firstproof=proof();lookup=bootstrap(conn,project,firstproof)
+ self.assertTrue(lookup.ready,lookup.reason);self.assertFalse(lookup.reused)
+ for key,values in zip(ids,cases):
+ for value in values:self.assertIn(('epoch',key),lookup.targets(value))
+ self.assertEqual(lookup.targets('QC'),{('epoch',ids[0])})
+ self.assertEqual(lookup.targets('qc'),{('epoch',ids[1])})
+ self.assertEqual(lookup.targets('é'),{('epoch',ids[2])})
+ self.assertEqual(lookup.targets('e\u0301'),{('epoch',ids[3])})
+ self.assertNotIn(('epoch',ids[0]),lookup.targets_outside(['QC']))
+ self.assertIn(('epoch',ids[1]),lookup.targets_outside(['QC']))
+ self.assertEqual(lookup.targets_outside([]),lookup.tagged_targets())
+ self.assertNotIn(('epoch',ids[-1]),lookup.tagged_targets())
+ sql,args=lookup._target_query('QC',('epoch',))
+ plan=conn.query('EXPLAIN FORMAT=JSON '+sql,args).fetchone()[0]
+ self.assertIn('by_tag',plan)
+ self.assertTrue(bootstrap(conn,project,firstproof).reused)
+ ddl_before=conn.query("SHOW GLOBAL STATUS WHERE Variable_name='Com_create_table'").fetchone()
+ self.assertTrue(bootstrap(conn,project,firstproof).reused)
+ self.assertEqual(conn.query("SHOW GLOBAL STATUS WHERE Variable_name='Com_create_table'").fetchone(),ddl_before)
+ # A->B with triggers, then B->A in a trigger gap: the canonical
+ # proof matches the old marker, but the sticky dirty bit forbids
+ # falsely reusing B after process restart.
+ conn.query('UPDATE recording_workspace.shared_annotation SET tags=%s WHERE target_uuid=%s',(json.dumps(['intermediate']),ids[2]))
+ trigger_name=next(name for name in TRIGGER_MANIFEST if name.endswith('_au'))
+ conn.query('DROP TRIGGER recording_workspace.'+trigger_name)
+ conn.query('UPDATE recording_workspace.shared_annotation SET tags=%s WHERE target_uuid=%s',(json.dumps(['é']),ids[2]))
+ spec=TRIGGER_MANIFEST[trigger_name]
+ conn.query('CREATE TRIGGER recording_workspace.'+trigger_name+' AFTER UPDATE ON recording_workspace.shared_annotation FOR EACH ROW '+spec['body'])
+ self.assertEqual(proof(),firstproof)
+ aba=bootstrap(conn,project,firstproof)
+ self.assertTrue(aba.ready,aba.reason);self.assertFalse(aba.reused)
+ self.assertEqual(aba.targets('intermediate'),set())
+ self.assertEqual(aba.targets('é'),{('epoch',ids[2])})
+ conn.query('UPDATE recording_workspace.shared_annotation SET tags=%s WHERE target_uuid=%s',(json.dumps(['updated']),ids[0]))
+ self.assertEqual(lookup.targets('QC'),set());self.assertEqual(lookup.targets('updated'),{('epoch',ids[0])})
+ with self.assertRaisesRegex(RuntimeError,'rollback'):
+ with conn.transaction:
+ conn.query('UPDATE recording_workspace.shared_annotation SET tags=%s WHERE target_uuid=%s',(json.dumps(['partial']),ids[0]))
+ self.assertEqual(lookup.targets('partial'),{('epoch',ids[0])})
+ raise RuntimeError('rollback')
+ self.assertEqual(lookup.targets('partial'),set());self.assertEqual(lookup.targets('updated'),{('epoch',ids[0])})
+ other=str(uuid.uuid4());write(ids[0],['updated'],author=other)
+ self.assertEqual(lookup.targets('updated'),{('epoch',ids[0])})
+ conn.query('DELETE FROM recording_workspace.shared_annotation WHERE target_uuid=%s AND profile_uuid=%s',(ids[0],profile))
+ self.assertEqual(lookup.targets('updated'),{('epoch',ids[0])})
+ conn.query('UPDATE recording_workspace.shared_annotation SET tags=JSON_ARRAY() WHERE target_uuid=%s',(ids[0],))
+ self.assertEqual(lookup.targets('updated'),set())
+ moved=str(uuid.uuid4());moved_author=str(uuid.uuid4())
+ conn.query('UPDATE recording_workspace.shared_annotation SET target_kind=%s,target_uuid=%s,profile_uuid=%s WHERE target_uuid=%s',
+ ('cell',moved,moved_author,ids[1]))
+ self.assertEqual(lookup.targets('qc'),{('cell',moved)})
+ self.assertEqual(lookup.targets('qc',('epoch',)),set())
+ write(str(uuid.uuid4()),['legal\x7f','inside\u0085value','inside\u3000value'])
+ for invalid in ([1],['duplicate','duplicate'],['x'*256],[' padded'],['\u3000padded'],['padded\u0085'],['bad\x00tag'],['a']*101):
+ with self.subTest(invalid=str(invalid)[:30]),self.assertRaises(Exception):write(str(uuid.uuid4()),invalid)
+ changed=proof();rebuilt=bootstrap(conn,project,changed)
+ self.assertTrue(rebuilt.ready,rebuilt.reason);self.assertFalse(rebuilt.reused)
+ self.assertTrue(rebuilt.validate_current())
+ with self.assertRaisesRegex(ValueError,'authority changed'):
+ rebuilt.checkpoint(changed,guard=lambda:False)
+ rebuilt.checkpoint(changed);self.assertTrue(bootstrap(conn,project,changed).reused)
+ conn.query('DROP TRIGGER recording_workspace.'+next(iter(TRIGGER_MANIFEST)))
+ with self.assertRaisesRegex(ValueError,'incomplete'):rebuilt.validate_current()
+ fixed=bootstrap(conn,project,changed)
+ self.assertTrue(fixed.ready,fixed.reason);self.assertFalse(fixed.reused)
+ conn.query('TRUNCATE TABLE recording_workspace.'+TABLE)
+ with self.assertRaisesRegex(ValueError,'incarnation'):fixed.checkpoint(changed)
+ repaired=bootstrap(conn,project,changed)
+ self.assertTrue(repaired.ready,repaired.reason);self.assertFalse(repaired.reused)
+ self.assertEqual(repaired.targets('qc'),{('cell',moved)})
+ # A clean native restart retains the DB index and proof marker.
+ raw.close();raw=None;native.stop_native_database(root);native.ensure_native_database(root)
+ raw=pymysql.connect(**native.connection_parameters(root),autocommit=True)
+ reopened=bootstrap(conn,project,changed)
+ self.assertTrue(reopened.ready,reopened.reason);self.assertTrue(reopened.reused)
+ self.assertEqual(reopened.targets('qc'),{('cell',moved)})
+ finally:
+ if raw is not None:raw.close()
+ native.stop_native_database(root)
diff --git a/python/tests/test_workspace_open_folder.py b/python/tests/test_workspace_open_folder.py
index 2e9867d..a5dbc0c 100644
--- a/python/tests/test_workspace_open_folder.py
+++ b/python/tests/test_workspace_open_folder.py
@@ -1,5 +1,6 @@
"""Opening an explicit existing project must never initialize arbitrary folders."""
import json
+import os
from pathlib import Path
import tempfile
import unittest
@@ -12,6 +13,8 @@ class OpenFolderTests(unittest.TestCase):
def setUp(self):
self.temp=tempfile.TemporaryDirectory(); self.addCleanup(self.temp.cleanup)
self.root=Path(self.temp.name)
+ index = patch.dict(os.environ, {'RIEKE_PROJECT_INDEX': str(self.root / 'preferences/project-index.json')})
+ index.start(); self.addCleanup(index.stop)
self.current=self.project('workspace/current')
self.other=self.project('other-workspace/other')
self.client=create_launcher(self.current.parent,self.root/'parser').test_client()
@@ -36,6 +39,39 @@ def test_explicit_other_workspace_opens_exact_project_without_rewriting_manifest
opened.assert_called_once_with(self.other.resolve(),identity,self.root/'parser')
self.assertEqual(before,{p.name:p.read_bytes() for p in self.other.iterdir()})
+ def test_external_open_is_remembered_after_launcher_restart(self):
+ identity = json.loads((self.other / 'project.json').read_text())['project_uuid']
+ with patch('workspace_launcher.open_project', return_value={'url': 'http://127.0.0.1:8877/', 'project_uuid': identity}):
+ self.assertEqual(self.post(self.other).status_code, 200)
+ restarted = create_launcher(self.current.parent, self.root / 'parser').test_client()
+ inventory = restarted.get('/api/projects').get_json()
+ external = next(project for project in inventory['projects'] if project['path'] == str(self.other.resolve()))
+ self.assertTrue(external['available'])
+ self.assertEqual(inventory['last_project_path'], str(self.other.resolve()))
+ with patch('workspace_launcher.open_project', return_value={'url': 'http://127.0.0.1:8877/', 'project_uuid': identity}) as opened:
+ response = restarted.post('/api/projects/' + identity + '/open', json={}, headers=self.headers)
+ self.assertEqual(response.status_code, 200)
+ opened.assert_called_once_with(self.other.resolve(), identity, self.root / 'parser')
+
+ def test_new_project_at_arbitrary_root_is_indexed_without_parent_workspace(self):
+ directory = self.root / 'independent' / 'user chosen study'
+ response = self.client.post('/api/projects', json={'name': 'Independent study',
+ 'project_directory': str(directory)}, headers=self.headers)
+ self.assertEqual(response.status_code, 201, response.get_json())
+ inventory = create_launcher(self.current.parent, self.root / 'parser').test_client().get('/api/projects').get_json()
+ self.assertIn(str(directory.resolve()), [project['path'] for project in inventory['projects']])
+ self.assertFalse((directory.parent / '.rieke-workspace.json').exists())
+
+ def test_completed_creation_is_reported_when_local_index_cannot_save(self):
+ directory = self.root / 'created despite profile error'
+ with patch('workspace_startup_registry.remember_project_path', side_effect=OSError('Profile disk is full')):
+ response = self.client.post('/api/projects', json={'name': 'Completed creation',
+ 'project_directory': str(directory)}, headers=self.headers)
+ self.assertEqual(response.status_code, 201, response.get_json())
+ self.assertEqual(response.get_json()['project']['path'], str(directory.resolve()))
+ self.assertIn('registry_warning', response.get_json())
+ self.assertTrue((directory / 'project.json').is_file())
+
def test_invalid_paths_and_bodies_never_start_services(self):
empty=self.root/'empty';empty.mkdir()
alias=self.root/'alias';alias.symlink_to(self.other,target_is_directory=True)
@@ -76,6 +112,28 @@ def test_local_mutation_protection_applies(self):
self.assertEqual(self.post(self.other,headers={**self.headers,'Origin':'https://foreign.example'}).status_code,403)
opened.assert_not_called()
+ def test_normal_folder_inspection_detects_received_package_and_requires_restore(self):
+ import workspace_portability as transfer
+ package = self.root / 'received-package'
+ package.mkdir()
+ project = json.loads((self.other / 'project.json').read_text())
+ transfer._write(package / 'project.json', project)
+ (package / 'database.sql').write_text('-- logical backup fixture\n')
+ transfer._write(package / transfer.MANIFEST, {'format': transfer.FORMAT,
+ 'version': 1, 'mode': 'complete', 'database_format': 'mysql8-logical-v1',
+ 'project_uuid': project['project_uuid'], 'sources': [], 'files': transfer._files(package)})
+ before = {p.name: p.read_bytes() for p in package.iterdir()}
+ with patch('workspace_launcher.open_project') as opened:
+ response = self.client.post('/api/projects/inspect-folder',
+ json={'directory': str(package)}, headers=self.headers)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.get_json()['kind'], 'prepared-transfer')
+ response = self.post(package)
+ self.assertEqual(response.status_code, 400)
+ self.assertIn('restore', response.get_json()['error'])
+ opened.assert_not_called()
+ self.assertEqual(before, {p.name: p.read_bytes() for p in package.iterdir()})
+
def test_explicit_restored_instance_can_open_without_merging_original(self):
app=Flask(__name__)
register_project_routes(app,retinanalysis_dir=self.root/'parser',project_dir=self.current)
diff --git a/python/tests/test_workspace_path_api.py b/python/tests/test_workspace_path_api.py
new file mode 100644
index 0000000..300c464
--- /dev/null
+++ b/python/tests/test_workspace_path_api.py
@@ -0,0 +1,76 @@
+"""Path inputs must not depend on the server CWD or escape managed uploads."""
+import io
+import os
+from pathlib import Path
+import tempfile
+import unittest
+from unittest.mock import patch
+
+if __package__:
+ from . import test_workspace_api as api_tests
+else:
+ import test_workspace_api as api_tests
+
+
+class ImportPathTests(unittest.TestCase):
+ def setUp(self):
+ self.case = api_tests.WorkspaceAPITests()
+ self.case.setUp()
+ self.addCleanup(self.case.doCleanups)
+
+ def test_relative_existing_file_rejected_before_queueing(self):
+ root = Path(self.case.temp.name)
+ (root / 'ambiguous.h5').write_bytes(b'Not parsed by this test')
+ previous = Path.cwd()
+ try:
+ os.chdir(root)
+ with patch('workspace_api.threading.Thread') as thread:
+ response = self.case.client.post('/api/imports',
+ json={'source_path':'ambiguous.h5'}, headers=self.case.headers)
+ self.assertEqual(response.status_code, 400, response.json)
+ self.assertIn('absolute recording file path', response.json['error'])
+ thread.assert_not_called()
+ finally:
+ os.chdir(previous)
+
+ def test_upload_directory_symlink_rejected_without_external_write(self):
+ with tempfile.TemporaryDirectory() as outside:
+ uploads = Path(self.case.temp.name) / 'raw-uploads'
+ if uploads.exists():
+ uploads.rmdir() # Fixture directory is empty.
+ uploads.symlink_to(outside, target_is_directory=True)
+ response = self.case.client.post('/api/imports',
+ data={'file':(io.BytesIO(b'Unparsed fixture'), 'recording.h5')},
+ content_type='multipart/form-data', headers=self.case.headers)
+ self.assertEqual(response.status_code, 400, response.json)
+ self.assertIn('symbolic links', response.json['error'])
+ self.assertEqual(list(Path(outside).iterdir()), [])
+
+ def test_queued_upload_worker_rejects_late_directory_redirect_before_read_or_cleanup(self):
+ import json
+ with patch('workspace_api.threading.Thread') as thread:
+ response = self.case.client.post('/api/imports',
+ data={'file': (io.BytesIO(b'Unparsed fixture'), 'recording.h5')},
+ content_type='multipart/form-data', headers=self.case.headers)
+ self.assertEqual(response.status_code, 202, response.json)
+ arguments = thread.call_args.kwargs
+ source, job_file = arguments['args']
+ uploads = source.parent.parent
+ uploads.rename(uploads.with_name('raw-uploads.saved'))
+ with tempfile.TemporaryDirectory() as outside:
+ external = Path(outside) / source.parent.name
+ external.mkdir()
+ recording = external / source.name
+ recording.write_bytes(b'Unparsed fixture')
+ uploads.symlink_to(outside, target_is_directory=True)
+ with patch('workspace_api.classify_source') as classify, patch('recording_workspace.workspace_tables', return_value=(None, None, self.case.events, None)):
+ arguments['target'](source, job_file)
+ classify.assert_not_called()
+ self.assertEqual(recording.read_bytes(), b'Unparsed fixture')
+ job = json.loads(job_file.read_text())
+ self.assertEqual(job['status'], 'failed')
+ self.assertFalse(job['catalog_committed'])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_workspace_portability.py b/python/tests/test_workspace_portability.py
index 1610382..2405385 100644
--- a/python/tests/test_workspace_portability.py
+++ b/python/tests/test_workspace_portability.py
@@ -4,9 +4,13 @@
The integration test creates/removes only its own disposable project databases.
"""
import fcntl
+import datetime as dt
+from decimal import Decimal
import json
import os
import shutil
+import subprocess
+import sys
from pathlib import Path
import tempfile
import types
@@ -80,6 +84,115 @@ def test_symbolic_links_and_runtime_configuration_are_not_transferable(self):
with self.assertRaisesRegex(ValueError, 'runtime files'):
transfer.inspect_package(self.package)
+ def test_non_object_project_and_malformed_database_inventory_are_rejected(self):
+ transfer._write(self.package / 'project.json', [])
+ self.seal()
+ with self.assertRaisesRegex(ValueError, 'identity|format'):
+ transfer.inspect_package(self.package)
+ transfer._write(self.package / 'project.json', {'format': 'recording-project', 'version': 1,
+ 'project_uuid': self.identity, 'name': 'Portable study'})
+ for inventory in ([], {'mysql.user': {'rows': 1, 'sha256': 'a' * 64}},
+ {'schema.epoch': {'rows': True, 'sha256': 'a' * 64}},
+ {'recording_workspace.event': {'rows': 1, 'sha256': None}}):
+ with self.subTest(inventory=inventory):
+ self.seal(database_inventory=inventory)
+ with self.assertRaisesRegex(ValueError, 'database inventory'):
+ transfer.inspect_package(self.package)
+
+ def test_durable_job_history_is_allowed_but_runtime_logs_are_rejected(self):
+ for directory in ('logs/imports', 'logs/app-jobs', 'logs/errors', 'logs/storage'):
+ transfer._write(self.package / directory / 'history.json', {'preserved': directory})
+ self.seal()
+ self.assertEqual(len(transfer.inspect_package(self.package)['files']), 6)
+ (self.package / 'logs/workspace-server.json').write_text('{"port":12345}')
+ self.seal()
+ with self.assertRaisesRegex(ValueError, 'runtime files'):
+ transfer.inspect_package(self.package)
+
+ def test_restore_rejects_missing_saved_state_before_rewriting_locators(self):
+ self.seal(database_inventory={'recording_workspace.search_preset':
+ {'rows': 2, 'sha256': 'a' * 64}})
+ destination = self.root / 'missing-history'
+ with patch('workspace_project_database.ensure_project_database'), \
+ patch.object(transfer, '_import'), \
+ patch.object(transfer, '_connection', return_value=MagicMock()), \
+ patch.object(transfer, '_database_inventory', return_value={}), \
+ patch.object(transfer, '_sources') as sources, \
+ patch.object(transfer, '_remove_runtime') as cleanup:
+ with self.assertRaisesRegex(ValueError, 'database content'):
+ transfer.restore_project(self.package, destination)
+ sources.assert_not_called()
+ cleanup.assert_called_once_with(destination)
+ self.assertFalse(destination.exists())
+
+ def test_empty_registration_never_reconstructs_populated_or_foreign_projects(self):
+ project = create_project(self.root / 'workspace', 'New project')
+ root = Path(project['path'])
+ for tables, identities, row in (
+ ([('recording_workspace', 'project')], [(str(uuid.uuid4()),)], None),
+ ([('recording_workspace', 'project'), ('schema', 'epoch')], [], (1,)),
+ ([('recording_workspace', 'project'), ('recording_workspace', 'source')], [], (1,))):
+ connection = MagicMock()
+ cursor = connection.cursor.return_value.__enter__.return_value
+ cursor.fetchall.side_effect = [tables, identities]
+ cursor.fetchone.return_value = row
+ with self.subTest(tables=tables, identities=identities), self.assertRaisesRegex(ValueError, 'identity'):
+ transfer.register_empty_project(root, connection)
+ self.assertFalse(any(call.args[0].startswith(('CREATE', 'INSERT')) for call in cursor.execute.call_args_list))
+ transfer._write(root / 'imports/registered.json', {'scientific': 'cannot reconstruct'})
+ connection = MagicMock()
+ connection.cursor.return_value.__enter__.return_value.fetchall.return_value = []
+ with self.assertRaisesRegex(ValueError, 'populated project'):
+ transfer.register_empty_project(root, connection)
+
+ def test_external_h5_dependencies_cannot_claim_complete_transfer(self):
+ import h5py
+ recording = self.package / 'raw-uploads/linked.h5'
+ recording.parent.mkdir()
+ with h5py.File(recording, 'w') as handle:
+ handle['donor'] = h5py.ExternalLink('missing-donor.h5', '/waveform')
+ metadata = self.package / 'imports/source/metadata.catalog.json'
+ transfer._write(metadata, {'fixture': 'metadata'})
+ sha = transfer._hash(recording)
+ self.seal(sources=[{'source_sha256': sha,
+ 'recording_ref': recording.relative_to(self.package).as_posix(),
+ 'metadata_ref': metadata.relative_to(self.package).as_posix(),
+ 'metadata_sha256': transfer._hash(metadata)}])
+ with patch('workspace_project_database.ensure_project_database') as runtime:
+ with self.assertRaisesRegex(ValueError, 'External H5 links'):
+ transfer.restore_project(self.package, self.root / 'external-dependent')
+ runtime.assert_not_called()
+ self.assertFalse((self.root / 'external-dependent').exists())
+
+ def test_received_preferences_are_validated_before_database_start(self):
+ from workspace_project_preferences import ProjectPreferences, REFERENCE
+ preferences = ProjectPreferences(self.package, self.identity).read()
+ preferences['project_uuid'] = str(uuid.uuid4())
+ transfer._write(self.package / REFERENCE, preferences)
+ self.seal()
+ with patch('workspace_project_database.ensure_project_database') as runtime:
+ with self.assertRaisesRegex(ValueError, 'preference file|identity'):
+ transfer.restore_project(self.package, self.root / 'foreign-preferences')
+ runtime.assert_not_called()
+ self.assertFalse((self.root / 'foreign-preferences').exists())
+
+ def test_complete_source_requires_valid_checksum_and_included_metadata(self):
+ import h5py
+ recording = self.package / 'raw-uploads/source.h5'
+ recording.parent.mkdir()
+ with h5py.File(recording, 'w') as handle:
+ handle.create_dataset('samples', data=[0.25, -2.0])
+ source = {'source_sha256': transfer._hash(recording),
+ 'recording_ref': 'raw-uploads/source.h5', 'metadata_ref': 'imports/absent.json'}
+ for checksum in (None, '', 'a' * 64):
+ with self.subTest(checksum=checksum):
+ self.seal(sources=[{**source, 'metadata_sha256': checksum}])
+ with patch('workspace_project_database.ensure_project_database') as runtime:
+ with self.assertRaisesRegex(ValueError, 'metadata checksum|missing a registered source dependency'):
+ transfer.restore_project(self.package, self.root / 'missing-metadata')
+ runtime.assert_not_called()
+ self.assertFalse((self.root / 'missing-metadata').exists())
+
def test_existing_destination_is_never_overwritten(self):
destination = self.root / 'existing'
destination.mkdir()
@@ -248,6 +361,35 @@ def test_foreign_project_and_external_artifact_refs_are_rejected(self):
class NativeTransferCommandTests(unittest.TestCase):
+ def test_database_inventory_compares_every_row_independent_of_order_and_json_spacing(self):
+ from pymysql.cursors import SSCursor
+
+ def inventory(rows):
+ connection, metadata, data = MagicMock(), MagicMock(), MagicMock()
+ metadata.__enter__.return_value = metadata
+ data.__enter__.return_value = data
+ metadata.fetchall.side_effect = [[('recording_workspace', 'search_preset_version')],
+ [('version', 'int'), ('recipe', 'json'), ('fingerprint', 'blob'),
+ ('created_at', 'datetime'), ('value', 'decimal(8,2)')]]
+ data.fetchmany.side_effect = [rows, []]
+ connection.cursor.side_effect = lambda kind=None: data if kind is SSCursor else metadata
+ result = transfer._database_inventory(connection)
+ data.execute.assert_called_once_with('SELECT * FROM `recording_workspace`.`search_preset_version`')
+ self.assertEqual(data.fetchmany.call_args.args, (1000,))
+ return result
+
+ date = dt.datetime(2026, 9, 29, 12, 30)
+ first = (1, '{"predicate": {"all":[]}, "tag":"kept"}', b'\x00\xff', date, Decimal('1.25'))
+ second = (2, '{"tag":"kept","predicate":{"all":[]}}', b'\x00\xff', date, Decimal('1.25'))
+ baseline = inventory([first, second])
+ respaced = (1, '{"tag":"kept","predicate":{"all":[]}}', b'\x00\xff', date, Decimal('1.25'))
+ self.assertEqual(baseline, inventory([second, respaced]))
+ self.assertEqual(baseline['recording_workspace.search_preset_version']['rows'], 2)
+ changed = (1, '{"tag":"lost"}', b'\x00\xff', date, Decimal('1.25'))
+ self.assertNotEqual(baseline, inventory([changed, second]))
+ self.assertNotEqual(baseline, inventory([first]))
+ self.assertNotEqual(baseline, inventory([first, first]))
+
def test_native_catalog_never_carries_donor_runtime_or_credentials(self):
identity, instance = str(uuid.uuid4()), str(uuid.uuid4())
catalog = transfer._catalog(identity, instance)
@@ -297,11 +439,77 @@ def run(arguments, **options):
sql = [call.args[0] for call in connection.cursor.return_value.__enter__.return_value.execute.call_args_list]
self.assertTrue(any('DROP USER' in query for query in sql))
self.assertFalse(any('GRANT ALL PRIVILEGES ON *.*' in query for query in sql))
- connection.close.assert_called_once()
+ self.assertEqual(connection.close.call_count, 2)
@unittest.skipUnless(os.environ.get('RIEKE_TEST_NATIVE_TRANSFER') == '1', 'opt-in disposable native MySQL integration')
class NativeRoundTripTests(unittest.TestCase):
+ def install_derived_authorities(self, root, identity):
+ import datajoint as dj
+ from workspace_native_mysql import connection_parameters
+ from workspace_state_generation import bootstrap, verify_export_triggers
+ from workspace_recovery_generation import RecoveryTracker
+ parameters = connection_parameters(root)
+ connection = dj.Connection(**{key: parameters[key] for key in ('host', 'port', 'user', 'password')})
+ try:
+ for statement in (
+ 'CREATE TABLE IF NOT EXISTS recording_workspace.curation (project_uuid varchar(36),protocol_uuid varchar(36),epoch_uuid varchar(36),tags JSON,revision int, PRIMARY KEY(project_uuid,protocol_uuid,epoch_uuid)) ENGINE=InnoDB',
+ 'CREATE TABLE IF NOT EXISTS recording_workspace.shared_annotation (project_uuid varchar(36),target_kind varchar(16),target_uuid varchar(36),profile_uuid varchar(36),tags JSON,revision int,PRIMARY KEY(project_uuid,target_kind,target_uuid,profile_uuid)) ENGINE=InnoDB',
+ 'CREATE TABLE IF NOT EXISTS recording_workspace.annotation_profile (project_uuid varchar(36),profile_uuid varchar(36),display_name varchar(120),PRIMARY KEY(project_uuid,profile_uuid)) ENGINE=InnoDB'):
+ connection.query(statement)
+ reader = bootstrap(connection, identity)
+ self.assertTrue(reader.ready, reader.reason)
+ with RecoveryTracker(connection, identity).capture(None) as plan:
+ self.assertIsNotNone(plan)
+ plan.verify()
+ report = verify_export_triggers(connection, transfer.DATABASES)
+ self.assertTrue(report['safe_to_omit'], report)
+ self.assertGreater(report['known_trigger_count'], 9)
+ finally:
+ connection.close()
+
+ def test_new_empty_projects_open_close_prepare_and_restore(self):
+ from workspace_project_database import ensure_project_database
+ open_app = """
+import sys
+from workspace_api import create_app
+app = create_app(sys.argv[1], sys.argv[2])
+response = app.test_client().get('/api/overview')
+assert response.status_code == 200, response.get_json()
+assert not app.extensions['workspace_service'].rows
+"""
+ with tempfile.TemporaryDirectory(prefix='rieke-empty-transfer-test-') as temporary:
+ base = Path(temporary).resolve()
+ code = Path(__file__).resolve().parents[2]
+ def open_empty(path):
+ result = subprocess.run([sys.executable, '-c', open_app, str(path), str(code / '.rieke-runtime/retinanalysis')],
+ capture_output=True, text=True, timeout=60)
+ self.assertEqual(result.returncode, 0, result.stdout + '\n' + result.stderr)
+ for opened in (False, True):
+ project = create_project(base / ('workspace-' + str(opened)), 'New empty project')
+ root, identity = Path(project['path']), project['uuid']
+ runtimes = [root]
+ try:
+ if opened:
+ ensure_project_database(root)
+ open_empty(root)
+ transfer._remove_runtime(root)
+ prepared = transfer.prepare_project(root, base / ('package-' + str(opened)))
+ self.assertEqual(prepared['source_count'], 0)
+ self.assertTrue(prepared['verified'])
+ transfer._remove_runtime(root)
+ shutil.rmtree(root)
+ destination = base / ('recipient-' + str(opened))
+ received = transfer.restore_project(prepared['directory'], destination)
+ runtimes = [destination]
+ self.assertEqual(received['project_uuid'], identity)
+ self.assertEqual(received['source_count'], 0)
+ open_empty(destination)
+ finally:
+ for runtime in runtimes:
+ if runtime.exists():
+ transfer._remove_runtime(runtime)
+
def save_export(self, connection, root, identity):
# Match the production DatasetRevision fields; the locator is mutable,
# while recipe and exported bytes are frozen scientific evidence.
@@ -433,9 +641,11 @@ def test_complete_project_survives_fresh_path_and_fresh_runtime(self):
saved_export = self.save_export(connection, root, identity)
finally:
connection.close()
+ self.install_derived_authorities(root, identity)
result = transfer.prepare_project(root, base / 'shared-folder')
self.assertTrue(result['verified'])
package = Path(result['directory'])
+ self.assertNotIn('CREATE DEFINER=', (package / 'database.sql').read_text())
self.assertFalse((package / 'catalog.json').exists())
self.assertFalse((package / 'database').exists())
# Hashes establish integrity, not trust: even correctly resealed
@@ -483,6 +693,9 @@ def test_complete_project_survives_fresh_path_and_fresh_runtime(self):
self.assertEqual(cursor.fetchone()[0], str(target))
finally:
connection.close()
+ # Restore uses its restricted SQL user; derived root-owned
+ # triggers are recreated only by the local app bootstrap.
+ self.install_derived_authorities(target, identity)
self.assertNotEqual(results[0]['instance_uuid'], results[1]['instance_uuid'])
self.assertNotEqual((runtimes[0] / 'database/native-credentials.json').read_bytes(),
(runtimes[1] / 'database/native-credentials.json').read_bytes())
diff --git a/python/tests/test_workspace_portability_app.py b/python/tests/test_workspace_portability_app.py
new file mode 100644
index 0000000..5d28dc2
--- /dev/null
+++ b/python/tests/test_workspace_portability_app.py
@@ -0,0 +1,140 @@
+"""Opt-in production API roundtrip with a real Symphony H5 fixture.
+
+Set RIEKE_TEST_PORTABLE_APP=1 and RIEKE_PORTABILITY_H5=/path/to/recording.h5.
+All writes target disposable projects; the supplied recording is read only.
+"""
+import json
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import tempfile
+import unittest
+
+import workspace_portability as transfer
+from workspace_projects import create_project_at
+from workspace_project_database import ensure_project_database
+from workspace_recording_files import retain_recording
+
+
+API_CHECK = r'''
+import json, sys, uuid
+from pathlib import Path
+from workspace_api import create_app
+root, parser, receipt, mode = map(str, sys.argv[1:])
+if mode == 'seed':
+ from recording_workspace import write_json
+ write_json(Path(root) / 'logs/app-jobs' / (str(uuid.uuid4()) + '.json'),
+ {'status': 'failed', 'source': '/historical/sender/failed.h5',
+ 'error': 'Preserved diagnostic fixture', 'started_at': '2026-09-29T01:00:00+00:00',
+ 'finished_at': '2026-09-29T01:00:02+00:00', 'catalog_committed': False})
+app = create_app(root, parser)
+client = app.test_client()
+headers = {'X-Workspace-Request': '1'}
+def request(path, body=None, method='GET'):
+ response = client.open('/api/' + path, method=method, json=body, headers=headers)
+ assert response.status_code < 300, (path, response.status_code, response.get_json())
+ payload = response.get_json()
+ if path == 'jobs':
+ for job in payload['jobs']:
+ job.pop('heartbeat_at', None)
+ return payload
+service = app.extensions['workspace_service']
+if mode == 'seed':
+ row = next(row for row in service.rows.values() if row['streams'])
+ predicate = {'field': 'epoch', 'operator': 'eq', 'value': row['epoch_uuid']}
+ recipe = {'name': 'Portable audit query', 'description': 'Recipient reproduction check',
+ 'predicate': predicate, 'splits': 'cell', 'pinned': True}
+ preset = request('search-presets', recipe, 'POST')
+ request('search-presets/' + preset['preset_uuid'],
+ {**recipe, 'name': 'Portable audit query v2', 'expected_version': 1}, 'PUT')
+ request('explore/run', {'predicate': predicate, 'splits': 'cell'}, 'POST')
+ revision = request('explore/revisions',
+ {'predicate': predicate, 'splits': 'cell', 'name': 'Portable audit baseline'}, 'POST')
+ profile = request('annotation-profiles', {'display_name': 'Portable audit author'}, 'POST')
+ request('annotations', {'target_kind': 'epoch', 'target_uuids': [row['epoch_uuid']],
+ 'profile_uuid': profile['profile_uuid'], 'tags_add': ['portable-audit'],
+ 'expected_revisions': {row['epoch_uuid']: 0}}, 'POST')
+ protocol = next(identity for identity in service.protocols
+ if row['epoch_uuid'] in service.fingerprints(identity))
+ detail = request('protocols/' + protocol)
+ request('protocols/' + protocol + '/curation',
+ {'epoch_uuids': [row['epoch_uuid']], 'changes': {'included': False, 'review_state': 'approved'},
+ 'expected_revisions': {row['epoch_uuid']: 0}, 'query_revision': detail['query_revision']}, 'POST')
+ request('protocols/' + protocol + '/tree-layout',
+ {'split_order': ['cell', 'block'], 'expected_version': 0}, 'PUT')
+ request('project-preferences',
+ {'field': 'recent_searches', 'value': [{'id': 'portable-audit-search',
+ 'name': 'Portable audit recent search', 'predicate': predicate, 'splits': 'cell',
+ 'pinned': True, 'matched_count': 1, 'cell_count': 1}], 'expected_revision': 0}, 'PUT')
+ request('project-preferences',
+ {'field': 'protocol_shortcuts', 'value': {protocol: {'section': 'pinned', 'rank': 0}},
+ 'expected_revision': 0}, 'PUT')
+ endpoints = ['search-presets', 'search-presets/' + preset['preset_uuid'] + '/versions',
+ 'explore/revisions/' + revision['revision_uuid'],
+ 'epochs/' + row['epoch_uuid'] + '/annotations', 'annotation-profiles',
+ 'events?limit=100', 'jobs', 'project-preferences', 'protocols/' + protocol + '/masks/export',
+ 'protocols/' + protocol + '/tree-layout',
+ 'epochs/' + row['epoch_uuid'] + '/trace?stream_uuid=' + row['streams'][0]['uuid'] + '&start=3&count=16']
+ expected = {'project_uuid': service.project['project_uuid'], 'epochs': len(service.rows),
+ 'protocols': sorted(service.protocols),
+ 'responses': {endpoint: request(endpoint) for endpoint in endpoints}}
+ Path(receipt).write_text(json.dumps(expected))
+else:
+ expected = json.loads(Path(receipt).read_text())
+ assert service.project['project_uuid'] == expected['project_uuid']
+ assert len(service.rows) == expected['epochs']
+ assert sorted(service.protocols) == expected['protocols']
+ for endpoint, payload in expected['responses'].items():
+ assert request(endpoint) == payload, endpoint
+ assert all(Path(manifest['source_path']).is_relative_to(Path(root))
+ and Path(manifest['metadata_path']).is_relative_to(Path(root))
+ for manifest in service.manifests.values())
+ request('overview')
+ print('Recipient production API state and waveform checks passed', flush=True)
+app.extensions['app_state_session_lock'].close()
+service.dj.conn().close()
+'''
+
+
+@unittest.skipUnless(os.environ.get('RIEKE_TEST_PORTABLE_APP') == '1',
+ 'opt-in production API/native MySQL integration')
+class PortableAppTests(unittest.TestCase):
+ def test_recipient_opens_all_saved_scientific_state_without_donor(self):
+ source = Path(os.environ['RIEKE_PORTABILITY_H5']).expanduser().resolve(strict=True)
+ code = Path(__file__).resolve().parents[2]
+ parser = code / '.rieke-runtime/retinanalysis'
+ with tempfile.TemporaryDirectory(prefix='rieke-portable-app-') as temporary:
+ base = Path(temporary).resolve()
+ root, restored = base / 'donor', base / 'recipient'
+ create_project_at(str(root), 'Production portability audit')
+ runtimes = [root]
+ try:
+ ensure_project_database(root)
+ managed = retain_recording(root, source, transfer._hash(source))
+ env = {**os.environ, 'PYTHONPATH': str(code / 'python')}
+ self.run_checked([sys.executable, str(code / 'python/recording_workspace.py'),
+ str(managed), '--project-dir', str(root), '--retinanalysis', str(parser)], env)
+ receipt = base / 'expected.json'
+ self.run_checked([sys.executable, '-c', API_CHECK, str(root), str(parser), str(receipt), 'seed'], env)
+ prepared = transfer.prepare_project(root, base / 'portable-package')
+ self.assertTrue(prepared['verified'])
+ transfer._remove_runtime(root)
+ shutil.rmtree(root)
+ runtimes.remove(root)
+ received = transfer.restore_project(prepared['directory'], restored)
+ runtimes.append(restored)
+ self.assertTrue(received['verified'])
+ self.run_checked([sys.executable, '-c', API_CHECK, str(restored), str(parser), str(receipt), 'check'], env)
+ finally:
+ for runtime in runtimes:
+ transfer._remove_runtime(runtime)
+
+ def run_checked(self, command, env):
+ result = subprocess.run(command, env=env, capture_output=True, text=True, timeout=240)
+ self.assertEqual(result.returncode, 0, result.stdout[-8000:] + result.stderr[-8000:])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_workspace_project_preferences.py b/python/tests/test_workspace_project_preferences.py
new file mode 100644
index 0000000..7316599
--- /dev/null
+++ b/python/tests/test_workspace_project_preferences.py
@@ -0,0 +1,95 @@
+import json
+from pathlib import Path
+import shutil
+import tempfile
+import threading
+import unittest
+import uuid
+
+from flask import Flask
+from workspace_project_preferences import ProjectPreferences, PreferenceConflict, REFERENCE, register_project_preference_routes
+
+
+class PortablePreferenceTests(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp.cleanup)
+ self.root = Path(self.temp.name) / 'donor'
+ self.root.mkdir()
+ self.identity = str(uuid.uuid4())
+ self.store = ProjectPreferences(self.root, self.identity)
+ self.protocol = str(uuid.uuid4())
+ self.pins = {self.protocol: {'section': 'pinned', 'rank': 0}}
+ self.search = {'id': 'query', 'name': 'History noise', 'predicate':
+ {'field': 'protocol', 'operator': 'contains', 'value': 'History'},
+ 'splits': 'date,cell', 'pinned': True, 'matched_count': 8, 'cell_count': 2,
+ 'lastRunAt': '2026-09-29T20:00:00Z'}
+
+ def test_copy_preserves_search_and_sidebar_without_browser_or_sql(self):
+ self.store.save('recent_searches', [self.search], 0)
+ saved = self.store.save('protocol_shortcuts', self.pins, 0)
+ recipient = self.root.parent / 'recipient'
+ shutil.copytree(self.root, recipient)
+ shutil.rmtree(self.root)
+ reopened = ProjectPreferences(recipient, self.identity)
+ self.assertEqual(reopened.read(), saved)
+ self.assertEqual(saved['state']['recent_searches'], [self.search])
+ self.assertEqual(saved['state']['protocol_shortcuts'], self.pins)
+ self.assertFalse(list((recipient / 'protocols').glob('*.json')))
+
+ def test_disjoint_updates_do_not_conflict_and_stale_same_field_refused(self):
+ saved = self.store.save('recent_searches', [self.search], 0)
+ self.assertEqual(saved['revisions']['recent_searches'], 1)
+ other = self.store.save('protocol_shortcuts', self.pins, 0)
+ self.assertEqual(other['state']['recent_searches'], [self.search])
+ before = (self.root / REFERENCE).read_bytes()
+ with self.assertRaises(PreferenceConflict):
+ self.store.save('recent_searches', [], 0)
+ self.assertEqual((self.root / REFERENCE).read_bytes(), before)
+ self.assertEqual(self.store.save('recent_searches', [self.search], 1)['revisions']['recent_searches'], 1)
+
+ def test_bad_shapes_and_future_or_foreign_files_cannot_replace_state(self):
+ self.store.save('recent_searches', [self.search], 0)
+ path = self.root / REFERENCE
+ before = path.read_bytes()
+ for field, value in [('protocol_shortcuts', {self.protocol: {'section': 'unknown', 'rank': 0}}),
+ ('protocol_shortcuts', {'not-a-uuid': {'section': 'main', 'rank': 0}}),
+ ('protocol_shortcuts', {self.protocol: {'section': 'main', 'rank': True}}),
+ ('recent_searches', [dict(self.search, cell_count=True)]),
+ ('recent_searches', [dict(self.search, predicate={'field': 'protocol', 'operator': 'execute', 'value': 'x'})]),
+ ('recent_searches', [self.search, self.search]),
+ ('pane_width', 900)]:
+ with self.subTest(field=field, value=value):
+ with self.assertRaises(ValueError):
+ self.store.save(field, value, 0)
+ self.assertEqual(path.read_bytes(), before)
+ for changes in ({'version': 2}, {'version': True}, {'project_uuid': str(uuid.uuid4())}):
+ value = json.loads(before)
+ value.update(changes)
+ path.write_text(json.dumps(value))
+ with self.assertRaises(ValueError):
+ self.store.save('protocol_shortcuts', self.pins, 0)
+ self.assertEqual(json.loads(path.read_text()), value)
+
+ def test_preference_symlinks_are_never_followed(self):
+ target = self.root.parent / 'outside'
+ target.mkdir()
+ (self.root / 'protocols').symlink_to(target)
+ with self.assertRaisesRegex(ValueError, 'symbolic'):
+ self.store.save('recent_searches', [self.search], 0)
+ self.assertEqual(list(target.iterdir()), [])
+
+ def test_routes_expose_only_project_fields_with_conflict_status(self):
+ app = Flask(__name__)
+ register_project_preference_routes(app, self.root, self.identity, threading.RLock())
+ client = app.test_client()
+ self.assertEqual(client.get('/api/project-preferences').json['state'], {})
+ body = dict(field='recent_searches', value=[self.search], expected_revision=0)
+ self.assertEqual(client.put('/api/project-preferences', json=body).status_code, 200)
+ self.assertEqual(client.put('/api/project-preferences', json=body).status_code, 409)
+ self.assertEqual(client.put('/api/project-preferences', json={**body, 'field': 'port'}).status_code, 400)
+ self.assertEqual(client.get('/api/project-preferences?directory=/other').status_code, 400)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_workspace_project_servers.py b/python/tests/test_workspace_project_servers.py
index 4d76378..88b4b9b 100644
--- a/python/tests/test_workspace_project_servers.py
+++ b/python/tests/test_workspace_project_servers.py
@@ -1,5 +1,6 @@
import io
import json
+import os
import socket
from pathlib import Path
import tempfile
@@ -15,6 +16,9 @@ def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.path = Path(self.temp.name) / 'project'
+ index = patch.dict(os.environ, {'RIEKE_PROJECT_INDEX': str(Path(self.temp.name) / 'preferences/project-index.json')})
+ index.start()
+ self.addCleanup(index.stop)
self.path.mkdir()
bootstrap = patch('workspace_project_database.ensure_project_database')
bootstrap.start()
@@ -94,6 +98,25 @@ def test_existing_process_reused_without_spawn(self):
self.assertEqual(result['project_uuid'], self.identity)
spawn.assert_not_called()
+ def test_explicit_folder_wins_over_same_identity_native_copy(self):
+ copy = self.path.parent / 'copy'
+ copy.mkdir()
+ project = json.loads((self.path / 'project.json').read_text())
+ project['name'] = 'A copy sorted before the selected project'
+ (copy / 'project.json').write_text(json.dumps(project))
+ for directory in (self.path, copy):
+ catalog = json.loads((self.path / 'catalog.json').read_text())
+ catalog['managed_database'] = {'kind': 'native-mysql'}
+ (directory / 'catalog.json').write_text(json.dumps(catalog))
+ with patch('workspace_project_servers.ready_url', return_value='http://127.0.0.1:8877/') as ready:
+ open_project(self.path, self.identity, self.path)
+ ready.assert_called_once_with(self.path.resolve(), self.identity)
+ from workspace_desktop import desktop_open_project
+ services = Mock()
+ with patch('workspace_desktop._SERVICES', services):
+ desktop_open_project(self.path, self.identity, self.path)
+ services.open.assert_called_once_with(str(self.path.resolve()), self.identity, self.path, 300)
+
def test_copied_server_record_never_reuses_original_service(self):
write_server_record(self.path,self.identity,8877)
record=self.path/'logs/workspace-server.json'
diff --git a/python/tests/test_workspace_project_validation.py b/python/tests/test_workspace_project_validation.py
index 591f7c7..60d2b44 100644
--- a/python/tests/test_workspace_project_validation.py
+++ b/python/tests/test_workspace_project_validation.py
@@ -4,8 +4,8 @@
import unittest
from unittest.mock import patch
-from workspace_projects import create_project
-from workspace_project_validation import validate_project_folder
+from workspace_projects import create_project, create_project_at
+from workspace_project_validation import inspect_project_folder, validate_project_folder
class ProjectValidationTests(unittest.TestCase):
@@ -33,6 +33,42 @@ def test_new_project_validation_does_not_create_or_modify_files(self):
self.assertEqual(before, after)
self.assertEqual(result['database_status'], 'not_initialized')
self.assertEqual(result['project']['uuid'], self.record['uuid'])
+ self.assertEqual(inspect_project_folder(self.root)['kind'], 'project')
+
+ def prepared(self):
+ import workspace_portability as transfer
+ package = Path(self.temp.name) / 'received package'
+ package.mkdir()
+ (package / 'project.json').write_text((self.root / 'project.json').read_text())
+ (package / 'database.sql').write_text('-- logical backup fixture\n')
+ transfer._write(package / transfer.MANIFEST, {
+ 'format': transfer.FORMAT, 'version': 1, 'mode': 'complete',
+ 'database_format': 'mysql8-logical-v1', 'project_uuid': self.record['uuid'],
+ 'sources': [], 'files': transfer._files(package)})
+ return package
+
+ def test_prepared_copy_is_recognized_without_runtime_or_file_changes(self):
+ package = self.prepared()
+ before = {p.name: p.read_bytes() for p in package.iterdir()}
+ with patch('workspace_project_database.ensure_project_database', side_effect=AssertionError('No server')):
+ result = inspect_project_folder(package)
+ self.assertEqual(result['kind'], 'prepared-transfer')
+ self.assertEqual(result['database_status'], 'restore_required')
+ self.assertEqual(result['project']['uuid'], self.record['uuid'])
+ self.assertEqual(result['project']['name'], 'Validation')
+ self.assertEqual(result['source_count'], 0)
+ self.assertEqual(before, {p.name: p.read_bytes() for p in package.iterdir()})
+
+ def test_corrupt_received_copy_is_not_offered_as_an_openable_project(self):
+ package = self.prepared()
+ (package / 'database.sql').write_text('corrupt')
+ with self.assertRaisesRegex(ValueError, 'checksum|inventory'):
+ inspect_project_folder(package)
+
+ def test_received_manifest_cannot_bypass_validation_with_project_catalog(self):
+ (self.root / 'transfer.json').write_text('{"format":"future","version":2}')
+ with self.assertRaisesRegex(ValueError, 'Unsupported prepared project'):
+ inspect_project_folder(self.root)
def test_initialized_project_never_returns_credentials_or_chmods(self):
self.initialized()
@@ -93,6 +129,89 @@ def test_manifest_identity_mismatch_and_project_symlink_are_rejected(self):
with self.assertRaisesRegex(ValueError, 'UUIDs do not match'):
validate_project_folder(self.root)
+ def test_nested_selection_suggests_closest_root_without_opening_or_changes(self):
+ before = {str(p): (p.read_bytes(), p.stat().st_mode) for p in self.root.rglob('*') if p.is_file()}
+ with patch('workspace_project_database.ensure_project_database', side_effect=AssertionError('No server')):
+ result = inspect_project_folder(self.root / 'logs/imports')
+ self.assertFalse(result['valid'])
+ self.assertEqual(result['kind'], 'project-root-suggestions')
+ self.assertEqual(result['candidates'], [{'path': str(self.root), 'name': 'Validation',
+ 'kind': 'project', 'reason': 'containing-project'}])
+ after = {str(p): (p.read_bytes(), p.stat().st_mode) for p in self.root.rglob('*') if p.is_file()}
+ self.assertEqual(before, after)
+
+ def test_parent_with_several_projects_suggests_all_immediate_valid_roots(self):
+ second = create_project_at(str(self.root.parent / 'Exact second project'), 'Second project')
+ broken = self.root.parent / 'broken project'
+ broken.mkdir()
+ (broken / 'project.json').write_text('{"format":"unknown"}')
+ (self.root.parent / 'project alias').symlink_to(self.root, target_is_directory=True)
+ with patch('workspace_project_database.ensure_project_database', side_effect=AssertionError('No server')):
+ result = inspect_project_folder(self.root.parent)
+ self.assertFalse(result['valid'])
+ self.assertEqual(result['kind'], 'project-root-suggestions')
+ paths = {value['path'] for value in result['candidates']}
+ self.assertEqual(paths, {str(self.root), second['path']})
+ self.assertTrue(all(value['reason'] == 'child-project' for value in result['candidates']))
+ self.assertIn('Several projects', result['message'])
+
+ def test_prepared_root_suggestions_validate_complete_inventory(self):
+ package = self.prepared().resolve()
+ nested = package / 'protocols'
+ nested.mkdir()
+ result = inspect_project_folder(nested)
+ self.assertEqual(result['candidates'][0]['path'], str(package))
+ self.assertEqual(result['candidates'][0]['kind'], 'prepared-transfer')
+ (package / 'database.sql').write_text('corrupt')
+ with self.assertRaisesRegex(ValueError, 'project.json'):
+ inspect_project_folder(nested)
+
+ def test_no_recursive_child_scan_or_automatic_choice(self):
+ selected = Path(self.temp.name).resolve() / 'Parent selected by user'
+ selected.mkdir()
+ deeper = selected / 'one folder down'
+ deeper.mkdir()
+ create_project_at(str(deeper / 'too deep'), 'Not an immediate child')
+ with self.assertRaisesRegex(ValueError, 'project.json'):
+ inspect_project_folder(selected)
+ create_project_at(str(selected / 'correct root'), 'One direct child')
+ result = inspect_project_folder(selected)
+ self.assertFalse(result['valid'])
+ self.assertEqual(len(result['candidates']), 1)
+ self.assertEqual(result['candidates'][0]['path'], str(selected / 'correct root'))
+
+ def test_selected_project_can_live_far_outside_preferred_parent(self):
+ exact = Path(self.temp.name).resolve() / 'Research drives/2026/North lab/My chosen top folder'
+ exact.parent.mkdir(parents=True)
+ project = create_project_at(str(exact), 'Independent project')
+ with patch.dict('os.environ', {'RECORDING_WORKSPACE_ROOT': str(self.root.parent)}):
+ report = inspect_project_folder(exact)
+ nested = inspect_project_folder(exact / 'imports')
+ self.assertEqual(report['kind'], 'project')
+ self.assertEqual(report['project']['path'], str(exact))
+ self.assertEqual(report['project']['uuid'], project['uuid'])
+ self.assertEqual(nested['candidates'][0]['path'], str(exact))
+
+ def test_selected_alias_and_alias_children_are_never_suggested(self):
+ selected = Path(self.temp.name).resolve() / 'only aliases'
+ selected.mkdir()
+ alias = selected / 'alias'
+ alias.symlink_to(self.root, target_is_directory=True)
+ with self.assertRaisesRegex(ValueError, 'symbolic link'):
+ inspect_project_folder(alias)
+ with self.assertRaisesRegex(ValueError, 'project.json'):
+ inspect_project_folder(selected)
+ with self.assertRaisesRegex(ValueError, 'project.json'):
+ inspect_project_folder(alias / 'imports')
+
+ def test_ancestor_search_is_bounded(self):
+ selected = self.root / 'logs'
+ for index in range(9):
+ selected = selected / ('nested-' + str(index))
+ selected.mkdir(parents=True)
+ with self.assertRaisesRegex(ValueError, 'project.json'):
+ inspect_project_folder(selected)
+
if __name__ == '__main__':
unittest.main()
diff --git a/python/tests/test_workspace_projection_objects.py b/python/tests/test_workspace_projection_objects.py
new file mode 100644
index 0000000..57630ed
--- /dev/null
+++ b/python/tests/test_workspace_projection_objects.py
@@ -0,0 +1,151 @@
+import copy
+import hashlib
+import json
+from pathlib import Path
+import tempfile
+import unittest
+import uuid
+from unittest.mock import patch
+import zlib
+
+from workspace_metadata_objects import canonical
+from workspace_projection_cache import ProjectionCache, VERSION
+from workspace_recipes import checksum
+
+
+def fixture(count=60):
+ parents = {kind: str(uuid.uuid4()) for kind in ('cell', 'group', 'block')}
+ metadata = {kind: {'uuid': identity, 'label': 'same label'} for kind, identity in parents.items()}
+ metadata['block']['frameTimes'] = [[position / 123. for position in range(1000)] for _ in range(4)]
+ rows, details = {}, {}
+ for position in range(count):
+ identity = str(uuid.uuid4())
+ rows[identity] = {'epoch_uuid': identity, 'source_sha256': 'a'*64,
+ **{kind+'_uuid': value for kind, value in parents.items()}}
+ details[identity] = {'metadata': {**metadata, 'epoch': {'uuid': identity, 'number': position}},
+ 'parameters': {'seed': position}}
+ return dict(rows=rows, details=details, cells={}, source={}, fingerprints={})
+
+
+class ProjectionObjectTests(unittest.TestCase):
+ def setUp(self):
+ self.folder = tempfile.TemporaryDirectory()
+ self.addCleanup(self.folder.cleanup)
+ self.cache = ProjectionCache(self.folder.name)
+
+ def document(self, key='key'):
+ return json.loads(zlib.decompress(self.cache._path(key).read_bytes()))
+
+ def reseal(self, document):
+ path = self.cache._path('key')
+ payload = zlib.compress(canonical(document))
+ path.write_bytes(payload)
+ path.with_suffix('.sha256').write_text(hashlib.sha256(payload).hexdigest())
+
+ def test_normalized_lazy_exact_independent_reconstruction(self):
+ value = fixture()
+ path = Path(self.cache.write('key', value))
+ document = self.document()
+ self.assertEqual(document['version'], 2)
+ self.assertEqual(len(document['objects']), 3)
+ for detail in document['details'].values():
+ self.assertEqual(set(detail['objects']), {'cell', 'group', 'block'})
+ self.assertEqual(set(detail['inline']['metadata']), {'epoch'})
+ raw_repeated = canonical(value)
+ raw_normalized = zlib.decompress(path.read_bytes())
+ self.assertLess(len(raw_normalized), len(raw_repeated) / 10)
+ self.assertLess(path.stat().st_size, len(zlib.compress(raw_repeated, 1)) / 5)
+ loaded = self.cache.read('key')
+ details = loaded['details']
+ self.assertEqual(details._decoder.object_decodes, 0)
+ for identity in value['rows']:
+ self.assertEqual(canonical(details[identity]), canonical(value['details'][identity]))
+ self.assertEqual(details._decoder.object_decodes, 3)
+ first, second = list(details)[:2]
+ details[first]['metadata']['block']['frameTimes'][0][0] = 'changed'
+ self.assertEqual(details[second], value['details'][second])
+ self.assertEqual(details[first], value['details'][first])
+ loaded['rows'][first]['block_uuid'] = str(uuid.uuid4())
+ self.assertEqual(details[first], value['details'][first])
+ # It is entirely memory-owned after reading, so reclamation is safe.
+ self.cache.publish([])
+ self.assertFalse(path.exists())
+ self.assertEqual(details[first], value['details'][first])
+
+ def test_conflicting_identity_rejects_before_replacing_previous_cache(self):
+ value = fixture(2)
+ self.cache.write('key', value)
+ old = self.cache._path('key').read_bytes()
+ bad = copy.deepcopy(value)
+ second = list(bad['details'])[1]
+ bad['details'][second]['metadata']['block'] = copy.deepcopy(bad['details'][second]['metadata']['block'])
+ bad['details'][second]['metadata']['block']['frameTimes'][0][0] = 999
+ with self.assertRaisesRegex(ValueError, 'Conflicting ancestor'):
+ self.cache.write('key', bad)
+ self.assertEqual(self.cache._path('key').read_bytes(), old)
+ self.assertEqual(self.cache.read('key'), value)
+ self.assertFalse(list(Path(self.folder.name).glob('*.writing*')))
+
+ def test_reference_ownership_and_malformed_objects_fail_closed(self):
+ self.cache.write('key', fixture(2))
+ original = self.document()
+ for mutation in ('source', 'uuid', 'missing', 'bool', 'inline', 'duplicate', 'bad-base64', 'digest'):
+ with self.subTest(mutation=mutation):
+ document = copy.deepcopy(original)
+ first = next(iter(document['details'].values()))
+ if mutation == 'source':
+ document['objects'][0]['source_sha'] = 'other'
+ elif mutation == 'uuid':
+ document['objects'][0]['uuid'] = str(uuid.uuid4())
+ elif mutation == 'missing':
+ first['objects']['cell'] = 999
+ elif mutation == 'bool':
+ first['objects']['cell'] = True
+ elif mutation == 'inline':
+ first['inline']['metadata']['cell'] = {}
+ elif mutation == 'duplicate':
+ document['objects'].append(document['objects'][0])
+ elif mutation == 'bad-base64':
+ document['objects'][0]['payload'] = '**not base64**'
+ else:
+ document['objects'][0]['sha256'] = 'z'*64
+ self.reseal(document)
+ self.assertIsNone(self.cache.read('key'))
+ # Inner checksums are evaluated only for requested metadata objects.
+ document = copy.deepcopy(original)
+ document['objects'][0]['sha256'] = '0'*64
+ self.reseal(document)
+ loaded = self.cache.read('key')
+ self.assertEqual(loaded['details']._decoder.object_decodes, 0)
+ with self.assertRaisesRegex(ValueError, 'checksum'):
+ loaded['details'][next(iter(loaded['details']))]
+
+ def test_equal_names_do_not_merge_different_uuids_or_sources(self):
+ value = fixture(3)
+ second, third = list(value['rows'])[1:]
+ value['details'][second] = copy.deepcopy(value['details'][second])
+ new_uuid = str(uuid.uuid4())
+ value['rows'][second]['block_uuid'] = new_uuid
+ value['details'][second]['metadata']['block']['uuid'] = new_uuid
+ value['rows'][third]['source_sha256'] = 'b'*64
+ self.cache.write('key', value)
+ self.assertEqual(len(self.document()['objects']), 7)
+ self.assertEqual(self.cache.read('key'), value)
+
+ def test_streaming_writer_never_serializes_whole_projection(self):
+ value = fixture(3)
+ original = json.dumps
+ def guarded(obj, *args, **kwargs):
+ self.assertIsNot(obj, value)
+ self.assertFalse(isinstance(obj, dict) and 'projection' in obj)
+ return original(obj, *args, **kwargs)
+ with patch('json.dumps', side_effect=guarded):
+ self.cache.write('key', value)
+ self.assertEqual(self.cache.read('key'), value)
+ old_path = Path(self.folder.name) / (checksum({'version': 1, 'key': 'key'}) + '.json.zlib')
+ self.assertNotEqual(old_path, self.cache._path('key'))
+ self.assertEqual(VERSION, 2)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_workspace_projects.py b/python/tests/test_workspace_projects.py
index ff369c9..aac3430 100644
--- a/python/tests/test_workspace_projects.py
+++ b/python/tests/test_workspace_projects.py
@@ -1,8 +1,10 @@
import json
+import os
from pathlib import Path
import tempfile
import unittest
import uuid
+from unittest.mock import patch
from workspace_projects import list_projects, MANIFEST_LIMIT
@@ -12,6 +14,9 @@ def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
+ isolated_index = patch.dict(os.environ, {'RIEKE_PROJECT_INDEX': str(self.root / '.user-state/project-index.json')})
+ isolated_index.start()
+ self.addCleanup(isolated_index.stop)
self.current = self.make_project('current', 'Current project')
def make_project(self, folder, name, identity=None, display_name=None):
@@ -90,10 +95,11 @@ def test_order_route_accepts_only_a_complete_validated_folder_list(self):
result = client.post('/api/projects/order', json={'paths': paths})
self.assertEqual(result.status_code, 200, result.get_json())
self.assertEqual([row['path'] for row in result.get_json()['projects']], paths)
- before = (self.root / '.rieke-os.json').read_text()
+ from workspace_startup_registry import project_index_path
+ before = project_index_path().read_text()
for invalid in [paths[:1], [paths[0], paths[0]], paths + ['/outside'], 'bad', [1], None]:
self.assertEqual(client.post('/api/projects/order', json={'paths': invalid}).status_code, 400)
- self.assertEqual((self.root / '.rieke-os.json').read_text(), before)
+ self.assertEqual(project_index_path().read_text(), before)
def test_only_valid_immediate_siblings_are_discovered(self):
self.make_project('container/nested', 'Nested project')
@@ -163,6 +169,93 @@ def test_boolean_versions_and_oversized_manifests_are_rejected(self):
(oversized / 'catalog.json').write_text(' ' * (MANIFEST_LIMIT + 1))
self.assertEqual(len(list_projects(self.current)['projects']), 1)
+ def test_remembered_external_root_survives_preferred_root_changes_without_scanning_neighbors(self):
+ from workspace_projects import list_managed_projects
+ from workspace_startup_registry import remember_project_path
+ outside = self.make_project('outside/selected', 'External study')
+ self.make_project('outside/not-selected', 'Unremembered neighbor')
+ saved = remember_project_path(outside.resolve(), set_last=True)
+ self.assertEqual(saved['last_project_path'], str(outside.resolve()))
+ before = (outside / 'project.json').read_bytes()
+ for preferred in [self.root / 'empty-preferred', self.root / 'another-preferred']:
+ result = list_managed_projects(preferred)
+ self.assertFalse(preferred.exists())
+ self.assertEqual([project['path'] for project in result['projects']], [str(outside.resolve())])
+ self.assertEqual(result['last_project_path'], str(outside.resolve()))
+ self.assertEqual(result['last_project_uuid'], saved['projects'][0]['project_uuid'])
+ result = list_projects(self.current)
+ self.assertEqual({project['name'] for project in result['projects']}, {'Current project', 'External study'})
+ self.assertEqual((outside / 'project.json').read_bytes(), before)
+ self.assertNotIn('password', json.dumps(saved))
+ self.assertFalse((outside.parent / '.rieke-os.json').exists())
+
+ def test_missing_corrupt_or_repurposed_remembered_folder_remains_visible_unavailable(self):
+ from workspace_projects import list_managed_projects
+ from workspace_startup_registry import remember_project_path
+ outside = self.make_project('outside/selected', 'External study')
+ saved = remember_project_path(outside.resolve())['projects'][0]
+ manifest = (outside / 'project.json').read_text()
+ for content in ['not json', json.dumps({**json.loads(manifest), 'project_uuid': str(uuid.uuid4())})]:
+ (outside / 'project.json').write_text(content)
+ project = list_managed_projects(self.root / 'preferred')['projects'][0]
+ self.assertFalse(project['available'])
+ self.assertEqual(project['uuid'], saved['project_uuid'])
+ self.assertEqual(project['name'], 'External study')
+ (outside / 'project.json').unlink()
+ (outside / 'catalog.json').unlink()
+ outside.rmdir()
+ project = list_managed_projects(self.root / 'preferred')['projects'][0]
+ self.assertFalse(project['available'])
+ self.assertIn('reconnect', project['unavailable_reason'])
+
+ def test_native_external_copies_keep_path_identity_recent_and_global_order(self):
+ from workspace_projects import create_project_at, list_managed_projects
+ from workspace_startup_registry import remember_project_path, remember_project_paths_order
+ first = create_project_at(str(self.root / 'elsewhere/first'), 'Same study')
+ second = self.root / 'other/copy'
+ import shutil
+ second.parent.mkdir()
+ shutil.copytree(first['path'], second)
+ remember_project_path(first['path'], set_last=True)
+ remember_project_path(second.resolve(), set_last=True)
+ paths = [str(second.resolve()), first['path']]
+ remember_project_paths_order(paths)
+ inventory = list_managed_projects(self.root / 'unrelated')
+ self.assertEqual([project['path'] for project in inventory['projects']], paths)
+ self.assertEqual({project['uuid'] for project in inventory['projects']}, {first['uuid']})
+ self.assertEqual(inventory['last_project_path'], str(second.resolve()))
+ self.assertEqual(inventory['last_project_uuid'], first['uuid'])
+ self.assertEqual([project['path'] for project in list_projects(Path(first['path']))['projects']], paths)
+ self.assertFalse((self.root / 'elsewhere/.project-create.lock').exists())
+ self.assertFalse((self.root / 'elsewhere/.rieke-os.json').exists())
+
+ def test_relocation_updates_only_local_reference_and_current_recent_path(self):
+ from workspace_projects import list_managed_projects
+ from workspace_startup_registry import remember_project_path, remember_project_paths_order, read_project_index
+ outside = self.make_project('outside/selected', 'External study')
+ remember_project_path(outside.resolve(), set_last=True)
+ remember_project_paths_order([str(outside.resolve())])
+ moved = self.root / 'moved'
+ previous = outside.resolve()
+ outside.rename(moved)
+ remember_project_path(moved.resolve(), previous_directory=previous)
+ index = read_project_index()
+ self.assertEqual([project['path'] for project in index['projects']], [str(moved.resolve())])
+ self.assertEqual(index['last_project_path'], str(moved.resolve()))
+ self.assertEqual(index['project_order'], [str(moved.resolve())])
+ self.assertEqual(len(list_managed_projects(self.root)['projects']), 2)
+
+ def test_prepared_packages_are_not_live_selectable_or_remembered_projects(self):
+ from workspace_projects import list_managed_projects
+ from workspace_startup_registry import remember_project_path, project_index_path
+ package = self.make_project('received', 'Prepared study')
+ (package / 'transfer.json').write_text('{}')
+ self.assertEqual(len(list_managed_projects(self.root)['projects']), 1)
+ self.assertEqual(len(list_projects(self.current)['projects']), 1)
+ with self.assertRaisesRegex(ValueError, 'restored'):
+ remember_project_path(package.resolve())
+ self.assertFalse(project_index_path().exists())
+
if __name__ == '__main__':
unittest.main()
diff --git a/python/tests/test_workspace_protocol_state.py b/python/tests/test_workspace_protocol_state.py
new file mode 100644
index 0000000..7d0cb9c
--- /dev/null
+++ b/python/tests/test_workspace_protocol_state.py
@@ -0,0 +1,681 @@
+"""Exact global revision reuse: fresh inputs, cross-client writes, bounded memory."""
+import copy
+import contextlib
+import json
+import sys
+from pathlib import Path
+import unittest
+from unittest.mock import patch
+import uuid
+
+from workspace_disk_index import DiskMetadataIndex
+from workspace_protocol_state import ProtocolStateReader
+from workspace_service import WorkspaceService
+import test_workspace_annotations as annotation_fixture
+
+
+class ProtocolStateTests(unittest.TestCase):
+ def setUp(self):
+ self.fixture=annotation_fixture.SharedAnnotationTests()
+ self.fixture.setUp()
+ self.addCleanup(self.fixture.doCleanups)
+ self.service=self.fixture.service
+ # Exercise the native read contract using disposable fixture state.
+ # Subclass/overridden adapter policies intentionally use the full oracle.
+ self.service.__class__=WorkspaceService
+ self.case=self.fixture.case
+ self.client=self.fixture.client
+ self.protocol=self.service.protocol_id
+ self.ids=self.service.ids
+ self.store=self.case.store
+ self.index=DiskMetadataIndex.build(Path(self.case.temp.name)/'revisions.sqlite',
+ self.service.rows,self.service.details,self.service.sources,'revision-generation',self.service.project['project_uuid'])
+ self.service.disk_index,self.service.details=self.index,self.index.details
+ self.reader=self.fixture.app.extensions['protocol_state_reader']
+
+ def native_generation(self):
+ from workspace_state_generation import GenerationToken
+ from workspace_recipes import checksum
+ owner=self
+ class FixtureAuthority:
+ def __init__(self):self.locked_checks=0;self.disabled=False;self.in_transaction=False
+ def token(self,protocol=None):
+ return None if self.disabled or self.in_transaction else self._token(protocol)
+ def _token(self,protocol=None):
+ saved=checksum(owner.case.curation.rows)
+ shared=checksum(json.dumps(owner.fixture.records.rows,sort_keys=True,default=str))
+ return GenerationToken('test-native-authority',owner.service.project['project_uuid'],
+ 'shared-epoch',int(shared[:12],16),protocol,'protocol-epoch' if protocol else None,
+ int(saved[:12],16) if protocol else None)
+ def assert_current_locked(self,expected):
+ from workspace_curation import RevisionConflict
+ self.locked_checks+=1
+ if self._token(expected.protocol_uuid)!=expected:raise RevisionConflict({'generation':'changed'})
+ tracker=FixtureAuthority()
+ self.store.state_generation=tracker
+ return tracker
+
+ def test_native_v3_cold_and_warm_skip_full_curation_oracle_and_agree_with_materialized_state(self):
+ tracker=self.native_generation()
+ self.save_curation(tags_add=['dense','second'])
+ with patch.object(self.reader,'full_state',side_effect=AssertionError('Native pages do not hydrate all curation')):
+ first=self.page();second=self.page()
+ self.assertEqual(first,second)
+ self.assertTrue(first['query_revision'].startswith('protocol-state-v3:'))
+ self.assertEqual(first['query_revision_contract'],'protocol-state-v3')
+ self.assertEqual(first['query_revision'],self.reader.materialized_state(self.protocol)[2])
+ self.case.curation.rows[0]['tags']=['same-version change']
+ with patch.object(self.reader,'full_state',side_effect=AssertionError('Generation change still reads selected rows only')):
+ changed=self.page()
+ self.assertNotEqual(first['query_revision'],changed['query_revision'])
+ tracker.disabled=True
+ fallback=self.page()
+ self.assertFalse(fallback['query_revision'].startswith('protocol-state-v3:'))
+ self.assertEqual(first['expected_binding_version'],0)
+ self.assertEqual(fallback['expected_binding_version'],0)
+
+ def test_native_page_fences_once_and_reads_selected_curation_once(self):
+ tracker=self.native_generation()
+ self.page() # Establish the exact source proof before counting hot reads.
+ self.case.curation.read_log.clear()
+ with patch.object(tracker,'token',wraps=tracker.token) as token:
+ page=self.page()
+ self.assertEqual(token.call_count,2)
+ self.assertEqual(len(self.case.curation.read_log),1)
+ self.assertEqual(self.case.curation.read_log[0]['restrictions'][-1],[{'epoch_uuid':self.ids[0]}])
+ self.assertEqual(page['expected_binding_version'],0)
+
+ def test_native_response_scope_rejects_metadata_and_raw_saved_row_races(self):
+ from workspace_curation import RevisionConflict
+ self.native_generation()
+ self.page()
+ provider=self.service.curation_provider
+ with self.assertRaises(RevisionConflict):
+ with self.reader.native_read(self.protocol,provider=provider) as read:
+ self.assertIsNotNone(read)
+ read.selected(self.ids[:1])
+ self.service._fingerprints[self.ids[0]]='e'*64
+ with self.assertRaises(RevisionConflict):
+ with self.reader.native_read(self.protocol,provider=provider) as read:
+ read.selected(self.ids[:1])
+ self.case.curation.rows.append({'project_uuid':self.service.project['project_uuid'],
+ 'protocol_uuid':self.protocol,'epoch_uuid':self.ids[1],'included':True,
+ 'tags':['external'],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':self.service._fingerprints[self.ids[1]]})
+ with self.assertRaisesRegex(RuntimeError,'closed'):read.selected(self.ids[:1])
+
+ def test_native_response_scope_preserves_order_and_rejects_duplicate_or_foreign_ids(self):
+ self.native_generation()
+ with self.reader.native_read(self.protocol,provider=self.service.curation_provider) as read:
+ self.assertEqual(list(read.selected(list(reversed(self.ids)))),list(reversed(self.ids)))
+ with self.assertRaisesRegex(ValueError,'Duplicate'):read.selected([self.ids[0],self.ids[0]])
+ with self.assertRaisesRegex(ValueError,'outside'):read.selected([str(uuid.uuid4())])
+
+ def test_native_http_rejects_external_change_after_provenance_hydration(self):
+ tracker=self.native_generation()
+ self.page()
+ code=self.reader.shared.for_epochs.__code__
+ changed=[];previous=sys.getprofile()
+ def observe(frame,event,arg):
+ if event=='return' and frame.f_code is code and not changed:
+ changed.append(True)
+ self.case.curation.rows.append({'project_uuid':self.service.project['project_uuid'],
+ 'protocol_uuid':self.protocol,'epoch_uuid':self.ids[1],'included':True,
+ 'tags':['external write after visible provenance'],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':self.service._fingerprints[self.ids[1]]})
+ try:
+ sys.setprofile(observe)
+ with patch.object(tracker,'token',wraps=tracker.token) as token:
+ response=self.client.get(self.case.base+'/epochs?limit=1')
+ finally:sys.setprofile(previous)
+ self.assertEqual(changed,[True])
+ self.assertEqual(token.call_count,2) # Native path, with final attestation.
+ self.assertEqual(response.status_code,409,response.get_json())
+
+ def test_native_cell_receipt_rejects_unbound_cell_metadata_change_during_projection(self):
+ self.native_generation()
+ self.page()
+ code=self.service.epoch_page.__code__
+ previous=sys.getprofile();changed=[]
+ def observe(frame,event,arg):
+ if event=='return' and frame.f_code is code and not changed:
+ changed.append(True)
+ self.service.cells[self.service.rows[self.ids[0]]['cell_uuid']]['label']='changed in place'
+ try:
+ sys.setprofile(observe)
+ response=self.client.get(self.case.base+'/epochs?limit=1&include_cells=true')
+ finally:sys.setprofile(previous)
+ self.assertEqual(changed,[True])
+ self.assertEqual(response.status_code,409,response.get_json())
+ current=self.client.get(self.case.base+'/epochs?limit=1&include_cells=true')
+ self.assertEqual(current.status_code,200,current.get_json())
+ self.assertTrue(any(cell['label']=='changed in place' for cell in current.get_json()['cells']))
+
+ def test_scoped_shared_index_rebuilds_after_refresh_provenance_metadata_or_final_sql_race(self):
+ from types import SimpleNamespace
+ from workspace_state_generation import StateGenerationAuthority
+ from workspace_shared_tag_index import SharedTagIndex
+ from workspace_recipes import checksum
+ self.fixture.edit('epoch',self.ids[0],['keep'])
+ original_rows=copy.deepcopy(self.fixture.records.rows)
+ original_fingerprints=dict(self.service._fingerprints)
+ for phase in ('refresh','membership','metadata','final_sql'):
+ with self.subTest(phase=phase):
+ self.fixture.records.rows[:]=copy.deepcopy(original_rows)
+ self.service._fingerprints.clear();self.service._fingerprints.update(original_fingerprints)
+ state={'fired':False,'fail_sql':False}
+ tracker=StateGenerationAuthority(SimpleNamespace(_conn=object(),in_transaction=False),
+ self.service.project['project_uuid'])
+ tracker.ready=True
+ def contract():
+ if state['fail_sql']:
+ state['fail_sql']=False
+ raise RuntimeError('Injected final SQL attestation failure')
+ return 'fixture-authority:'+str(tracker._connection_epoch)
+ tracker._contract=contract
+ tracker._scope=lambda kind,identity: ('scope epoch',int(checksum(
+ json.dumps(self.fixture.records.rows if kind=='shared_annotations' else self.case.curation.rows,
+ sort_keys=True,default=str))[:12],16))
+ self.store.state_generation=tracker;self.reader.shared.state_generation=tracker
+ self.assertIsNotNone(self.reader.native_context(self.protocol),tracker.reason)
+ def change():
+ if state['fired']:return
+ state['fired']=True
+ if phase in ('refresh','membership'):self.fixture.records.rows[0]['tags']=['changed']
+ elif phase=='metadata':self.service._fingerprints[self.ids[0]]='f'*64
+ else:state['fail_sql']=True
+ def records(keys):
+ for row in copy.deepcopy(self.fixture.records.rows):
+ yield row
+ if phase=='refresh':change()
+ index=SharedTagIndex(generation=tracker.token,changes=lambda previous:None,
+ records=records,validate=self.reader.shared._index_record_tags)
+ self.addCleanup(index.close)
+ self.reader.shared._shared_tag_index=index
+ self.reader.shared._shared_tag_index_tracker=tracker
+ code=(index.matching.__wrapped__.__code__ if phase=='membership'
+ else self.reader.shared.for_epochs.__code__)
+ previous=sys.getprofile()
+ def observe(frame,event,arg):
+ if phase!='refresh' and event=='return' and frame.f_code is code:change()
+ try:
+ sys.setprofile(observe)
+ response=self.client.get(self.case.base+'/epochs?limit=1&tag=keep&include_cells=true')
+ finally:sys.setprofile(previous)
+ self.assertTrue(state['fired'])
+ self.assertEqual(response.status_code,409,response.get_json())
+ self.assertIsNone(index.connection)
+ expected='changed' if phase in ('refresh','membership') else 'keep'
+ response=self.client.get(self.case.base+'/epochs',query_string={'limit':1,'tag':expected,'include_cells':'true'})
+ self.assertEqual(response.status_code,200,response.get_json())
+ self.assertEqual(response.get_json()['total'],1)
+ self.assertEqual(response.get_json()['epochs'][0]['epoch_uuid'],self.ids[0])
+ self.assertEqual([item['tag'] for item in response.get_json()['epochs'][0]['annotations']['epoch_tags']],[expected])
+ self.assertGreaterEqual(index.stats['full_builds'],2)
+ index.close()
+
+ def test_replaced_page_provider_uses_existing_public_service_read(self):
+ self.native_generation()
+ original=self.service.curation_provider
+ with patch.object(self.service,'curation_provider',wraps=original) as provider:
+ self.page()
+ provider.assert_called_once()
+
+ def test_epoch_page_cell_receipt_covers_full_filtered_membership_in_native_and_fallback(self):
+ tracker=self.native_generation()
+ cell=self.service.rows[self.ids[0]]['cell_uuid']
+ self.fixture.edit('cell',cell,['filtered cell'])
+ for disabled in (False,True):
+ tracker.disabled=disabled
+ for filters in ({},{'cell_uuid':cell},{'tag':'filtered cell'}):
+ summary=self.client.get(self.case.base,query_string=filters).get_json()
+ response=self.client.get(self.case.base+'/epochs',query_string={**filters,'limit':1,'include_cells':'true'})
+ self.assertEqual(response.status_code,200,response.get_json())
+ page=response.get_json()
+ self.assertEqual({item['cell_uuid']:item['epochs'] for item in page['cells']},
+ {item['cell_uuid']:item['epochs'] for item in summary['cells']})
+ self.assertEqual(sum(item['epochs'] for item in page['cells']),page['total'])
+ self.assertEqual(page['expected_binding_version'],summary['expected_binding_version'])
+ self.assertEqual(page['query_revision'],summary['query_revision'])
+ self.assertLessEqual(len(page['epochs']),1)
+ for query in ('include_cells=1','include_cells=true&include_cells=false'):
+ self.assertEqual(self.client.get(self.case.base+'/epochs?'+query).status_code,400)
+
+ def test_registered_metadata_fields_do_not_load_scope_values_or_saved_state(self):
+ expected=self.index.catalog()['fields']
+ with patch.object(self.index,'values',side_effect=AssertionError('No epoch value materialization')), \
+ patch.object(self.service,'filtered_rows',side_effect=AssertionError('No filtered epoch scan')), \
+ patch.object(self.service,'_tree_rows',side_effect=AssertionError('No tree epoch scan')), \
+ patch.object(self.reader,'full_state',side_effect=AssertionError('No saved-state scan')):
+ response=self.client.get('/api/metadata/fields')
+ self.assertEqual(response.status_code,200,response.get_json())
+ fields=response.get_json()['fields']
+ self.assertEqual([field['id'] for field in fields],[field['id'] for field in expected])
+ self.assertEqual([field.get('grouping_role') for field in fields],[field.get('grouping_role') for field in expected])
+ self.assertTrue(all('distinct_count' not in field and 'examples' not in field for field in fields))
+ fields[0]['label']='changed output'
+ self.assertNotEqual(self.service.metadata_fields()['fields'][0]['label'],'changed output')
+ self.assertEqual(self.client.get('/api/metadata/fields?tag=hidden').status_code,400)
+ self.service._loaded=False
+ with self.assertRaises(RuntimeError):self.service.metadata_fields()
+ self.service._loaded=True
+ with self.index.path.open('ab') as stream:stream.write(b'changed seal')
+ with self.assertRaises(ValueError):self.service.metadata_fields()
+
+ def test_native_protocols_share_exact_source_proofs_without_aliasing_mutable_inputs(self):
+ from workspace_protocol_state import _retained_bytes,STATE_CACHE_BYTES
+ self.native_generation()
+ other=str(uuid.uuid4())
+ self.service.protocols[other]=copy.deepcopy(self.service.protocols[self.protocol])
+ self.service.protocols[other]['definition']['protocol_uuid']=other
+ self.service.protocols[other]['result']['protocol_uuid']=other
+ first=self.reader.native_context(self.protocol)
+ second=self.reader.native_context(other)
+ left=self.reader.native_cache[self.protocol];right=self.reader.native_cache[other]
+ self.assertIs(left['metadata']['fingerprints'],right['metadata']['fingerprints'])
+ self.assertIs(left['metadata']['query']['epochs'],right['metadata']['query']['epochs'])
+ self.assertIs(left['members'],right['members']);self.assertIs(left['allowed'],right['allowed'])
+ self.assertIsNot(left['metadata']['fingerprints'],self.service._fingerprints)
+ self.assertIsNot(left['metadata']['query']['epochs'],self.service.protocols[self.protocol]['result']['epochs'])
+ retained=_retained_bytes(self.reader.native_cache,STATE_CACHE_BYTES)+4096*len(self.reader.native_cache)
+ repeated=sum(entry['size'] for entry in self.reader.native_cache.values())
+ self.assertLess(retained,repeated)
+ self.reader.native_cache.clear()
+ with patch('workspace_protocol_state.STATE_CACHE_BYTES',retained+256):
+ self.reader.native_context(self.protocol);self.reader.native_context(other)
+ self.assertEqual(set(self.reader.native_cache),{self.protocol,other})
+ self.service._fingerprints[self.ids[0]]='d'*64
+ changed=self.reader.native_context(other)
+ self.assertNotEqual(second['query_revision'],changed['query_revision'])
+ self.assertNotEqual(left['metadata']['fingerprints'][self.ids[0]],'d'*64)
+ self.assertNotEqual(first['query_revision'],self.reader.native_context(self.protocol)['query_revision'])
+ self.service.protocols[other]['result']['epochs'].reverse()
+ changed_again=self.reader.native_context(other)
+ self.assertNotEqual(changed['query_revision'],changed_again['query_revision'])
+
+ def test_specialized_epoch_comparison_matches_recursive_type_and_shape_contract(self):
+ from workspace_protocol_state import _plain_epoch_records,_epoch_query_equal,_exact_equal
+ class StringSubclass(str):pass
+ class DictSubclass(dict):pass
+ original={'epochs':[{'uuid':'first','metadata_hash':'a'},{'uuid':'second','metadata_hash':'b'}],
+ 'numeric_guard':0.0,'nested':{'items':[True,1,1.0]}}
+ self.assertTrue(_plain_epoch_records(original['epochs']))
+ variants=[]
+ for field,value in [('uuid',False),('uuid',1),('metadata_hash',StringSubclass('a')),
+ ('metadata_hash',None),('extra','x')]:
+ candidate=copy.deepcopy(original);candidate['epochs'][0][field]=value;variants.append(candidate)
+ candidate=copy.deepcopy(original);candidate['epochs'][0]=DictSubclass(candidate['epochs'][0]);variants.append(candidate)
+ candidate=copy.deepcopy(original);candidate['epochs'].reverse();variants.append(candidate)
+ candidate=copy.deepcopy(original);candidate['epochs']=tuple(candidate['epochs']);variants.append(candidate)
+ for value in (False,0,-0.0):
+ candidate=copy.deepcopy(original);candidate['numeric_guard']=value;variants.append(candidate)
+ variants.append(copy.deepcopy(original))
+ for candidate in variants:
+ self.assertEqual(_epoch_query_equal(candidate,original),_exact_equal(candidate,original),candidate)
+
+ def test_native_plain_epoch_proof_preserves_other_query_numeric_types(self):
+ self.native_generation()
+ revisions=[]
+ for value in (1,True,1.0,0.0,-0.0):
+ self.service.protocols[self.protocol]['result']['numeric_guard']=value
+ revisions.append(self.page()['query_revision'])
+ self.assertTrue(self.reader.native_cache[self.protocol]['plain_epochs'])
+ self.assertEqual(len(set(revisions)),len(revisions))
+
+ def test_native_v3_mutation_uses_selected_context_and_transaction_preflight(self):
+ tracker=self.native_generation()
+ before=self.page()
+ first=before['epochs'][0]['epoch_uuid']
+ body={'epoch_uuids':[first],'changes':{'tags_add':['saved']},'expected_revisions':{first:0},
+ 'query_revision':before['query_revision'],'expected_binding_version':0}
+ with patch.object(self.reader,'full_state',side_effect=AssertionError('Native selected mutation must not materialize protocol')):
+ response=self.client.post(self.case.base+'/curation',json=body,headers=self.case.headers)
+ after=self.page()
+ self.assertEqual(response.status_code,200,response.get_json())
+ self.assertEqual(tracker.locked_checks,1)
+ self.assertNotEqual(before['query_revision'],after['query_revision'])
+ rejected=self.client.post(self.case.base+'/curation',json=body,headers=self.case.headers)
+ self.assertEqual(rejected.status_code,409)
+
+ def test_native_protocol_summary_matches_legacy_without_full_saved_tag_reads(self):
+ tracker=self.native_generation()
+ self.save_curation(ids=self.ids[:1],tags_add=['one','two'],included=False,review_state='approved')
+ self.save_curation(ids=self.ids[1:],tags_add=['three'],review_state='approved')
+ self.service._fingerprints[self.ids[1]]='c'*64 # Approval must become stale.
+ self.fixture.edit('cell',self.service.rows[self.ids[0]]['cell_uuid'],['inherited'])
+ for query in ('','?cell_uuid='+self.service.rows[self.ids[0]]['cell_uuid'],'?tag=inherited'):
+ tracker.disabled=True
+ expected=self.client.get(self.case.base+query)
+ self.assertEqual(expected.status_code,200,expected.get_json())
+ expected=expected.get_json()
+ tracker.disabled=False
+ self.case.curation.read_log.clear()
+ with patch.object(self.reader,'full_state',side_effect=AssertionError('No full state for native summary')), \
+ patch.object(self.service,'filtered_rows',side_effect=AssertionError('No decorated full curation rows')):
+ response=self.client.get(self.case.base+query)
+ self.assertEqual(response.status_code,200,response.get_json())
+ actual=response.get_json()
+ self.assertTrue(actual['query_revision'].startswith('protocol-state-v3:'))
+ for body in (actual,expected):
+ body.pop('query_revision',None);body.pop('expected_query_revision',None);body.pop('query_revision_contract',None)
+ self.assertEqual(actual,expected)
+ self.assertTrue(self.case.curation.read_log)
+ self.assertTrue(all(row['projected'] is not None and 'tags' not in row['projected']
+ for row in self.case.curation.read_log),self.case.curation.read_log)
+
+ def test_native_overview_matches_legacy_without_protocol_tag_hydration(self):
+ tracker=self.native_generation()
+ self.save_curation(ids=self.ids[:1],tags_add=['one','two'],included=False,review_state='approved')
+ self.save_curation(ids=self.ids[1:],tags_add=['three'],review_state='approved')
+ self.service._fingerprints[self.ids[1]]='c'*64
+ self.fixture.edit('epoch',self.ids[0],['project-tag'])
+ tracker.disabled=True
+ with patch.object(self.service,'events',return_value=[]):
+ expected=self.client.get('/api/overview')
+ self.assertEqual(expected.status_code,200,expected.get_json());expected=expected.get_json()
+ tracker.disabled=False
+ self.case.curation.read_log.clear()
+ with patch.object(self.reader,'full_state',side_effect=AssertionError('No full state for native overview')), \
+ patch.object(self.service,'overview',side_effect=AssertionError('No decorated legacy overview')), \
+ patch.object(self.service,'events',return_value=[]):
+ response=self.client.get('/api/overview')
+ self.assertEqual(response.status_code,200,response.get_json());actual=response.get_json()
+ for body in (actual,expected):
+ for protocol in body['protocols']:protocol.pop('query_revision',None)
+ self.assertEqual(actual,expected)
+ self.assertTrue(all(row['projected'] is not None and 'tags' not in row['projected']
+ for row in self.case.curation.read_log),self.case.curation.read_log)
+
+ def test_custom_shared_snapshot_policy_does_not_enter_native_revision_contract(self):
+ self.native_generation()
+ original=self.reader.shared.snapshot
+ def custom():return {**original(),'revision':'custom-policy'}
+ with patch.object(self.reader.shared,'snapshot',side_effect=custom):
+ self.assertIsNone(self.reader.native_context(self.protocol,self.ids))
+ result=self.reader.read_selected(self.protocol,self.ids)
+ self.assertFalse(result[1].startswith('protocol-state-v3:'))
+
+ def test_native_v3_binding_guard_preserves_revision_inside_transaction(self):
+ tracker=self.native_generation()
+ connection=self.store.dj.conn()
+ connection_type=type(connection)
+ original_transaction=connection_type.transaction
+ @contextlib.contextmanager
+ def transaction(owner):
+ with original_transaction.__get__(owner,connection_type):
+ tracker.in_transaction=True
+ try:yield
+ finally:tracker.in_transaction=False
+ with patch.object(connection_type,'transaction',property(transaction)), patch.object(self.service,'refresh',return_value={}):
+ saved=self.client.post('/api/explore/revisions',json={'predicate':{'all':[]},'splits':'cell'},headers=self.case.headers)
+ self.assertEqual(saved.status_code,201,saved.get_json())
+ identity=saved.get_json()['revision_uuid']
+ comparison=self.client.post('/api/explore/revisions/'+identity+'/compare-to-protocol',
+ json={'protocol_uuid':self.protocol},headers=self.case.headers)
+ self.assertEqual(comparison.status_code,200,comparison.get_json())
+ current=comparison.get_json()
+ self.assertTrue(current['expected_query_revision'].startswith('protocol-state-v3:'))
+ applied=self.client.post('/api/explore/revisions/'+identity+'/apply-to-protocol',json={
+ 'protocol_uuid':self.protocol,'expected_binding_version':current['expected_binding_version'],
+ 'expected_query_revision':current['expected_query_revision']},headers=self.case.headers)
+ self.assertEqual(applied.status_code,200,applied.get_json())
+ self.assertGreater(tracker.locked_checks,0)
+
+ def test_native_v3_fences_revision_and_visible_provenance(self):
+ self.native_generation()
+ original=self.fixture.shared.for_epochs if hasattr(self.fixture,'shared') else self.reader.shared.for_epochs
+ def racing(rows):
+ result=original(rows)
+ self.case.curation.rows.append({'project_uuid':self.service.project['project_uuid'],
+ 'protocol_uuid':self.protocol,'epoch_uuid':self.ids[1], 'included':True,
+ 'tags':['race'],'review_state':'unreviewed','revision':1,
+ 'metadata_fingerprint':self.service._fingerprints[self.ids[1]]})
+ return result
+ with patch.object(self.reader.shared,'for_epochs',side_effect=racing):
+ response=self.client.get(self.case.base+'/epochs?limit=1')
+ self.assertEqual(response.status_code,409,response.get_json())
+
+ def page(self):
+ response=self.client.get(self.case.base+'/epochs?limit=1')
+ self.assertEqual(response.status_code,200,response.get_json())
+ return response.get_json()
+
+ def assert_oracle(self, actual, ids=None):
+ result,states,revision=self.reader.full_state(self.protocol)
+ expected=ids or self.ids
+ self.assertEqual(actual[0],{key:states[key] for key in expected})
+ self.assertEqual(actual[1],revision)
+ self.assertEqual(actual[2],result.get('dataset_binding',{}).get('version',0))
+
+ def save_curation(self, ids=None, **changes):
+ ids=ids or self.ids[:1]
+ return self.store.update(self.protocol,ids,changes,{key:0 for key in ids},
+ {key:self.service._fingerprints[key] for key in ids},'fixture')
+
+ def test_warm_http_page_reuses_exact_revision_and_hydrates_only_selected_state(self):
+ first=self.page()
+ self.assertIn(self.protocol,self.reader.cache)
+ calls=[]
+ code=self.store._state.__code__
+ previous=sys.getprofile()
+ def observe(frame,event,arg):
+ if event=='call' and frame.f_code is code:calls.append(None)
+ with patch.object(self.reader,'full_state',side_effect=AssertionError('No full state on a hit')):
+ sys.setprofile(observe)
+ try:second=self.page()
+ finally:sys.setprofile(previous)
+ self.assertEqual(second,first)
+ self.assertEqual(len(calls),1)
+ self.assertEqual(first['query_revision'],self.reader.full_state(self.protocol)[2])
+
+ def test_sparse_same_revision_sql_edits_and_deletions_invalidate_by_exact_values(self):
+ self.save_curation(tags_add=['before'])
+ before=self.reader.read_selected(self.protocol,self.ids)
+ row=self.case.curation.rows[0]
+ row['tags']=['changed outside this process'] # Deliberately no counter bump.
+ row['included']=False
+ after=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(after[1],before[1])
+ self.assert_oracle(after)
+ self.assertEqual(after[0][self.ids[0]]['tags'],row['tags'])
+ self.case.curation.rows.clear()
+ removed=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(removed[1],after[1])
+ self.assert_oracle(removed)
+
+ def test_shared_tag_change_invalidates_even_when_target_is_outside_page(self):
+ before=self.reader.read_selected(self.protocol,self.ids[:1])
+ self.fixture.edit('epoch',self.ids[1],['other page'])
+ after=self.reader.read_selected(self.protocol,self.ids[:1])
+ self.assertNotEqual(after[1],before[1])
+ self.assert_oracle(after,self.ids[:1])
+ # Full shared records, not only numeric revisions, determine freshness.
+ self.fixture.records.rows[0]['tags']=['same revision, new tag']
+ changed=self.reader.read_selected(self.protocol,self.ids[:1])
+ self.assertNotEqual(changed[1],after[1])
+ self.assert_oracle(changed,self.ids[:1])
+
+ def test_metadata_membership_sources_and_numeric_representation_invalidate(self):
+ before=self.reader.read_selected(self.protocol,self.ids)
+ self.service._fingerprints[self.ids[0]]='c'*64
+ changed=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(before[1],changed[1]);self.assert_oracle(changed)
+ self.service.set_source_state_provider(lambda:{'a'*64:{'query_excluded':True}})
+ excluded=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(changed[1],excluded[1]);self.assert_oracle(excluded)
+ # Query values preserve JSON numeric type and signed-zero semantics.
+ query=self.service.protocols[self.protocol]['result']
+ revisions=[]
+ for value in (1,1.0,True,0.0,-0.0):
+ query['numeric_guard']=value
+ actual=self.reader.read_selected(self.protocol,self.ids)
+ self.assert_oracle(actual);revisions.append(actual[1])
+ self.assertEqual(len(set(revisions)),len(revisions))
+ query['epochs'].pop()
+ with self.assertRaisesRegex(ValueError,'outside'):
+ self.reader.read_selected(self.protocol,self.ids)
+ self.assert_oracle(self.reader.read_selected(self.protocol,self.ids[:1]),self.ids[:1])
+
+ def test_cache_never_hides_seal_corruption_or_failed_refresh(self):
+ self.reader.read_selected(self.protocol,self.ids)
+ self.service._loaded=False
+ with self.assertRaises(RuntimeError):self.reader.read_selected(self.protocol,self.ids)
+ self.service._loaded=True
+ with self.index.path.open('ab') as stream:stream.write(b'changed')
+ with self.assertRaises(ValueError):self.reader.read_selected(self.protocol,self.ids)
+
+ def test_cached_revision_cannot_allow_detail_from_outside_current_query(self):
+ self.reader.read_selected(self.protocol,self.ids)
+ self.service.protocols[self.protocol]['result']['epochs'].pop()
+ response=self.client.get('/api/epochs/'+self.ids[1]+'?protocol_uuid='+self.protocol)
+ self.assertEqual(response.status_code,400,response.get_json())
+ self.assertIn('outside',response.get_json()['error'])
+
+ def test_warm_cache_does_not_bypass_stale_mutation_or_batch_revision_guards(self):
+ page=self.page()
+ self.fixture.edit('epoch',self.ids[1],['changed'])
+ response=self.client.post(self.case.base+'/curation',json={
+ 'epoch_uuids':self.ids[:1],'query_revision':page['query_revision'],
+ 'changes':{'included':False},'expected_revisions':{self.ids[0]:0}},headers=self.case.headers)
+ self.assertEqual(response.status_code,409,response.get_json())
+ self.assertFalse(self.case.curation.rows)
+ response=self.client.post(self.case.base+'/curation/read',json={
+ 'epoch_uuids':self.ids[:1],'query_revision':page['query_revision'],
+ 'expected_binding_version':0,'selection_scope':{'filters':{},'cell_uuid':None}},headers=self.case.headers)
+ self.assertEqual(response.status_code,409,response.get_json())
+
+ def test_racing_writer_prevents_admission_of_a_mixed_proof(self):
+ self.save_curation(tags_add=['before'])
+ oracle=self.reader.full_state
+ def racing(protocol):
+ result=oracle(protocol)
+ self.case.curation.rows[0]['tags']=['after']
+ return result
+ with patch.object(self.reader,'full_state',side_effect=racing):
+ older=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotIn(self.protocol,self.reader.cache)
+ self.assertEqual(older[0][self.ids[0]]['tags'],['before'])
+ newer=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(newer[1],older[1]);self.assert_oracle(newer)
+
+ def test_byte_pressure_evicts_scopes_and_large_saved_values_are_not_retained(self):
+ expected=self.reader.read_selected(self.protocol,self.ids)
+ size=self.reader.cache[self.protocol]['size']
+ other=str(uuid.uuid4())
+ self.service.protocols[other]=copy.deepcopy(self.service.protocols[self.protocol])
+ self.service.protocols[other]['result']['protocol_uuid']=other
+ self.service.protocols[other]['definition']['protocol_uuid']=other
+ self.reader.cache.clear()
+ with patch('workspace_protocol_state.STATE_CACHE_BYTES',size+4096):
+ self.reader.read_selected(self.protocol,self.ids)
+ self.reader.read_selected(other,self.ids)
+ self.assertEqual(list(self.reader.cache),[other])
+ self.assertEqual(self.reader.read_selected(self.protocol,self.ids),expected)
+ self.assertEqual(list(self.reader.cache),[self.protocol])
+ self.save_curation(tags_add=['before'])
+ self.case.curation.rows[0]['tags']=['x'*100000]
+ self.reader.cache.clear()
+ with patch('workspace_protocol_state.STATE_CACHE_BYTES',size+4096), \
+ patch.object(self.reader,'full_state',wraps=self.reader.full_state) as oracle:
+ first=self.reader.read_selected(self.protocol,self.ids)
+ second=self.reader.read_selected(self.protocol,self.ids)
+ self.assertEqual(first,second)
+ self.assertEqual(oracle.call_count,2)
+ self.assertFalse(self.reader.cache)
+
+ def test_binding_change_and_mutable_bound_cell_metadata_invalidate(self):
+ before=self.reader.read_selected(self.protocol,self.ids)
+ preview=self.service.explore_preview({'all':[]},'cell',include_tree=False,include_catalog_summary=False)
+ history=self.case.explorer_history
+ created=history.create(preview,self.service.sources,'catalog.json','fixture')
+ history.bind(created['revision_uuid'],self.protocol,0,'fixture',{},2)
+ bound=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(bound[1],before[1]);self.assert_oracle(bound)
+ self.service.cells[self.service.cell_ids[0]]['label']='Changed label'
+ changed=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(bound[1],changed[1]);self.assert_oracle(changed)
+ self.service.rows[self.ids[1]]['cell_uuid']=self.service.cell_ids[0]
+ ownership=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(ownership[1],changed[1]);self.assert_oracle(ownership)
+
+ def test_public_read_only_and_overridden_store_policies_use_full_oracle(self):
+ class PublicReadOnly:
+ def read(inner,*args,**kwargs):return self.store.read(*args,**kwargs)
+ reader=ProtocolStateReader(self.service,PublicReadOnly(),self.fixture.store,self.reader.full_state)
+ self.assert_oracle(reader.read_selected(self.protocol,self.ids))
+ self.assertFalse(reader.cache)
+ native=self.store.read
+ policy={'included':True}
+ def custom(*args,**kwargs):
+ states=native(*args,**kwargs)
+ for state in states.values():state['included']=policy['included']
+ return states
+ with patch.object(self.store,'read',side_effect=custom):
+ first=self.reader.read_selected(self.protocol,self.ids)
+ policy['included']=False
+ second=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(first[1],second[1]);self.assert_oracle(second)
+ self.assertFalse(self.reader.cache)
+
+ def test_overridden_service_policy_and_unmatched_binding_headers_fall_back(self):
+ native=self.service.query_result
+ policy={'changed':False}
+ def custom(protocol):
+ result=native(protocol)
+ if policy['changed']:result['custom_policy']='new'
+ return result
+ with patch.object(self.service,'query_result',side_effect=custom):
+ first=self.reader.read_selected(self.protocol,self.ids)
+ policy['changed']=True
+ second=self.reader.read_selected(self.protocol,self.ids)
+ self.assertNotEqual(first[1],second[1]);self.assert_oracle(second)
+ self.assertFalse(self.reader.cache)
+ history=self.case.explorer_history
+ self.service.set_binding_provider(history.protocol_binding,header_provider=lambda protocol:None)
+ first=self.reader.read_selected(self.protocol,self.ids)
+ preview=self.service.explore_preview({'all':[]},'cell',include_tree=False,include_catalog_summary=False)
+ created=history.create(preview,self.service.sources,'catalog.json','fixture')
+ history.bind(created['revision_uuid'],self.protocol,0,'fixture',{},2)
+ second=self.reader.read_selected(self.protocol,self.ids)
+ self.assertEqual(second[2],1)
+ self.assertNotEqual(first[1],second[1]);self.assert_oracle(second)
+ self.assertFalse(self.reader.cache)
+
+ def test_rejected_scope_uses_one_full_read_and_generation_change_retries(self):
+ self.save_curation(tags_add=['before'])
+ self.case.curation.rows[0]['tags']=['x'*100000]
+ with patch('workspace_protocol_state.STATE_CACHE_BYTES',20000):
+ first=self.reader.read_selected(self.protocol,self.ids)
+ self.assertIn(self.protocol,self.reader.rejected)
+ self.case.curation.read_log.clear()
+ with patch.object(self.reader,'_metadata',side_effect=AssertionError('Skip futile admission')):
+ second=self.reader.read_selected(self.protocol,self.ids)
+ self.case.curation.rows[0]['included']=False
+ changed=self.reader.read_selected(self.protocol,self.ids)
+ with self.assertRaisesRegex(ValueError,'outside'):
+ self.reader.read_selected(self.protocol,[str(uuid.uuid4())])
+ self.assertEqual(first,second)
+ self.assertNotEqual(changed[1],second[1])
+ self.assertEqual(len(self.case.curation.read_log),3)
+ self.assert_oracle(changed)
+ self.service._fingerprints=dict(self.service._fingerprints)
+ with patch.object(self.reader,'_metadata',wraps=self.reader._metadata) as metadata:
+ self.reader.read_selected(self.protocol,self.ids)
+ self.assertGreater(metadata.call_count,0)
+ # A changed/custom adapter cannot retain a previous native proof.
+ with patch.object(self.store,'read',wraps=self.store.read):
+ self.assert_oracle(self.reader.read_selected(self.protocol,self.ids))
+ self.assertNotIn(self.protocol,self.reader.rejected)
+
+ def test_rejection_memo_is_bounded(self):
+ with patch('workspace_protocol_state.STATE_CACHE_BYTES',1):
+ for _ in range(12):
+ protocol=str(uuid.uuid4())
+ self.service.protocols[protocol]=copy.deepcopy(self.service.protocols[self.protocol])
+ self.service.protocols[protocol]['result']['protocol_uuid']=protocol
+ self.reader.read_selected(protocol,self.ids)
+ self.assertEqual(len(self.reader.rejected),8)
+ self.assertFalse(self.reader.cache)
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_recovery_generation.py b/python/tests/test_workspace_recovery_generation.py
new file mode 100644
index 0000000..6e91a95
--- /dev/null
+++ b/python/tests/test_workspace_recovery_generation.py
@@ -0,0 +1,182 @@
+"""Native recovery feed/snapshot proofs, using only a disposable owned server."""
+import os
+import json
+from types import SimpleNamespace
+import threading
+import time
+import unittest
+import uuid
+
+import test_workspace_state_generation as fixtures
+NativeConnection=fixtures.NativeConnection
+from workspace_recovery_generation import RecoveryTracker, CLOCK, FEED, recovery_trigger_manifest
+import workspace_native_mysql as native
+
+
+@unittest.skipUnless(os.environ.get('RIEKE_TEST_NATIVE_MYSQL')=='1','opt-in native recovery authority proof')
+class RecoveryGenerationTests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ fixtures.NativeGenerationTests.setUpClass()
+ cls.connection=fixtures.NativeGenerationTests.connection;cls.root=fixtures.NativeGenerationTests.root
+ @classmethod
+ def tearDownClass(cls):fixtures.NativeGenerationTests.tearDownClass()
+
+ def setUp(self):
+ self.project,self.protocol,self.epoch=[str(uuid.uuid4()) for _ in range(3)]
+ self.tracker=RecoveryTracker(self.connection,self.project).bootstrap()
+ self.assertTrue(self.tracker.ready,self.tracker.reason)
+
+ def insert(self, connection=None, epoch=None):
+ (connection or self.connection).query('INSERT INTO recording_workspace.curation VALUES (%s,%s,%s,%s,1)',
+ (self.project,self.protocol,epoch or self.epoch,'["one","ON","on"]'))
+
+ def capture(self, prior=None):
+ with self.tracker.capture(prior) as plan:
+ self.assertIsNotNone(plan,self.tracker.reason)
+ rows=self.connection.query('SELECT * FROM recording_workspace.curation WHERE project_uuid=%s',
+ (self.project,),as_dict=True).fetchall()
+ plan.verify()
+ return plan,rows
+
+ def test_cached_source_schema_is_reused_and_ddl_invalidates_it(self):
+ from unittest.mock import patch
+ import workspace_recovery_generation as generation
+ initial,_=self.capture()
+ with patch.object(generation,'table_specs',wraps=generation.table_specs) as specs:
+ repeated,_=self.capture(initial.watermark)
+ self.assertTrue(repeated.complete)
+ self.assertEqual(specs.call_count,0)
+ self.connection.query('ALTER TABLE recording_workspace.curation ADD COLUMN optional_note TEXT NULL')
+ try:
+ changed,_=self.capture(repeated.watermark)
+ self.assertFalse(changed.complete)
+ self.assertIn('optional_note',changed.columns['curation'])
+ self.assertGreater(specs.call_count,0)
+ finally:
+ self.connection.query('ALTER TABLE recording_workspace.curation DROP COLUMN optional_note')
+ self.tracker.bootstrap()
+
+ def test_exact_keys_key_moves_rollback_and_retention_floor(self):
+ initial,_=self.capture();self.assertFalse(initial.complete)
+ self.insert()
+ changed,rows=self.capture(initial.watermark)
+ self.assertTrue(changed.complete)
+ self.assertEqual(changed.keys['curation'],[(self.project,self.protocol,self.epoch)])
+ self.assertEqual(changed.primary_keys['curation'],['project_uuid','protocol_uuid','epoch_uuid'])
+ self.assertEqual(changed.json_columns['curation'],['tags'])
+ self.assertEqual(len(rows),1)
+ moved=str(uuid.uuid4())
+ self.connection.query('UPDATE recording_workspace.curation SET epoch_uuid=%s,tags=%s WHERE project_uuid=%s',
+ (moved,'["two"]',self.project))
+ moved_plan,_=self.capture(changed.watermark)
+ self.assertEqual(set(moved_plan.keys['curation']),{(self.project,self.protocol,self.epoch),(self.project,self.protocol,moved)})
+ self.connection._conn.begin()
+ self.connection.query('DELETE FROM recording_workspace.curation WHERE project_uuid=%s',(self.project,))
+ self.connection._conn.rollback()
+ unchanged,_=self.capture(moved_plan.watermark)
+ self.assertTrue(unchanged.complete);self.assertFalse(any(unchanged.keys.values()))
+ self.tracker.prune(unchanged.watermark,keep_generations=0)
+ count=self.connection.query(f'SELECT COUNT(*) FROM recording_workspace.{FEED} WHERE project_uuid=%s',(self.project,)).fetchone()[0]
+ self.assertEqual(count,0)
+ stale,_=self.capture(initial.watermark);self.assertFalse(stale.complete)
+ current,_=self.capture(unchanged.watermark);self.assertTrue(current.complete)
+ self.assertEqual(len(recovery_trigger_manifest(self.connection)),9)
+
+ def test_shared_instance_watermark_across_clients_and_anchor_loss(self):
+ import pymysql
+ second=NativeConnection(pymysql.connect(**native.connection_parameters(self.root),autocommit=True))
+ try:
+ first,_=self.capture()
+ other=RecoveryTracker(second,self.project).bootstrap()
+ self.assertTrue(other.ready,other.reason)
+ with other.capture(first.watermark) as plan:
+ self.assertIsNotNone(plan,other.reason);self.assertTrue(plan.complete)
+ self.assertEqual(plan.watermark,first.watermark)
+ self.insert(second)
+ delta,_=self.capture(first.watermark);self.assertTrue(delta.complete)
+ self.assertEqual(len(delta.keys['curation']),1)
+ # Release the anchor owned by this connection without changing data.
+ self.connection.query("SELECT RELEASE_LOCK('rieke_recovery_instance_v1')")
+ with other.capture(delta.watermark) as plan:
+ self.assertIsNotNone(plan,other.reason);self.assertFalse(plan.complete)
+ self.assertNotEqual(plan.watermark['authority'],delta.watermark['authority'])
+ finally:second._conn.close()
+ self.tracker.bootstrap()
+
+ def test_curation_vocabulary_exact_union_case_unicode_and_incremental_reads(self):
+ from workspace_curation_vocabulary import CurationVocabulary
+ from unittest.mock import patch
+ other,second=str(uuid.uuid4()),str(uuid.uuid4())
+ records=[(self.protocol,self.epoch,['ON','Straße','dup','dup']),
+ (other,self.epoch,['ON','on','é']), (other,second,['on','İ','z','\ud7ff'])]
+ for protocol,epoch,tags in records:
+ self.connection.query('INSERT INTO recording_workspace.curation VALUES (%s,%s,%s,%s,1)',
+ (self.project,protocol,epoch,json.dumps(tags)))
+ store=SimpleNamespace(project_uuid=self.project,dj=SimpleNamespace(conn=lambda:self.connection))
+ index=CurationVocabulary(store,self.tracker)
+ self.addCleanup(index.close)
+ def oracle(query):
+ saved=self.connection.query('SELECT epoch_uuid,tags FROM recording_workspace.curation WHERE project_uuid=%s',
+ (self.project,),as_dict=True).fetchall()
+ membership={}
+ for row in saved:
+ for tag in set(json.loads(row['tags'])):membership.setdefault(tag,set()).add(row['epoch_uuid'])
+ return [{'tag':tag,'count':len(ids)} for tag,ids in sorted(membership.items(),
+ key=lambda item:(-len(item[1]),item[0].casefold(),item[0])) if tag.casefold().startswith(query.casefold())]
+ for query in ('','on','STRASSE','i','\ud7ff','\ud800'):
+ self.assertEqual(index.suggestions(query,100)['tags'],oracle(query))
+ self.assertEqual(index.stats['full_builds'],1)
+ self.connection.query('UPDATE recording_workspace.curation SET tags=%s WHERE project_uuid=%s AND protocol_uuid=%s AND epoch_uuid=%s',
+ ('["on","added"]',self.project,self.protocol,self.epoch))
+ watermark=self.tracker.token();self.tracker.prune(watermark)
+ with patch.object(index,'records',wraps=index.records) as read:
+ self.assertEqual(index.suggestions('',100)['tags'],oracle(''))
+ self.assertTrue(read.called);self.assertIsNotNone(read.call_args.args[0])
+ self.assertEqual(len(read.call_args.args[0]),1)
+ self.assertEqual(index.stats['full_builds'],1);self.assertEqual(index.stats['rows_loaded'],4)
+ self.connection.query('DELETE FROM recording_workspace.curation WHERE project_uuid=%s AND protocol_uuid=%s AND epoch_uuid=%s',
+ (self.project,other,self.epoch))
+ self.assertEqual(index.suggestions('',100)['tags'],oracle(''))
+ moved=str(uuid.uuid4())
+ self.connection.query('UPDATE recording_workspace.curation SET epoch_uuid=%s WHERE project_uuid=%s AND protocol_uuid=%s',
+ (moved,self.project,self.protocol))
+ self.assertEqual(index.suggestions('',100)['tags'],oracle(''))
+ self.connection.query('UPDATE recording_workspace.curation SET tags=%s WHERE project_uuid=%s AND protocol_uuid=%s',
+ ('["after gap"]',self.project,self.protocol))
+ with patch.object(self.tracker,'_continues',return_value=False):
+ self.assertEqual(index.suggestions('',100)['tags'],oracle(''))
+ self.assertEqual(index.stats['full_builds'],2)
+
+ def test_inflight_writer_commits_before_capture_clock_and_new_data_match_watermark(self):
+ import pymysql
+ self.insert()
+ before,_=self.capture()
+ writer=NativeConnection(pymysql.connect(**native.connection_parameters(self.root),autocommit=True))
+ try:
+ writer._conn.begin()
+ writer.query('UPDATE recording_workspace.curation SET tags=%s WHERE project_uuid=%s',('["committed while capture waits"]',self.project))
+ completed=[]
+ def commit():
+ time.sleep(.1);writer._conn.commit();completed.append(True)
+ worker=threading.Thread(target=commit);worker.start()
+ # Simulate DataJoint's eager consistent-snapshot start, not PyMySQL
+ # begin(). The capture must choose READ COMMITTED for this one TX.
+ import contextlib
+ from unittest.mock import patch
+ @contextlib.contextmanager
+ def transaction(connection):
+ connection.query('START TRANSACTION WITH CONSISTENT SNAPSHOT');connection.in_transaction=True
+ try:
+ yield;connection._conn.commit()
+ except Exception:connection._conn.rollback();raise
+ finally:connection.in_transaction=False
+ with patch.object(NativeConnection,'transaction',property(transaction)):
+ after,rows=self.capture(before.watermark)
+ worker.join(5);self.assertTrue(completed)
+ self.assertGreater(after.watermark['generation'],before.watermark['generation'])
+ self.assertIn('committed while capture waits',rows[0]['tags'])
+ finally:writer._conn.rollback();writer._conn.close()
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_recovery_store.py b/python/tests/test_workspace_recovery_store.py
new file mode 100644
index 0000000..77a5cf3
--- /dev/null
+++ b/python/tests/test_workspace_recovery_store.py
@@ -0,0 +1,358 @@
+"""Independent scientific and crash oracles for the current-state mirror.
+
+Expected values come from this fixture and explicit operations, never from the
+production encoder, row-key helper, or recovery loader.
+"""
+import copy
+import errno
+import json
+import os
+from pathlib import Path
+import select
+import shutil
+import sqlite3
+import subprocess
+import sys
+import tempfile
+import unittest
+import uuid
+from unittest.mock import patch
+
+import workspace_recovery_store as recovery
+from workspace_state_snapshot import TABLES
+
+
+def identity(number):
+ return str(uuid.UUID(int=number))
+
+
+def scientific_fixture():
+ project=identity(1);protocols=[identity(10),identity(11)];epochs=[identity(20),identity(21)];cell=identity(30)
+ profiles=[identity(40+i) for i in range(3)];revision=identity(50)
+ tables={name:[] for name in TABLES}
+ keys={
+ 'annotation_profile':['project_uuid','profile_uuid'],
+ 'shared_annotation':['project_uuid','target_kind','target_uuid','profile_uuid'],
+ 'curation':['project_uuid','protocol_uuid','epoch_uuid'],
+ 'explorer_revision':['project_uuid','revision_uuid'],
+ 'protocol_binding':['project_uuid','protocol_uuid'],
+ }
+ for p,profile in enumerate(profiles):
+ author='Same display name' if p<2 else 'Third author'
+ tables['annotation_profile'].append(dict(project_uuid=project,profile_uuid=profile,display_name=author,
+ created_at='2026-09-29T10:11:12',created_by='fixture'))
+ for kind,target in [('cell',cell),*[('epoch',epoch) for epoch in epochs]]:
+ tables['shared_annotation'].append(dict(project_uuid=project,target_kind=kind,target_uuid=target,
+ profile_uuid=profile,tags=['QC','qc','é','e\u0301',f'{kind}-{p}'],author_name=author,
+ revision=1,updated_at='2026-09-29T10:11:12'))
+ for n,protocol in enumerate(protocols):
+ for epoch in epochs:
+ tables['curation'].append(dict(project_uuid=project,protocol_uuid=protocol,epoch_uuid=epoch,
+ included=1,tags=['QC',f'protocol-{n}'],review_state='unreviewed',revision=1,
+ metadata_fingerprint='b'*64))
+ members=[{'uuid':epoch,'metadata_hash':'b'*64} for epoch in epochs]
+ recipe={'revision_uuid':revision,'epochs':members,'predicate':{'field':'annotations/effective/tags','operator':'contains','value':'QC'},
+ 'source_revisions':['a'*64],'typed':{'boolean':True,'integer':1,'float':1.0,'none':None,'empty':[]},
+ 'provenance':{'author':'Same display name'}}
+ tables['explorer_revision']=[dict(project_uuid=project,revision_uuid=revision,parent_revision_uuid=None,
+ recipe=recipe,summary={'epoch_count':2})]
+ tables['protocol_binding']=[dict(project_uuid=project,protocol_uuid=protocols[0],revision_uuid=revision,version=1)]
+ state={'format':'rieke-app-state','version':1,'project':{'project_uuid':project,'name':'Independent fixture'},
+ 'source_sha256s':['a'*64],'source_references':[{'source_sha256':'a'*64,'metadata_sha256':'b'*64,'source_path':'/recordings/source.h5'}],
+ 'protocols':{protocols[0]+'.json':{'protocol_uuid':protocols[0],'initial_revision_uuid':revision}},'tables':tables}
+ return state,keys
+
+
+def canonical(state):
+ result=copy.deepcopy(state)
+ for rows in result['tables'].values():
+ rows.sort(key=lambda row:json.dumps(row,ensure_ascii=False,sort_keys=True,separators=(',',':')))
+ return json.dumps(result,ensure_ascii=False,sort_keys=True,separators=(',',':'),allow_nan=False)
+
+
+class RecoveryStoreTests(unittest.TestCase):
+ def setUp(self):
+ self.temporary=tempfile.TemporaryDirectory();self.addCleanup(self.temporary.cleanup)
+ self.root=Path(self.temporary.name).resolve();self.state,self.keys=scientific_fixture()
+ self.watermark={'authority':'fixture-authority','epoch':identity(99),'generation':0}
+
+ def write_baseline(self,*,day='2026-09-29'):
+ return recovery.write(self.root,state=copy.deepcopy(self.state),keys=self.keys,watermark=self.watermark,day=day)
+
+ def inspect(self):return recovery.inspect(self.root)
+
+ def load(self):
+ item=self.inspect();return recovery.load_database(item['path'],expected=item['pointer'])
+
+ def delta(self,changes=None,deleted=None,*,day='2026-09-29',generation=1):
+ header=copy.deepcopy(self.state);header['tables']={table:[] for table in TABLES}
+ return recovery.write(self.root,state=header,keys=self.keys,watermark={**self.watermark,'generation':generation},
+ changes=changes or {},deleted=deleted,day=day)
+
+ def assert_scientific_state(self,wanted):
+ self.assertEqual(canonical(self.load()),canonical(wanted))
+
+ def test_content_seals_track_only_changed_tables_and_match_complete_rebuild(self):
+ self.write_baseline()
+ original=copy.deepcopy(self.inspect()['header']['table_seals'])
+ row={**self.state['tables']['shared_annotation'][0],'tags':['new exact é'],'revision':2}
+ self.delta({'shared_annotation':[row]})
+ changed=self.inspect()['header']['table_seals']
+ self.assertNotEqual(changed['shared_annotation'],original['shared_annotation'])
+ for table in TABLES:
+ if table!='shared_annotation':self.assertEqual(changed[table],original[table])
+ wanted=self.load()
+ recovery.write(self.root,state=wanted,keys=self.keys,watermark=self.watermark,day='2026-09-29')
+ self.assertEqual(self.inspect()['header']['table_seals'],changed)
+ self.delta(deleted={'shared_annotation':[[row[name] for name in self.keys['shared_annotation']]]})
+ self.assertEqual(self.inspect()['header']['table_seals']['shared_annotation']['count'],8)
+ self.load()
+
+ def test_verified_content_requires_fresh_unchanged_matching_authority(self):
+ from unittest.mock import Mock
+ self.watermark['project_uuid']=self.state['project']['project_uuid']
+ self.write_baseline()
+ tracker=Mock();tracker.token.return_value=self.watermark
+ proof=recovery.verified_table_content(self.root,tracker)
+ self.assertEqual(proof['project_uuid'],self.state['project']['project_uuid'])
+ self.assertEqual(set(proof['tables']),{'shared_annotation','annotation_profile'})
+ tracker.token.side_effect=[self.watermark,{**self.watermark,'generation':1}]
+ self.assertIsNone(recovery.verified_table_content(self.root,tracker))
+ tracker.token.side_effect=None;tracker.token.return_value=None
+ self.assertIsNone(recovery.verified_table_content(self.root,tracker))
+
+ def test_legacy_header_restores_but_requires_full_capture_before_content_proof(self):
+ from unittest.mock import Mock
+ import hashlib
+ self.watermark['project_uuid']=self.state['project']['project_uuid']
+ self.write_baseline();current=self.inspect();header=current['header'];header.pop('table_seals')
+ blob=recovery._pack(header)
+ with sqlite3.connect(current['path']) as db:
+ db.execute('UPDATE recovery_header SET payload=?,sha256=?',(blob,hashlib.sha256(blob).digest()))
+ self.assert_scientific_state(self.state)
+ tracker=Mock();tracker.token.return_value=self.watermark
+ self.assertIsNone(recovery.verified_table_content(self.root,tracker))
+ with self.assertRaisesRegex(ValueError,'content-sealed baseline'):self.delta()
+ self.write_baseline()
+ self.assertIsNotNone(recovery.verified_table_content(self.root,tracker))
+
+ def test_baseline_round_trip_preserves_exact_types_tags_profiles_and_frozen_members(self):
+ result=self.write_baseline();self.assert_scientific_state(self.state)
+ self.assertFalse(result['incremental']);self.assertEqual(self.inspect()['header']['watermark'],self.watermark)
+ typed=self.load()['tables']['explorer_revision'][0]['recipe']['typed']
+ self.assertIs(type(typed['boolean']),bool);self.assertIs(type(typed['integer']),int);self.assertIs(type(typed['float']),float)
+ self.assertEqual({row['author_name'] for row in self.load()['tables']['shared_annotation']},{'Same display name','Third author'})
+
+ def test_after_images_empty_tags_and_key_moves_preserve_unaffected_protocols_and_pin(self):
+ self.write_baseline();wanted=copy.deepcopy(self.state)
+ old=wanted['tables']['shared_annotation'][0];old_key=[old[name] for name in self.keys['shared_annotation']]
+ moved={**old,'target_uuid':identity(31),'profile_uuid':identity(43),'tags':[],'revision':2}
+ wanted['tables']['shared_annotation'][0]=moved
+ decision={**wanted['tables']['curation'][0],'tags':['qc','new'],'revision':2};wanted['tables']['curation'][0]=decision
+ self.delta({'shared_annotation':[moved],'curation':[decision]}, {'shared_annotation':[old_key]})
+ self.assert_scientific_state(wanted)
+ self.assertEqual(self.inspect()['header']['watermark']['generation'],1)
+ self.assertEqual(self.load()['tables']['explorer_revision'],self.state['tables']['explorer_revision'])
+
+ def test_deleting_all_rows_then_reinserting_does_not_resurrect_old_rows(self):
+ self.write_baseline();wanted=copy.deepcopy(self.state)
+ deleted={table:[[row[name] for name in self.keys[table]] for row in rows]
+ for table,rows in wanted['tables'].items() if rows}
+ self.delta(deleted=deleted);wanted['tables']={table:[] for table in TABLES};self.assert_scientific_state(wanted)
+ row={**self.state['tables']['shared_annotation'][1],'tags':['returned'],'revision':7}
+ self.delta({'shared_annotation':[row]},generation=2);wanted['tables']['shared_annotation']=[row];self.assert_scientific_state(wanted)
+
+ def test_repeated_after_image_is_idempotent_and_zero_change_advances_only_watermark(self):
+ self.write_baseline();row={**self.state['tables']['shared_annotation'][0],'tags':['new'],'revision':2}
+ wanted=copy.deepcopy(self.state);wanted['tables']['shared_annotation'][0]=row
+ self.delta({'shared_annotation':[row]});self.delta({'shared_annotation':[row]},generation=2);self.delta(generation=3)
+ self.assert_scientific_state(wanted);self.assertEqual(self.inspect()['header']['watermark']['generation'],3)
+
+ def test_duplicate_primary_key_in_complete_capture_preserves_previous_mirror(self):
+ self.write_baseline();invalid=copy.deepcopy(self.state)
+ invalid['tables']['shared_annotation'][0]['tags']=['first duplicate']
+ invalid['tables']['shared_annotation'].append({**invalid['tables']['shared_annotation'][0],
+ 'tags':['conflicting duplicate']})
+ with self.assertRaises(sqlite3.IntegrityError):
+ recovery.write(self.root,state=invalid,keys=self.keys,
+ watermark={**self.watermark,'generation':1},day='2026-09-29')
+ self.assert_scientific_state(self.state)
+ self.assertEqual(self.inspect()['header']['watermark'],self.watermark)
+
+ def test_duplicate_primary_key_in_delta_rolls_back_deletions_and_after_images(self):
+ self.write_baseline();first={**self.state['tables']['shared_annotation'][0],'tags':['first duplicate']}
+ second={**first,'tags':['conflicting duplicate']}
+ deleted=self.state['tables']['curation'][0]
+ deleted_key=[deleted[name] for name in self.keys['curation']]
+ with self.assertRaisesRegex(ValueError,'Duplicate primary key'):
+ self.delta({'shared_annotation':[first,second]}, {'curation':[deleted_key]})
+ self.assert_scientific_state(self.state)
+ self.assertEqual(self.inspect()['header']['watermark'],self.watermark)
+
+ def test_incremental_requires_baseline_and_exact_project_key_contract(self):
+ with self.assertRaisesRegex(ValueError,'baseline'):self.delta()
+ self.write_baseline();before=canonical(self.load())
+ foreign={**self.state['tables']['shared_annotation'][0],'project_uuid':identity(999)}
+ with self.assertRaisesRegex(ValueError,'foreign'):self.delta({'shared_annotation':[foreign]})
+ key=[foreign[name] for name in self.keys['shared_annotation']]
+ with self.assertRaisesRegex(ValueError,'different project'):self.delta(deleted={'shared_annotation':[key]})
+ self.assertEqual(canonical(self.load()),before);self.assertEqual(self.inspect()['header']['watermark'],self.watermark)
+
+ def test_checkpoint_is_immutable_and_retention_preserves_unmanaged_backups(self):
+ result=self.write_baseline();daily=Path(result['daily_backup']);original=daily.read_bytes()
+ row={**self.state['tables']['shared_annotation'][0],'tags':['later'],'revision':2}
+ self.delta({'shared_annotation':[row]});self.assertEqual(daily.read_bytes(),original)
+ folder=daily.parent;legacy=folder/'2025-01-01.sqlite';legacy.write_bytes(b'legacy backup')
+ explicit=folder/'before_restore.sqlite';explicit.write_bytes(b'user restore backup')
+ self.delta(day='2026-09-30',generation=2);self.delta(day='2026-10-01',generation=3)
+ managed=sorted(folder.glob('checkpoint-v2-*.sqlite'))
+ self.assertLessEqual(len(managed),recovery.CHECKPOINT_COUNT);self.assertEqual(managed[-1].name,'checkpoint-v2-2026-10-01.sqlite')
+ self.assertEqual(legacy.read_bytes(),b'legacy backup');self.assertEqual(explicit.read_bytes(),b'user restore backup')
+ with patch.object(recovery,'CHECKPOINT_BYTES',1):
+ self.delta(day='2026-10-02',generation=4)
+ self.assertTrue(recovery.storage(self.root)['checkpoint_budget_exceeded'])
+ self.assertEqual(len(list(folder.glob('checkpoint-v2-*.sqlite'))),1)
+
+ def test_pointer_scope_and_path_validation_precedes_loading(self):
+ self.write_baseline();item=self.inspect();pointer=item['pointer'];path=self.root/'app-state.json'
+ for changes in ({'project_uuid':identity(999)},{'store':'../../outside.sqlite'},{'version':999}):
+ with self.subTest(changes=changes):
+ path.write_text(json.dumps({**pointer,**changes}))
+ with self.assertRaises(ValueError):recovery.inspect(self.root)
+ path.write_text(json.dumps(pointer));self.assert_scientific_state(self.state)
+
+ def test_pointer_store_uuid_must_match_database_identity(self):
+ self.write_baseline();item=self.inspect();new_id=identity(900)
+ copied=item['path'].with_name('current-'+uuid.UUID(new_id).hex+'.sqlite');shutil.copy2(item['path'],copied)
+ pointer={**item['pointer'],'store_uuid':new_id,'store':'backups/app-state/'+copied.name}
+ (self.root/'app-state.json').write_text(json.dumps(pointer))
+ with self.assertRaisesRegex(ValueError,'identity'):recovery.inspect(self.root)
+
+ def test_symlinked_directories_and_current_files_are_rejected(self):
+ external=self.root/'external';external.mkdir();(self.root/'backups').symlink_to(external,target_is_directory=True)
+ with self.assertRaises(ValueError):self.write_baseline()
+ self.assertEqual(list(external.iterdir()),[]);(self.root/'backups').unlink();self.write_baseline()
+ item=self.inspect();moved=external/'copied.sqlite';shutil.move(item['path'],moved);item['path'].symlink_to(moved)
+ before=moved.read_bytes()
+ with self.assertRaises(ValueError):recovery.inspect(self.root)
+ self.assertEqual(moved.read_bytes(),before)
+
+ def test_complete_row_corruption_is_detected_before_restore_input(self):
+ self.write_baseline();item=self.inspect()
+ with sqlite3.connect(item['path']) as db:
+ key=db.execute('SELECT table_name,row_key FROM recovery_rows LIMIT 1').fetchone()
+ db.execute('UPDATE recovery_rows SET payload=? WHERE table_name=? AND row_key=?',(b'not the original content',*key))
+ with self.assertRaisesRegex(ValueError,'checksum'):recovery.load_database(item['path'])
+
+ def test_missing_row_is_detected_even_when_sqlite_structure_is_valid(self):
+ self.write_baseline();item=self.inspect()
+ with sqlite3.connect(item['path']) as db:
+ key=db.execute('SELECT table_name,row_key FROM recovery_rows LIMIT 1').fetchone()
+ db.execute('DELETE FROM recovery_rows WHERE table_name=? AND row_key=?',key)
+ self.assertEqual(db.execute('PRAGMA quick_check').fetchall(),[('ok',)])
+ with self.assertRaises(ValueError):recovery.load_database(item['path'])
+
+ def test_failure_after_row_updates_rolls_back_rows_and_watermark(self):
+ self.write_baseline();row={**self.state['tables']['shared_annotation'][0],'tags':['must roll back'],'revision':2}
+ original=recovery._write_rows
+ def failed(*args,**kwargs):
+ original(*args,**kwargs);raise OSError(errno.ENOSPC,'injected disk full after rows')
+ with patch.object(recovery,'_write_rows',side_effect=failed):
+ with self.assertRaises(OSError):self.delta({'shared_annotation':[row]})
+ self.assert_scientific_state(self.state);self.assertEqual(self.inspect()['header']['watermark'],self.watermark)
+
+ def test_fsync_failure_after_sqlite_commit_is_reported_without_repeating_mutation(self):
+ self.write_baseline();row={**self.state['tables']['shared_annotation'][0],'tags':['committed before error'],'revision':2}
+ wanted=copy.deepcopy(self.state);wanted['tables']['shared_annotation'][0]=row
+ with patch.object(recovery.os,'fsync',side_effect=OSError(errno.EIO,'injected synchronization failure')):
+ with self.assertRaises(OSError):self.delta({'shared_annotation':[row]})
+ self.assert_scientific_state(wanted);self.assertEqual(self.inspect()['header']['watermark']['generation'],1)
+ self.delta({'shared_annotation':[row]});self.assert_scientific_state(wanted)
+
+ def test_failed_pointer_publication_keeps_older_v1_snapshot_readable(self):
+ legacy=json.dumps(self.state).encode();(self.root/'app-state.json').write_bytes(legacy)
+ with patch('workspace_state_snapshot.atomic_write',side_effect=OSError(errno.EIO,'pointer publication failed')):
+ with self.assertRaises(OSError):self.write_baseline()
+ self.assertEqual((self.root/'app-state.json').read_bytes(),legacy);self.assertIsNone(recovery.inspect(self.root))
+ self.write_baseline();self.assert_scientific_state(self.state)
+
+ def test_failed_baseline_publications_do_not_accumulate_full_current_images(self):
+ legacy=json.dumps(self.state).encode();(self.root/'app-state.json').write_bytes(legacy)
+ for _ in range(3):
+ with patch('workspace_state_snapshot.atomic_write',side_effect=OSError(errno.EIO,'pointer publication failed')):
+ with self.assertRaises(OSError):self.write_baseline()
+ self.write_baseline();item=self.inspect();self.assert_scientific_state(self.state)
+ self.assertEqual(list(item['path'].parent.glob('current-*.sqlite')),[item['path']])
+
+ def test_error_after_pointer_publication_keeps_the_referenced_database(self):
+ from workspace_state_snapshot import atomic_write
+ def published_then_error(*args,**kwargs):
+ atomic_write(*args,**kwargs);raise OSError(errno.EIO,'error after pointer publication')
+ with patch('workspace_state_snapshot.atomic_write',side_effect=published_then_error):
+ with self.assertRaises(OSError):self.write_baseline()
+ self.assert_scientific_state(self.state)
+
+ def test_daily_checkpoint_failure_preserves_the_committed_current_mirror(self):
+ self.write_baseline();row={**self.state['tables']['shared_annotation'][0],'tags':['current survives'],'revision':2}
+ wanted=copy.deepcopy(self.state);wanted['tables']['shared_annotation'][0]=row
+ with patch.object(recovery,'checkpoint',side_effect=OSError(errno.ENOSPC,'daily checkpoint full')):
+ with self.assertRaises(OSError):self.delta({'shared_annotation':[row]},day='2026-09-30')
+ self.assert_scientific_state(wanted);self.assertEqual(self.inspect()['header']['watermark']['generation'],1)
+
+ def _kill_at(self,phase):
+ row={**self.state['tables']['shared_annotation'][0],'tags':['crash boundary'],'revision':2}
+ header=copy.deepcopy(self.state);header['tables']={table:[] for table in TABLES}
+ request={'root':str(self.root),'state':header,'keys':self.keys,'watermark':{**self.watermark,'generation':1},
+ 'changes':{'shared_annotation':[row]},'day':'2026-09-29'}
+ if phase=='rows':
+ request['changes']['shared_annotation']=[{**original,'tags':['crash boundary',*[identity(100000+i*20+j) for j in range(20)]],'revision':2}
+ for i,original in enumerate(self.state['tables']['shared_annotation'])]
+ request_path=self.root/'child-request.json';request_path.write_text(json.dumps(request))
+ script='''import json,os,sys,time
+from contextlib import contextmanager
+import workspace_recovery_store as recovery
+request=json.load(open(sys.argv[1]));phase=sys.argv[2]
+def stop():
+ os.write(1,b'READY\\n');time.sleep(30)
+if phase=='rows':
+ connect=recovery._connect
+ @contextmanager
+ def tiny_cache(*args,**kwargs):
+ with connect(*args,**kwargs) as database:
+ if not kwargs.get('readonly'):
+ database.execute('PRAGMA cache_size=1');database.execute('PRAGMA cache_spill=ON')
+ yield database
+ recovery._connect=tiny_cache
+ original=recovery._write_rows
+ def write_rows(*args,**kwargs):
+ original(*args,**kwargs);stop()
+ recovery._write_rows=write_rows
+else:
+ recovery.os.fsync=lambda descriptor:stop()
+recovery.write(**request)
+'''
+ process=subprocess.Popen([sys.executable,'-c',script,str(request_path),phase],stdout=subprocess.PIPE,stderr=subprocess.PIPE,
+ env={**os.environ,'PYTHONPATH':str(Path(recovery.__file__).parent)})
+ try:
+ ready,_,_=select.select([process.stdout],[],[],10)
+ self.assertTrue(ready,'Recovery subprocess did not reach its crash cut')
+ line=process.stdout.readline()
+ if line!=b'READY\n':self.fail('Recovery subprocess failed: '+process.stderr.read().decode())
+ process.kill();process.wait(timeout=10)
+ finally:
+ if process.poll() is None:process.kill();process.wait(timeout=10)
+ process.stdout.close();process.stderr.close()
+ return row
+
+ def test_actual_process_kill_before_commit_recovers_complete_old_state(self):
+ original=self.state['tables']['shared_annotation'][0]
+ self.state['tables']['shared_annotation']=[{**original,'target_kind':'epoch','target_uuid':identity(1000+i)} for i in range(500)]
+ self.write_baseline();self._kill_at('rows');self.assert_scientific_state(self.state)
+ self.assertEqual(self.inspect()['header']['watermark'],self.watermark)
+
+ def test_actual_process_kill_after_commit_recovers_complete_new_state(self):
+ self.write_baseline();row=self._kill_at('fsync');wanted=copy.deepcopy(self.state);wanted['tables']['shared_annotation'][0]=row
+ self.assert_scientific_state(wanted);self.assertEqual(self.inspect()['header']['watermark']['generation'],1)
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_refresh_cache.py b/python/tests/test_workspace_refresh_cache.py
index 3c05a41..d315f6b 100644
--- a/python/tests/test_workspace_refresh_cache.py
+++ b/python/tests/test_workspace_refresh_cache.py
@@ -1,5 +1,6 @@
"""Incremental source metadata cache with actual tiny H5 files and fake SQL."""
import copy
+import gc
import hashlib
import json
import os
@@ -48,11 +49,25 @@ def setUp(self):
def add_source(self, name):
identity, cell_id, group_id, block_id, epoch_id, stream_id = [str(uuid.uuid4()) for _ in range(6)]
source_path = self.folder / (name + '.h5')
- stream_path = f'/block-{block_id}/epochs/epoch-{epoch_id}/responses/stream-{stream_id}'
+ animal_id, prep_id = str(uuid.uuid4()), str(uuid.uuid4())
+ block_path = f'/experiment-{identity}/epochGroups/group-{group_id}/epochBlocks/block-{block_id}'
+ stream_path = f'{block_path}/epochs/epoch-{epoch_id}/responses/Amp1-{stream_id}'
with h5py.File(source_path, 'w') as h5:
- block = h5.create_group('/block-' + block_id)
- block.attrs['uuid'] = block_id
- ep = h5.create_group(f'/block-{block_id}/epochs/epoch-{epoch_id}')
+ experiment = h5.create_group('/experiment-' + identity)
+ experiment.attrs['uuid'] = identity
+ animal = experiment.create_group('sources/animal-' + animal_id)
+ animal.attrs['uuid'] = animal_id
+ animal['experiment'] = experiment
+ prep = animal.create_group('sources/preparation-' + prep_id)
+ prep.attrs['uuid'] = prep_id
+ cell = prep.create_group('sources/cell-' + cell_id)
+ cell.attrs['uuid'] = cell_id
+ group = experiment.create_group('epochGroups/group-' + group_id)
+ group.attrs['uuid'] = group_id
+ group['source'] = cell
+ block = h5.create_group(block_path)
+ block.attrs.update(uuid=block_id, protocolID='example')
+ ep = h5.create_group(f'{block_path}/epochs/epoch-{epoch_id}')
ep.attrs['uuid'] = epoch_id
stream = h5.create_group(stream_path)
stream.attrs['uuid'], stream.attrs['sampleRate'] = stream_id, 10000.
@@ -65,7 +80,7 @@ def add_source(self, name):
block = {'uuid': block_id, 'protocolID': 'example', 'epochs': [epoch], 'parameters': {}}
group = {'uuid': group_id, 'label': 'Control', 'epoch_blocks': [block]}
cell = {'uuid': cell_id, 'label': name, 'type': 'fixture', 'start_time': '09/24/2026 12:00:00:000000', 'epoch_groups': [group]}
- document = {'uuid': identity, 'animals': [{'preparations': [{'cells': [cell]}]}]}
+ document = {'uuid': identity, 'animals': [{'uuid': animal_id, 'preparations': [{'uuid': prep_id, 'cells': [cell]}]}]}
metadata_path = self.folder/'imports'/(name + '.json')
metadata_path.write_text(json.dumps(document))
sha = hashlib.sha256(source_path.read_bytes()).hexdigest()
@@ -233,6 +248,37 @@ def test_warm_restart_uses_sealed_index_without_projection_decode_or_h5_headers(
self.assertTrue(all(cell['date']=='2026-09-24' for cell in restarted.cells.values()))
self.assertEqual({cell['start_time'] for cell in restarted.cells.values()}, {'09/24/2026 12:00:00:000000'})
+ def test_successful_generation_publication_reclaims_retired_indexes_and_projections(self):
+ source=Path(self.records[0]['manifest']['source_path'])
+ for _ in range(5):
+ previous=self.service.disk_index.path
+ stamp=source.stat();os.utime(source,ns=(stamp.st_atime_ns,stamp.st_mtime_ns+1000000))
+ self.service.refresh()
+ self.assertFalse(previous.exists())
+ self.assertEqual(list((self.folder/'cache/metadata').glob('*.sqlite')),[self.service.disk_index.path])
+ self.assertLessEqual(len(list((self.folder/'cache/source-projections').glob('*.json.zlib'))),2)
+ current=self.service.disk_index.path
+ reopened=WorkspaceService(self.folder)
+ self.assertEqual(reopened.disk_index.path,current)
+ self.assertEqual(reopened.last_refresh['metadata_index'],'reopened')
+
+ def test_failure_after_index_build_does_not_publish_or_reclaim_last_valid_generation(self):
+ from workspace_disk_index import DiskMetadataIndex
+ previous=self.service.disk_index
+ manifest=previous.path.parent/'.current-generations.json'
+ before=manifest.read_bytes();old_bytes=previous.path.read_bytes()
+ source=Path(self.records[0]['manifest']['source_path']);source.touch()
+ original=DiskMetadataIndex.build
+ def raced_build(*args,**kwargs):
+ index=original(*args,**kwargs);source.touch();return index
+ with patch.object(DiskMetadataIndex,'build',side_effect=raced_build):
+ with self.assertRaisesRegex(ValueError,'while building metadata index'):
+ self.service.refresh()
+ gc.collect()
+ self.assertEqual(manifest.read_bytes(),before)
+ self.assertEqual(previous.path.read_bytes(),old_bytes)
+ self.assertEqual(len(previous.rows()),2)
+
def test_all_inputs_are_preflighted_before_any_lazy_projection_is_read(self):
second = Path(self.records[1]['manifest']['metadata_path'])
second.write_text(second.read_text()+' ')
@@ -244,7 +290,7 @@ def test_all_inputs_are_preflighted_before_any_lazy_projection_is_read(self):
self.assertFalse(self.service._loaded)
def test_new_source_reuses_unchanged_lazy_projections_without_eager_merge(self):
- from collections import ChainMap
+ from workspace_service import _SourceDetails
from workspace_disk_index import DiskMetadataIndex
old_index = self.service.disk_index
old_ids = set(self.service.rows)
@@ -260,8 +306,8 @@ def test_new_source_reuses_unchanged_lazy_projections_without_eager_merge(self):
self.assertEqual(parses.call_count,1)
self.assertEqual(builds.call_count,1)
merged=builds.call_args.args[2]
- self.assertIsInstance(merged,ChainMap)
- self.assertEqual(sum(not isinstance(mapping,dict) for mapping in merged.maps),2)
+ self.assertIsInstance(merged,_SourceDetails)
+ self.assertEqual(sum(not isinstance(mapping,dict) for mapping in merged.sources.values()),2)
self.assertEqual((receipt['reused_sources'],receipt['rebuilt_sources']),(2,1))
self.assertTrue(old_ids < set(self.service.rows))
self.assertEqual(len(self.service.rows),3)
diff --git a/python/tests/test_workspace_root_boundaries.py b/python/tests/test_workspace_root_boundaries.py
new file mode 100644
index 0000000..5a433c4
--- /dev/null
+++ b/python/tests/test_workspace_root_boundaries.py
@@ -0,0 +1,96 @@
+"""Root rejection must happen before any workspace/project files are written."""
+import tempfile
+import unittest
+from pathlib import Path
+
+from workspace_installation import initialize_workspace, select_workspace, MANIFEST
+from workspace_launcher import create_launcher
+from workspace_projects import create_project, list_managed_projects
+
+
+class RootBoundaryTests(unittest.TestCase):
+ def setUp(self):
+ temporary = tempfile.TemporaryDirectory()
+ self.addCleanup(temporary.cleanup)
+ self.base = Path(temporary.name).resolve()
+ self.app = self.base / 'Application with spaces'
+ self.app.mkdir()
+ self.root = select_workspace(self.base / 'Research ü workspace', self.app)
+ self.project = Path(create_project(self.root, 'Original study', code_root=self.app)['path'])
+
+ def test_nested_project_storage_rejected_without_writes(self):
+ preference = self.app / '.rieke-runtime/workspace-selection.json'
+ original = preference.read_bytes()
+ for candidate in (self.project, self.project / 'imports' / 'Accidental root'):
+ before = set(self.project.rglob('*'))
+ for action in (lambda: select_workspace(candidate, self.app),
+ lambda: initialize_workspace(candidate, self.app),
+ lambda: create_project(candidate, 'Nested study', code_root=self.app)):
+ with self.assertRaisesRegex(ValueError, 'parent workspace'):
+ action()
+ self.assertEqual(set(self.project.rglob('*')), before)
+ self.assertEqual(preference.read_bytes(), original)
+
+ def test_nested_workspace_and_symlink_alias_cannot_bypass_boundary(self):
+ candidate = self.root / 'Nested root'
+ with self.assertRaisesRegex(ValueError, 'existing workspace'):
+ select_workspace(candidate, self.app)
+ self.assertFalse(candidate.exists())
+ alias = self.base / 'project alias'
+ alias.symlink_to(self.project, target_is_directory=True)
+ with self.assertRaisesRegex(ValueError, 'parent workspace'):
+ select_workspace(alias / 'imports' / 'Root', self.app)
+ self.assertFalse((self.project / 'imports' / 'Root').exists())
+
+ def test_http_rejected_selection_keeps_original_root_and_projects(self):
+ app = create_launcher(self.root, self.app, application_dir=self.app)
+ client = app.test_client()
+ response = client.post('/api/workspace', headers={'X-Workspace-Request':'1'},
+ json={'directory':str(self.project / 'exports' / 'Wrong root')})
+ self.assertEqual(response.status_code, 400, response.json)
+ self.assertIn('parent workspace', response.json['error'])
+ inventory = client.get('/api/projects').json
+ self.assertEqual(inventory['managed_root'], str(self.root))
+ self.assertEqual(len(inventory['projects']), 1)
+ self.assertFalse((self.project / 'exports' / 'Wrong root').exists())
+
+ def test_sibling_workspace_is_separate_and_existing_files_preserved(self):
+ second = self.base / 'Second workspace'
+ second.mkdir()
+ note = second / 'notes.txt'
+ note.write_text('Preserve this file')
+ select_workspace(second, self.app)
+ self.assertEqual(list_managed_projects(second)['projects'], [])
+ self.assertEqual(len(list_managed_projects(self.root)['projects']), 1)
+ self.assertEqual(note.read_text(), 'Preserve this file')
+ self.assertTrue((second / MANIFEST).is_file())
+
+ def test_explicit_project_folder_under_workspace_preserves_root_and_cannot_nest_in_project(self):
+ from workspace_projects import create_project_at
+ preference = self.app / '.rieke-runtime/workspace-selection.json'
+ original = preference.read_bytes()
+ chosen = self.root / 'Organization' / 'Exact study folder'
+ created = create_project_at(str(chosen), 'Exact study', code_root=self.app)
+ self.assertEqual(Path(created['path']), chosen)
+ self.assertEqual(preference.read_bytes(), original)
+ self.assertFalse((chosen.parent / MANIFEST).exists())
+ rejected = chosen / 'imports' / 'Nested project'
+ with self.assertRaisesRegex(ValueError, 'parent workspace'):
+ create_project_at(str(rejected), 'Nested project', code_root=self.app)
+ self.assertFalse(rejected.exists())
+
+ def test_missing_saved_root_not_recreated_and_explicit_override_works(self):
+ from workspace_bootstrap import runtime_paths
+ moved = self.root.with_name('Moved workspace')
+ self.root.rename(moved)
+ with self.assertRaisesRegex(ValueError, 'Saved workspace is unavailable'):
+ runtime_paths(self.app, environ={})
+ self.assertFalse(self.root.exists())
+ alternate = initialize_workspace(self.base / 'Alternate workspace', self.app)
+ settings = runtime_paths(self.app, environ={'RECORDING_WORKSPACE_ROOT':str(alternate)})
+ self.assertEqual(settings['managed_root'], str(alternate))
+ self.assertFalse(self.root.exists())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_workspace_service.py b/python/tests/test_workspace_service.py
index caecbe6..2801318 100644
--- a/python/tests/test_workspace_service.py
+++ b/python/tests/test_workspace_service.py
@@ -15,23 +15,30 @@
class EventRelation:
"""Minimal queryable event fixture that records the actual fetch contract."""
- def __init__(self, rows, calls, restrictions=()):
- self.rows, self.calls, self.restrictions = rows, calls, restrictions
+ def __init__(self, rows, calls, restrictions=(), projected=None):
+ self.rows, self.calls, self.restrictions, self.projected = rows, calls, restrictions, projected
def __and__(self, restriction):
- return EventRelation(self.rows, self.calls, self.restrictions + (restriction,))
+ return EventRelation(self.rows, self.calls, self.restrictions + (restriction,), self.projected)
+
+ def proj(self, *fields):
+ return EventRelation(self.rows, self.calls, self.restrictions, {'event_uuid', *fields})
def fetch(self, *, as_dict, order_by=None, limit=None, offset=0):
self.calls.append({'restrictions': self.restrictions, 'order_by': order_by,
'limit': limit, 'offset': offset})
- rows = [row for row in self.rows if all(
- all(row.get(key) == value for key, value in restriction.items())
- for restriction in self.restrictions)]
+ def matches(row, restriction):
+ if isinstance(restriction, list):
+ return any(matches(row, item) for item in restriction)
+ return all(row.get(key) == value for key, value in restriction.items())
+ rows = [row for row in self.rows if all(matches(row, restriction) for restriction in self.restrictions)]
for clause in reversed((order_by or '').split(',')):
if clause.strip():
field, direction = clause.strip().split()
rows.sort(key=lambda row: row[field], reverse=direction == 'DESC')
- return copy.deepcopy(rows[offset:None if limit is None else offset + limit])
+ rows = rows[offset:None if limit is None else offset + limit]
+ return copy.deepcopy([{key: row[key] for key in self.projected} for row in rows]
+ if self.projected is not None else rows)
class EventReadTests(unittest.TestCase):
diff --git a/python/tests/test_workspace_shared_tag_cache_dependencies.py b/python/tests/test_workspace_shared_tag_cache_dependencies.py
new file mode 100644
index 0000000..675d157
--- /dev/null
+++ b/python/tests/test_workspace_shared_tag_cache_dependencies.py
@@ -0,0 +1,106 @@
+"""Deterministic dependency-cache oracle using canonical rows and plain sets.
+
+The scalar evaluator below does not call the production predicate matcher or
+SQLite query builder. Every round primes eight cached queries, mutates canonical
+records, refreshes the real SQLite index, then checks exact membership and order.
+"""
+import random
+import unittest
+
+from test_workspace_shared_tag_index import Fixture
+
+
+FIELDS=('annotations/cell/tags','annotations/epoch/tags','annotations/effective/tags')
+
+
+def scalar_values(saved,row):
+ cell=set();epoch=set()
+ for (kind,target,_),record in saved.items():
+ if kind=='cell' and target==row['cell_uuid']:cell.update(record['tags'])
+ if kind=='epoch' and target==row['epoch_uuid']:epoch.update(record['tags'])
+ return {FIELDS[0]:sorted(cell),FIELDS[1]:sorted(epoch),FIELDS[2]:sorted(cell|epoch)}
+
+
+def scalar_matches(node,values):
+ if 'all' in node:return all(scalar_matches(child,values) for child in node['all'])
+ if 'any' in node:return any(scalar_matches(child,values) for child in node['any'])
+ if 'not' in node:return not scalar_matches(node['not'],values)
+ actual=values[node['field']];operator=node['operator']
+ # All three annotation fields exist as arrays even without saved records.
+ if operator=='exists':return True
+ if operator in ('missing','is_null'):return False
+ expected=node['value']
+ if operator=='contains':return expected in actual
+ if operator=='eq':return actual==expected
+ if operator=='ne':return actual!=expected
+ if operator=='in':return any(actual==candidate for candidate in expected)
+ if operator=='not_in':return all(actual!=candidate for candidate in expected)
+ raise AssertionError('Unexpected oracle operator: '+operator)
+
+
+class SharedTagCacheDependencyOracleTests(unittest.TestCase):
+ def test_200_mutation_batches_preserve_exact_cached_membership_and_fresh_tokens(self):
+ randomizer=random.Random(20260930)
+ fixture=Fixture();self.addCleanup(fixture.index.close)
+ vocabulary=['QC','qc','Inherited','different','é','e\u0301','new','absent']
+ checks=0
+ for step in range(200):
+ rows=[fixture.rows[index] for index in randomizer.sample(range(6),randomizer.randrange(1,7))]
+ queries=[]
+ for _ in range(8):
+ field=randomizer.choice(FIELDS)
+ operator=randomizer.choice(['contains','eq','ne','in','not_in','exists','missing','is_null'])
+ node={'field':field,'operator':operator}
+ if operator=='contains':node['value']=randomizer.choice(vocabulary)
+ elif operator in ('eq','ne'):
+ node['value']=sorted(randomizer.sample(vocabulary,randomizer.randrange(3)))
+ elif operator in ('in','not_in'):
+ node['value']=[[],sorted(randomizer.sample(vocabulary,randomizer.randrange(3)))]
+ if randomizer.choice([False,True]):node={'not':node}
+ if randomizer.choice([False,True]):
+ node={randomizer.choice(['all','any']):[node,{'field':randomizer.choice(FIELDS),
+ 'operator':'contains','value':randomizer.choice(vocabulary)}]}
+ filters=randomizer.choice([{}, {}, {'tag':randomizer.choice(vocabulary)},{'tagged':'true'}])
+ queries.append((filters,node))
+ fixture.index.refresh()
+ for filters,node in queries:fixture.index.matching(rows,filters,node)
+
+ for _ in range(randomizer.randrange(1,4)):
+ kind=randomizer.choice(['cell','epoch'])
+ target=(randomizer.choice(['c0','c1','c2']) if kind=='cell'
+ else randomizer.choice([row['epoch_uuid'] for row in fixture.rows]))
+ profile=randomizer.choice(['p1','p2','p3']);key=(kind,target,profile)
+ action=randomizer.randrange(4)
+ if action==0 and key in fixture.saved:
+ del fixture.saved[key]
+ fixture.version+=1;fixture.changes[key]=fixture.version
+ elif action==1 and key in fixture.saved:
+ fixture.saved[key]['author_name']='renamed'
+ fixture.version+=1;fixture.changes[key]=fixture.version
+ elif action==2 and fixture.saved:
+ old=randomizer.choice(list(fixture.saved));record=fixture.saved.pop(old)
+ fixture.version+=1;fixture.changes[old]=fixture.version
+ record.update(target_kind=kind,target_uuid=target,profile_uuid=profile,revision=fixture.version)
+ fixture.saved[key]=record;fixture.changes[key]=fixture.version
+ else:
+ fixture.put(kind,target,profile,randomizer.sample(vocabulary,randomizer.randrange(4)))
+
+ fixture.index.refresh()
+ for filters,node in queries:
+ expected=[]
+ for row in rows:
+ values=scalar_values(fixture.saved,row);effective=values[FIELDS[2]]
+ if (scalar_matches(node,values) and ('tag' not in filters or filters['tag'] in effective)
+ and ('tagged' not in filters or effective)):
+ expected.append(row['epoch_uuid'])
+ with self.subTest(batch=step,filters=filters,predicate=node):
+ actual,token=fixture.index.matching(rows,filters,node)
+ self.assertEqual(actual,tuple(expected))
+ self.assertEqual(token,fixture.version)
+ self.assertEqual(fixture.index.match_cache_bytes,
+ sum(entry[1] for entry in fixture.index.match_cache.values()))
+ checks+=1
+ self.assertEqual(checks,1600)
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_shared_tag_index.py b/python/tests/test_workspace_shared_tag_index.py
new file mode 100644
index 0000000..ecc4fa7
--- /dev/null
+++ b/python/tests/test_workspace_shared_tag_index.py
@@ -0,0 +1,271 @@
+"""Exact multi-profile/inherited membership and incremental source fencing."""
+import copy
+import json
+import sqlite3
+import unittest
+from unittest.mock import patch
+
+from workspace_annotations import tags
+from workspace_predicates import matches
+from workspace_predicates import predicate_catalog
+from workspace_tree import value_key,value_order,value_label
+from workspace_service import validate_tag_filter_predicate
+from workspace_shared_tag_index import SharedTagIndex,SharedTagsChanged,FIELDS
+
+
+class Fixture:
+ def __init__(self):
+ self.version=1;self.calls=[];self.changes={};self.saved={}
+ self.rows=[{'epoch_uuid':f'e{i}','cell_uuid':f'c{i//2}'} for i in range(6)]
+ self.put('cell','c0','p1',['Inherited','QC'])
+ self.put('epoch','e0','p1',['QC','qc','é'])
+ self.put('epoch','e0','p2',['QC','e\u0301'])
+ self.put('epoch','e1','p2',[])
+ self.put('epoch','e2','p1',['different'])
+ self.index=SharedTagIndex(generation=lambda:self.version,
+ changes=lambda old:([{'target_kind':key[0],'target_uuid':key[1],'profile_uuid':key[2]}
+ for key,value in self.changes.items() if value>old],self.version),
+ records=self.records,validate=lambda row:tags(row['tags']))
+ def put(self,kind,target,profile,values):
+ self.version+=1;key=(kind,target,profile);self.changes[key]=self.version
+ self.saved[key]={'target_kind':kind,'target_uuid':target,'profile_uuid':profile,
+ 'author_name':'same author display name','revision':self.version,'tags':values}
+ def records(self,keys):
+ self.calls.append(copy.deepcopy(keys))
+ selected=self.saved.values() if keys is None else [self.saved[(key['target_kind'],key['target_uuid'],key['profile_uuid'])]
+ for key in keys if (key['target_kind'],key['target_uuid'],key['profile_uuid']) in self.saved]
+ yield from copy.deepcopy(list(selected))
+ def values(self,row):
+ cell={tag for (kind,target,_),record in self.saved.items() if kind=='cell' and target==row['cell_uuid'] for tag in record['tags']}
+ epoch={tag for (kind,target,_),record in self.saved.items() if kind=='epoch' and target==row['epoch_uuid'] for tag in record['tags']}
+ return dict(zip(FIELDS,[sorted(cell),sorted(epoch),sorted(cell|epoch)]))
+
+
+class SharedTagIndexTests(unittest.TestCase):
+ def setUp(self):self.f=Fixture();self.addCleanup(self.f.index.close)
+
+ def test_exact_case_unicode_profile_union_inheritance_and_all_predicate_operators(self):
+ f=self.f;self.assertTrue(f.index.refresh())
+ for field in FIELDS:
+ predicates=[{'field':field,'operator':op} for op in ('exists','missing','is_null')]
+ predicates += [{'field':field,'operator':'contains','value':value} for value in ('QC','qc','é','e\u0301','Inherited','absent')]
+ predicates += [{'field':field,'operator':op,'value':value} for op in ('eq','ne') for value in ([],['QC'],['QC','qc'],['qc','QC'],['QC','QC'],['Inherited','QC'])]
+ predicates += [{'field':field,'operator':op,'value':[[],['QC'],['different']]} for op in ('in','not_in')]
+ for predicate in predicates:
+ for node in (predicate,{'not':predicate},{'all':[predicate,{'not':{'field':field,'operator':'missing'}}]}, {'any':[predicate,{'field':field,'operator':'eq','value':[]}]}):
+ with self.subTest(node=node):
+ expected=tuple(row['epoch_uuid'] for row in f.rows if matches(node,f.values(row)))
+ actual,token=f.index.matching(f.rows,{},node)
+ self.assertEqual(actual,expected);self.assertEqual(token,f.version)
+ self.assertEqual(len(f.calls),1,'Warm filters must not reread canonical tag JSON')
+
+ def test_scope_order_and_not_complement_exclude_foreign_epochs(self):
+ f=self.f;f.index.refresh();rows=[f.rows[5],f.rows[1],f.rows[0]]
+ node={'not':{'field':'annotations/epoch/tags','operator':'contains','value':'QC'}}
+ self.assertEqual(f.index.matching(rows,{},node)[0],('e5','e1'))
+ self.assertEqual(f.index.matching(rows,{'tag':'Inherited'})[0],('e1','e0'))
+ self.assertEqual(f.index.matching(rows,{'tagged':'true'})[0],('e1','e0'))
+
+ def test_unrelated_tag_edits_keep_boolean_membership_but_use_fresh_generation(self):
+ f=self.f;f.index.refresh()
+ predicate={'all':[{'any':[{'field':'annotations/epoch/tags','operator':'contains','value':'QC'},
+ {'field':'annotations/cell/tags','operator':'contains','value':'Inherited'}]},
+ {'not':{'field':'annotations/effective/tags','operator':'contains','value':'excluded'}}]}
+ original,old=f.index.matching(f.rows,{},predicate)
+ f.put('epoch','e2','p1',['different','unrelated'])
+ f.index.refresh();sql=[];f.index.connection.set_trace_callback(sql.append)
+ actual,current=f.index.matching(f.rows,{},predicate)
+ self.assertEqual(actual,original);self.assertNotEqual(current,old)
+ self.assertFalse(any('FROM filter_scope AS s WHERE' in query for query in sql),sql)
+ # Changing a referenced inherited literal must invalidate the result.
+ f.put('cell','c0','p1',['QC']);f.index.refresh();sql.clear()
+ actual,_=f.index.matching(f.rows,{},predicate)
+ self.assertEqual(actual,tuple(row['epoch_uuid'] for row in f.rows if matches(predicate,f.values(row))))
+ self.assertTrue(any('FROM filter_scope AS s WHERE' in query for query in sql),sql)
+
+ def test_array_and_empty_memberships_invalidate_on_unrelated_literals(self):
+ f=self.f;f.index.refresh()
+ queries=[({}, {'field':'annotations/epoch/tags','operator':'eq','value':[]}),
+ ({'tagged':'true'},None),({}, {'field':'annotations/epoch/tags','operator':'in','value':[[],['different']]})]
+ for filters,predicate in queries:f.index.matching(f.rows,filters,predicate)
+ f.put('epoch','e1','p2',['new']);f.index.refresh()
+ self.assertEqual(f.index.match_cache_bytes,0)
+ for filters,predicate in queries:
+ expected=tuple(row['epoch_uuid'] for row in f.rows if
+ (bool(f.values(row)['annotations/effective/tags']) if predicate is None else matches(predicate,f.values(row))))
+ self.assertEqual(f.index.matching(f.rows,filters,predicate)[0],expected)
+
+ def test_large_change_batches_drop_matches_without_retaining_old_tag_sets(self):
+ f=self.f;f.index.refresh();f.index.matching(f.rows,{'tag':'QC'})
+ f.put('epoch','e2','p1',['different','unrelated'])
+ f.put('epoch','e3','p1',['unrelated'])
+ with patch('workspace_shared_tag_index.MATCH_DELTA_MAX_RECORDS',1):f.index.refresh()
+ self.assertEqual(f.index.match_cache_bytes,0)
+ self.assertEqual(f.index.matching(f.rows,{'tag':'QC'})[0],('e0','e1'))
+
+ def test_exact_literal_invalidation_handles_case_unicode_deletions_and_author_only_changes(self):
+ f=self.f;f.index.refresh()
+ f.index.matching(f.rows,{'tag':'QC'});f.index.matching(f.rows,{'tag':'é'})
+ f.put('epoch','e0','p1',['QC','é']) # Only lowercase qc was removed.
+ f.index.refresh();self.assertEqual(len(f.index.match_cache),2)
+ f.put('epoch','e0','p1',['QC','e\u0301'])
+ f.index.refresh();self.assertEqual(len(f.index.match_cache),1)
+ f.saved[('epoch','e0','p1')]['author_name']='Changed stored name'
+ f.version+=1;f.changes[('epoch','e0','p1')]=f.version
+ f.index.refresh();self.assertEqual(len(f.index.match_cache),1)
+ del f.saved[('epoch','e0','p1')];f.version+=1;f.changes[('epoch','e0','p1')]=f.version
+ f.index.refresh();self.assertEqual(len(f.index.match_cache),0)
+ self.assertEqual(f.index.matching(f.rows,{'tag':'QC'})[0],('e0','e1'))
+
+ def test_incremental_edits_deletion_and_profile_changes_load_only_changed_rows(self):
+ f=self.f;f.index.refresh();f.calls.clear()
+ f.put('epoch','e2','p1',['QC','new'])
+ key=('cell','c0','p1');del f.saved[key];f.version+=1;f.changes[key]=f.version
+ f.version+=1;f.changes[('profile','p1','p1')]=f.version
+ f.index.refresh()
+ self.assertEqual(len(f.calls),1)
+ self.assertCountEqual(f.calls[0],[{'target_kind':'epoch','target_uuid':'e2','profile_uuid':'p1'},
+ {'target_kind':'cell','target_uuid':'c0','profile_uuid':'p1'}])
+ self.assertEqual(f.index.matching(f.rows,{'tag':'QC'})[0],('e0','e2'))
+ self.assertEqual(f.index.stats['full_builds'],1);self.assertEqual(f.index.stats['delta_updates'],1)
+
+ def test_generation_race_and_invalid_tags_never_publish_a_partial_index(self):
+ f=self.f;f.index.refresh();old=f.index.token
+ f.put('epoch','e2','p1',['new'])
+ original=f.index.records
+ def racing(keys):
+ yield from original(keys)
+ f.version+=1
+ f.index.records=racing
+ with self.assertRaises(SharedTagsChanged):f.index.refresh()
+ self.assertEqual(f.index.token,old)
+ f.index.records=original;f.index.refresh()
+ f.put('epoch','e2','p1',['duplicate','duplicate'])
+ with self.assertRaises(ValueError):f.index.refresh()
+ with self.assertRaises(SharedTagsChanged):f.index.matching(f.rows,{'tag':'new'})
+
+ def test_unavailable_generation_never_uses_old_cached_membership(self):
+ f=self.f;f.index.refresh();f.version=None
+ self.assertFalse(f.index.refresh())
+ with self.assertRaises(SharedTagsChanged):f.index.matching(f.rows,{'tag':'QC'})
+
+ def test_large_valid_literals_do_not_exceed_sqlite_variable_limit(self):
+ f=self.f;f.index.refresh()
+ predicate={'any':[{'field':'annotations/effective/tags','operator':'eq',
+ 'value':[f't{number:02}' for number in range(100)]} for _ in range(70)]}
+ predicate=validate_tag_filter_predicate(json.dumps(predicate))
+ f.index.connection.setlimit(sqlite3.SQLITE_LIMIT_VARIABLE_NUMBER,999)
+ self.assertEqual(f.index.matching(f.rows,{},predicate)[0],())
+
+ def test_summary_is_exact_across_profiles_inheritance_and_empty_scope(self):
+ f=self.f;f.index.refresh()
+ for rows in (f.rows,[f.rows[1],f.rows[0]],[]):
+ expected_tags={};cells=set();epochs=set()
+ for row in rows:
+ for tag in f.values(row)['annotations/effective/tags']:
+ item=expected_tags.setdefault(tag,[set(),set()]);item[0].add(row['epoch_uuid']);item[1].add(row['cell_uuid'])
+ epochs.add(row['epoch_uuid'])
+ if f.values(row)['annotations/cell/tags']:cells.add(row['cell_uuid'])
+ counts=sorted([{'tag':tag,'epoch_count':len(e),'cell_count':len(c)} for tag,(e,c) in expected_tags.items()],
+ key=lambda item:(-item['epoch_count'],item['tag'].casefold(),item['tag']))
+ expected={'shared_tagged_cells':len(cells),'shared_tagged_epochs':len(epochs),'tags':counts[:12],
+ 'total_tags':len(counts),'truncated':len(counts)>12}
+ self.assertEqual(f.index.summary(rows),expected)
+ self.assertEqual(f.index.summary(rows),expected)
+ self.assertEqual(len(f.calls),1)
+
+ def test_catalog_matches_exact_legacy_arrays_authorship_order_and_bounded_choices(self):
+ f=self.f
+ for number in range(70):
+ row={'epoch_uuid':f'x{number}','cell_uuid':'extra'};f.rows.append(row)
+ f.put('epoch',row['epoch_uuid'],'p3',[f'unique-{number}', 'quoted"tag','\\slash','Σ'])
+ f.index.refresh()
+ fields=[*FIELDS,'annotations/authors','annotations/author_uuids']
+ actual=f.index.catalog(f.rows,fields)
+ for field in fields:
+ values={}
+ for row in f.rows:
+ effective=[record for (kind,target,_),record in f.saved.items()
+ if (kind=='epoch' and target==row['epoch_uuid'] or kind=='cell' and target==row['cell_uuid']) and record['tags']]
+ current=f.values(row)
+ current['annotations/authors']=sorted({r['author_name'] for r in effective})
+ current['annotations/author_uuids']=sorted({r['profile_uuid'] for r in effective})
+ values[row['epoch_uuid']]={field:current[field]}
+ distinct={value_key(current[field]):current[field] for current in values.values()}
+ expected=predicate_catalog({'fields':[{'id':field}]},values)['fields'][0]
+ self.assertEqual(actual[field]['choices'],expected['choices'])
+ self.assertEqual(actual[field]['choices_truncated'],expected['choices_truncated'])
+ self.assertEqual(actual[field]['distinct_count'],len(distinct))
+ self.assertEqual(actual[field]['examples'],[value_label(value)[:160] for value in sorted(distinct.values(),key=value_order)[:5]])
+ f.index.catalog(f.rows,fields)
+ self.assertEqual(len(f.calls),1)
+
+ def test_incremental_aggregates_match_clean_rebuild_after_changes_and_tombstones(self):
+ f=self.f;f.index.refresh();fields=[*FIELDS,'annotations/authors','annotations/author_uuids']
+ f.index.catalog(f.rows,fields);f.index.summary(f.rows)
+ for number in range(18):
+ kind='cell' if number%3==0 else 'epoch'
+ target=f'c{number%3}' if kind=='cell' else f'e{number%6}'
+ profile=f'p{number%2+1}'
+ f.put(kind,target,profile,[] if number%4==0 else [f'new-{number%5}','QC' if number%2 else 'qc'])
+ if number%7==0:
+ key=(kind,target,profile);del f.saved[key]
+ f.index.refresh()
+ reference=SharedTagIndex(generation=lambda:f.version,changes=lambda old:None,
+ records=f.records,validate=lambda row:tags(row['tags']))
+ try:
+ reference.refresh()
+ self.assertEqual(f.index.catalog(f.rows,fields),reference.catalog(f.rows,fields))
+ self.assertEqual(f.index.summary(f.rows),reference.summary(f.rows))
+ self.assertEqual(f.index.matching(f.rows,{'tag':'QC'}),reference.matching(f.rows,{'tag':'QC'}))
+ finally:reference.close()
+
+ def test_filter_pagination_does_not_discard_catalog_or_summary_work(self):
+ f=self.f;f.index.refresh();fields=list(FIELDS)
+ catalog=f.index.catalog(f.rows,fields)
+ summary=f.index.summary(f.rows)
+ with patch.object(f.index,'_array_sql',side_effect=AssertionError('Do not rebuild catalog arrays while paging')):
+ f.index.matching(f.rows[::2],{'tag':'QC'})
+ f.index.matching(f.rows,{'tag':'Inherited'})
+ self.assertEqual(f.index.catalog(f.rows,fields),catalog)
+ self.assertEqual(f.index.summary(f.rows),summary)
+
+ def test_cached_membership_is_generation_fenced_and_budgeted(self):
+ f=self.f;f.index.refresh()
+ first=f.index.matching(f.rows,{'tag':'QC'})[0]
+ self.assertIs(f.index.matching(f.rows,{'tag':'QC'})[0],first)
+ with patch('workspace_shared_tag_index.MATCH_CACHE_BYTES',1):
+ f.put('epoch','e2','p1',['QC']);f.index.refresh()
+ self.assertEqual(f.index.matching(f.rows,{'tag':'QC'})[0],('e0','e1','e2'))
+ self.assertEqual(f.index.match_cache_bytes,0)
+ f.version+=1
+ with self.assertRaises(SharedTagsChanged):f.index.matching(f.rows,{'tag':'QC'})
+
+ def test_repeated_edits_reclaim_unused_tag_and_catalog_dictionary_entries(self):
+ f=self.f;f.index.refresh();fields=list(FIELDS)
+ f.index.catalog(f.rows,fields);f.index.summary(f.rows)
+ for number in range(100):
+ f.put('epoch','e2','p1',[f'changed-{number}'])
+ f.index.refresh()
+ current={tag for row in f.saved.values() for tag in row['tags']}
+ self.assertEqual(f.index.connection.execute('SELECT COUNT(*) FROM tag_values').fetchone()[0],len(current))
+ self.assertEqual(f.index.connection.execute('SELECT COUNT(*) FROM catalog_examples').fetchone()[0],
+ f.index.connection.execute('SELECT COUNT(DISTINCT example_id) FROM catalog_buckets').fetchone()[0])
+ self.assertFalse(f.index.connection.execute("SELECT 1 FROM summary_tags WHERE tag='changed-0'").fetchone())
+
+ def test_catalog_digest_collisions_never_merge_distinct_exact_arrays(self):
+ f=self.f;f.index.refresh();fields=list(FIELDS)
+ f.index.connection.create_function('array_hash',1,lambda value:b'forced collision',deterministic=True)
+ f.index.catalog(f.rows,fields)
+ f.put('epoch','e2','p1',['new exact array']);f.index.refresh()
+ reference=SharedTagIndex(generation=lambda:f.version,changes=lambda old:None,
+ records=f.records,validate=lambda row:tags(row['tags']))
+ try:
+ reference.refresh()
+ self.assertEqual(f.index.catalog(f.rows,fields),reference.catalog(f.rows,fields))
+ self.assertEqual(f.index.connection.execute('SELECT COUNT(DISTINCT value_hash) FROM catalog_buckets').fetchone()[0],1)
+ self.assertGreater(f.index.connection.execute('SELECT COUNT(*) FROM catalog_buckets').fetchone()[0],1)
+ finally:reference.close()
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_shared_tag_threads.py b/python/tests/test_workspace_shared_tag_threads.py
new file mode 100644
index 0000000..c8c5880
--- /dev/null
+++ b/python/tests/test_workspace_shared_tag_threads.py
@@ -0,0 +1,79 @@
+"""Transfer actual SQLite ownership safely across scientific HTTP threads."""
+import concurrent.futures
+import copy
+import threading
+import unittest
+
+from workspace_shared_tag_index import SharedTagIndex
+
+
+class SharedTagThreadTests(unittest.TestCase):
+ def setUp(self):
+ self.version=1
+ self.rows=[{'epoch_uuid':'epoch-1','cell_uuid':'cell-1'}, {'epoch_uuid':'epoch-2','cell_uuid':'cell-1'}]
+ self.saved=[{'target_kind':'epoch','target_uuid':'epoch-1','profile_uuid':'profile-1',
+ 'author_name':'Author','revision':1,'tags':['exact','é']}]
+ self.index=SharedTagIndex(generation=lambda:self.version,changes=lambda token:None,
+ records=lambda keys:iter(copy.deepcopy(self.saved)),validate=lambda row:row['tags'])
+ self.addCleanup(self.index.close)
+
+ def test_connection_built_in_one_thread_can_rebuild_query_and_close_in_another(self):
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as first, \
+ concurrent.futures.ThreadPoolExecutor(max_workers=1) as second:
+ first_id=first.submit(threading.get_ident).result(timeout=5)
+ self.assertTrue(first.submit(self.index.refresh).result(timeout=5))
+ self.assertEqual(first.submit(self.index.matching,self.rows,{'tag':'exact'}).result(timeout=5)[0],('epoch-1',))
+ # Keep the first worker alive so SQLite cannot mistake a recycled
+ # thread identifier for ownership transfer.
+ self.assertNotEqual(first_id,second.submit(threading.get_ident).result(timeout=5))
+ # Force the production full-rebuild path that closes the previous
+ # connection before creating its replacement on a Waitress worker.
+ self.version=2
+ self.saved[0]['revision']=2;self.saved[0]['tags']=['changed']
+ self.assertTrue(second.submit(self.index.refresh).result(timeout=5))
+ self.assertEqual(second.submit(self.index.matching,self.rows,{'tag':'changed'}).result(timeout=5)[0],('epoch-1',))
+ self.assertEqual(second.submit(self.index.summary,self.rows).result(timeout=5)['shared_tagged_epochs'],1)
+ second.submit(self.index.close).result(timeout=5)
+ self.assertIsNone(self.index.connection)
+
+ def test_overlapping_queries_have_independent_exact_temp_scopes(self):
+ self.index.refresh()
+ barrier=threading.Barrier(6)
+ def query(number):
+ barrier.wait(timeout=5)
+ rows=self.rows if number%2==0 else list(reversed(self.rows))
+ for _ in range(12):
+ actual,token=self.index.matching(rows,{}, {'not':{'field':'annotations/epoch/tags','operator':'contains','value':'absent'}})
+ if actual!=tuple(row['epoch_uuid'] for row in rows) or token!=1:
+ raise AssertionError('Another request replaced this query scope')
+ catalog=self.index.catalog(rows,['annotations/epoch/tags'])
+ if catalog['annotations/epoch/tags']['count']!=2:
+ raise AssertionError('Another request replaced this catalog scope')
+ if self.index.summary(rows)['shared_tagged_epochs']!=1:
+ raise AssertionError('Another request replaced this summary scope')
+ return True
+ with concurrent.futures.ThreadPoolExecutor(max_workers=6) as pool:
+ self.assertTrue(all(result.result(timeout=15) for result in [pool.submit(query,i) for i in range(6)]))
+
+ def test_close_waits_for_full_refresh_transaction_and_allows_later_reopen(self):
+ entered,release,closed=threading.Event(),threading.Event(),threading.Event()
+ def records(keys):
+ entered.set()
+ if not release.wait(timeout=5):raise AssertionError('Test did not release paused canonical read')
+ yield from copy.deepcopy(self.saved)
+ self.index.records=records
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
+ refreshing=pool.submit(self.index.refresh)
+ self.assertTrue(entered.wait(timeout=5))
+ closing=pool.submit(lambda:(self.index.close(),closed.set()))
+ try:self.assertFalse(closed.wait(timeout=.05),'Close raced an active SQLite transaction')
+ finally:release.set()
+ self.assertTrue(refreshing.result(timeout=5))
+ closing.result(timeout=5)
+ self.assertIsNone(self.index.connection)
+ self.index.records=lambda keys:iter(copy.deepcopy(self.saved))
+ self.assertTrue(self.index.refresh())
+ self.assertEqual(self.index.matching(self.rows,{'tag':'exact'})[0],('epoch-1',))
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_shared_vocabulary.py b/python/tests/test_workspace_shared_vocabulary.py
new file mode 100644
index 0000000..2b96afe
--- /dev/null
+++ b/python/tests/test_workspace_shared_vocabulary.py
@@ -0,0 +1,117 @@
+"""Exact autocomplete counts and attribution survive edits and scope switches."""
+import unittest
+from unittest.mock import patch
+from test_workspace_shared_tag_index import Fixture
+from workspace_shared_tag_index import SharedTagsChanged
+
+
+class SharedVocabularyTests(unittest.TestCase):
+ def setUp(self):
+ self.fixture=Fixture()
+ self.addCleanup(self.fixture.index.close)
+
+ def expected(self, query='', limit=30):
+ values={}
+ for (kind,target,profile),record in sorted(self.fixture.saved.items()):
+ for tag in record['tags']:
+ entry=values.setdefault(tag,{'targets':set(),'authors':{}})
+ entry['targets'].add((kind,target));entry['authors'][profile]=record['author_name']
+ selected=sorted(((tag,value) for tag,value in values.items() if tag.casefold().startswith(query.strip().casefold())),
+ key=lambda item:(-len(item[1]['targets']),item[0].casefold(),item[0]))
+ return {'tags':[{'tag':tag,'count':len(value['targets']),'authors':[
+ {'profile_uuid':key,'display_name':name} for key,name in sorted(value['authors'].items())]} for tag,value in selected[:limit]],
+ 'scope':'project_shared_annotations','query':query,'total':len(selected),'limit':limit,
+ 'count_unit':'distinct_annotation_targets','match':'case_insensitive_prefix','has_more':len(selected)>limit}
+
+ def check(self):
+ for query in ('','qC','é','e\u0301','different','new',' qc ','%','_','\U0010ffff','\ud800'):
+ for limit in (1,30):
+ self.assertEqual(self.fixture.index.suggestions(query,limit),self.expected(query,limit))
+
+ def test_counts_are_targets_not_profiles_or_inherited_epochs_and_warm_reads_are_bounded(self):
+ self.check();self.assertEqual(len(self.fixture.calls),1)
+ for _ in range(4):self.check()
+ self.assertEqual(len(self.fixture.calls),1)
+ self.fixture.index.matching(self.fixture.rows[:2],{'tag':'QC'})
+ self.check() # Autocomplete covers the project, not the last filter scope.
+ self.assertEqual(len(self.fixture.calls),1)
+
+ def test_edit_remove_delete_and_rebuild_preserve_exact_counts_and_names(self):
+ f=self.fixture;self.check()
+ for kind,target,profile,tags in [('epoch','e0','p1',['QC','new']),('epoch','e0','p2',[]),
+ ('cell','c0','p1',[]),('epoch','e5','p1',['QC']),('epoch','e2','p1',['QC']),
+ ('epoch','e5','p1',[]),('epoch','e0','p1',[]),('epoch','e2','p1',[])]:
+ f.put(kind,target,profile,tags)
+ f.saved[(kind,target,profile)]['author_name']='Distinct stored name '+target
+ self.check()
+ key=('epoch','e2','p1');del f.saved[key];f.version+=1;f.changes[key]=f.version
+ self.check()
+ f.index.changes=lambda _:None
+ f.put('epoch','e3','p2',['new'])
+ self.check();self.assertEqual(f.index.stats['full_builds'],2)
+
+ def test_generation_failure_rolls_back_derived_counts_then_recovers(self):
+ f=self.fixture;self.check();f.put('epoch','e2','p1',['QC','new'])
+ original=f.index.records
+ def race(keys):
+ yield from original(keys)
+ f.version+=1
+ with patch.object(f.index,'records',side_effect=race):
+ with self.assertRaises(SharedTagsChanged):f.index.suggestions('',30)
+ self.check()
+
+ def test_same_display_names_keep_profiles_separate_and_last_target_name_is_stable(self):
+ f=self.fixture
+ f.put('epoch','e5','p1',['QC']);f.saved[('epoch','e5','p1')]['author_name']='Final saved name'
+ self.check()
+ f.put('epoch','e0','p1',['QC']);f.saved[('epoch','e0','p1')]['author_name']='Earlier saved name'
+ self.check()
+ f.put('epoch','e5','p1',[]);self.check()
+ f.put('epoch','e5','p2',['QC']);self.check()
+
+ def test_many_prefixes_bound_author_cache_and_evicted_results_rebuild_exactly(self):
+ f=self.fixture
+ f.put('epoch','e0','p1',[f'tag-{i:03}' for i in range(80)])
+ f.put('epoch','e1','p2',[f'tag-{i:03}' for i in range(80,160)])
+ for i in range(160):
+ query=f'tag-{i:03}'
+ self.assertEqual(f.index.suggestions(query,1),self.expected(query,1))
+ self.assertEqual(f.index.connection.execute('SELECT COUNT(*) FROM shared_vocabulary_ready').fetchone()[0],128)
+ self.assertEqual(f.index.suggestions('tag-000',1),self.expected('tag-000',1))
+
+ def test_failed_initialization_discards_partial_derived_schema_and_retries_safely(self):
+ from workspace_shared_vocabulary import SharedVocabulary
+ original=SharedVocabulary.__init__
+ def fail_after_schema(vocabulary,index):
+ original(vocabulary,index)
+ raise OSError('Injected derived storage failure')
+ with patch.object(SharedVocabulary,'__init__',fail_after_schema):
+ with self.assertRaises(OSError):self.fixture.index.suggestions('',30)
+ self.assertIsNone(self.fixture.index.connection)
+ self.check()
+
+ def test_large_external_batch_rebuilds_only_vocabulary_without_old_union_copies(self):
+ f=self.fixture;self.check()
+ f.put('epoch','e1','p2',['new']);f.put('epoch','e2','p1',['new','QC'])
+ with patch('workspace_shared_tag_index.MATCH_DELTA_MAX_RECORDS',1), \
+ patch.object(f.index.shared_vocabulary,'before',side_effect=AssertionError('No large old-union copy')):
+ f.index.refresh()
+ self.assertIsNone(f.index.shared_vocabulary)
+ self.check();self.assertEqual(f.index.stats['full_builds'],1)
+
+ def test_failed_large_batch_restores_dropped_vocabulary_then_retries(self):
+ f=self.fixture;self.check();original=f.index.shared_vocabulary
+ f.put('epoch','e1','p2',['new']);f.put('epoch','e2','p1',['new','QC'])
+ records=f.index.records
+ def race(keys):
+ yield from records(keys)
+ f.version+=1
+ with patch('workspace_shared_tag_index.MATCH_DELTA_MAX_RECORDS',1), \
+ patch.object(f.index,'records',side_effect=race):
+ with self.assertRaises(SharedTagsChanged):f.index.refresh()
+ self.assertIs(f.index.shared_vocabulary,original)
+ self.assertTrue(f.index.connection.execute('SELECT COUNT(*) FROM shared_vocabulary_counts').fetchone()[0])
+ self.check()
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_source_identity.py b/python/tests/test_workspace_source_identity.py
new file mode 100644
index 0000000..5d71ecb
--- /dev/null
+++ b/python/tests/test_workspace_source_identity.py
@@ -0,0 +1,304 @@
+"""Physical H5 identity and dependency closure, using only disposable recordings."""
+import copy
+import json
+from pathlib import Path
+import tempfile
+import unittest
+import uuid
+
+import h5py
+import numpy as np
+
+import recording_workspace as workspace
+from workspace_service import WorkspaceService, read_response_window
+
+
+def uid(number):
+ return str(uuid.UUID(int=number))
+
+
+def fixture(path, *, soft=False):
+ """Two identically labelled cells, with one streamless epoch and empty block."""
+ doc = {'uuid': uid(1), 'rig_type': 'PATCH', 'animals': []}
+ with h5py.File(path, 'w') as h5:
+ root = h5.create_group('experiment-' + uid(1))
+ root.attrs.update(uuid=uid(1).encode(), label=b'Experiment')
+ root.create_group('properties')
+ animal = root.create_group('sources/animal-' + uid(2))
+ animal.attrs['uuid'] = uid(2)
+ animal['experiment'] = h5py.SoftLink(root.name) if soft else root
+ prep = animal.create_group('sources/preparation-' + uid(3))
+ prep.attrs['uuid'] = uid(3)
+ parsed_prep = {'uuid': uid(3), 'cells': []}
+ doc['animals'] = [{'uuid': uid(2), 'preparations': [parsed_prep]}]
+ for index in (0, 1):
+ offset = 10 + index * 10
+ cell = prep.create_group('sources/cell-' + uid(offset))
+ cell.attrs['uuid'] = uid(offset)
+ group = root.create_group('epochGroups/group-' + uid(offset+1))
+ group.attrs['uuid'] = uid(offset+1)
+ group['source'] = h5py.SoftLink(cell.name) if soft else cell
+ block = group.create_group('epochBlocks/block-' + uid(offset+2))
+ block.attrs.update(uuid=uid(offset+2), protocolID='example.Protocol')
+ block.create_group('protocolParameters')
+ epoch = block.create_group('epochs/epoch-' + uid(offset+3))
+ epoch.attrs['uuid'] = uid(offset+3)
+ epoch.create_group('protocolParameters')
+ parsed_epoch = {'uuid': uid(offset+3), 'start_time': '09/29/2026 12:00:00:000000',
+ 'parameters': {}, 'properties': {}, 'attributes': {}, 'responses': {}, 'stimuli': {}}
+ if not index:
+ stream = epoch.create_group('responses/Amp1-' + uid(offset+4))
+ stream.attrs.update(uuid=uid(offset+4), sampleRate=10000.)
+ data = np.zeros(4, dtype=[('quantity', 'f8'), ('units', 'S8')])
+ data['quantity'], data['units'] = np.arange(4), b'pA'
+ stream.create_dataset('data', data=data)
+ parsed_epoch['responses']['Amp1'] = {'uuid': uid(offset+4), 'h5path': stream.name,
+ 'sampleRate': 10000., 'sampleRateUnits': 'Hz'}
+ parsed_group = {'uuid': uid(offset+1), 'epoch_blocks': [
+ {'uuid': uid(offset+2), 'protocolID': 'example.Protocol', 'epochs': [parsed_epoch]}]}
+ parsed_prep['cells'].append({'uuid': uid(offset), 'label': 'Same label',
+ 'start_time': '09/29/2026 11:00:00:000000', 'epoch_groups': [parsed_group]})
+ # The pinned parser intentionally omits empty blocks. They still need duplicate checks.
+ empty = group.create_group('epochBlocks/empty-' + uid(30))
+ empty.attrs.update(uuid=uid(30), protocolID='example.Protocol')
+ empty.create_group('epochs')
+ return doc
+
+
+class SourceIdentityTests(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp.cleanup)
+ self.path = Path(self.temp.name) / 'source.h5'
+ self.doc = fixture(self.path)
+
+ def validate(self):
+ with h5py.File(self.path, 'r') as h5:
+ workspace.validate_source_identity(h5, self.doc)
+
+ def cells(self):
+ return self.doc['animals'][0]['preparations'][0]['cells']
+
+ def test_control_distinct_cells_same_label_and_streamless_epoch(self):
+ self.validate()
+ self.doc = fixture(self.path, soft=True)
+ self.validate()
+
+ def test_wrong_group_owner_among_real_cells(self):
+ cells = self.cells()
+ cells[0]['epoch_groups'], cells[1]['epoch_groups'] = cells[1]['epoch_groups'], cells[0]['epoch_groups']
+ with self.assertRaisesRegex(ValueError, 'group.*source cell|cell.*ownership'):
+ self.validate()
+
+ def test_wrong_preparation_ownership(self):
+ self.doc['animals'][0]['preparations'][0]['uuid'] = uid(900)
+ with self.assertRaisesRegex(ValueError, 'preparation.*(identity|membership|source)'):
+ self.validate()
+
+ def test_duplicate_raw_streamless_epoch_is_not_collapsed(self):
+ with h5py.File(self.path, 'r+') as h5:
+ group = h5['/experiment-' + uid(1) + '/epochGroups/group-' + uid(21) + '/epochBlocks/block-' + uid(22) + '/epochs']
+ group.create_group('omitted').attrs['uuid'] = uid(23)
+ with self.assertRaisesRegex(ValueError, 'Duplicate.*epoch.*UUID'):
+ self.validate()
+
+ def test_empty_block_duplicate_uuid_rejected(self):
+ with h5py.File(self.path, 'r+') as h5:
+ h5['/experiment-' + uid(1) + '/epochGroups/group-' + uid(21) + '/epochBlocks/empty-' + uid(30)].attrs['uuid'] = uid(12)
+ with self.assertRaisesRegex(ValueError, 'Duplicate.*block.*UUID'):
+ self.validate()
+
+ def test_stream_membership_kind_and_device(self):
+ for mutation in ('omit', 'kind', 'device'):
+ with self.subTest(mutation=mutation):
+ self.doc = fixture(self.path)
+ epoch = self.cells()[0]['epoch_groups'][0]['epoch_blocks'][0]['epochs'][0]
+ stream = epoch['responses'].pop('Amp1')
+ if mutation == 'kind':
+ epoch['stimuli']['Amp1'] = stream
+ if mutation == 'device':
+ epoch['responses']['Amp2'] = stream
+ with self.assertRaisesRegex(ValueError, 'stream.*(membership|identity|device|kind)'):
+ self.validate()
+
+ def test_read_model_independently_rejects_wrong_ownership(self):
+ self.cells()[0]['epoch_groups'][0]['uuid'] = uid(900)
+ stat = self.path.stat()
+ signature = (str(self.path), stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns, stat.st_ctime_ns)
+ source = {'source_sha256': workspace.digest(self.path), 'manifest': {
+ 'counts': {'cells': 2, 'epochs': 2}, 'validated_at': '2026-09-29'}}
+ with self.assertRaisesRegex(ValueError, 'group.*(identity|membership|source)'):
+ WorkspaceService.__new__(WorkspaceService)._read_source_metadata(source, self.doc, self.path, signature)
+
+ def signature(self):
+ stat = self.path.stat()
+ return (str(self.path), stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns, stat.st_ctime_ns)
+
+ def trace(self):
+ epoch = next(workspace.epochs(self.doc))[3]
+ stream = epoch['responses']['Amp1']
+ return read_response_window(self.path, self.signature(),
+ {'epoch_uuid': epoch['uuid'], 'source_sha256': workspace.digest(self.path)},
+ {'uuid': stream['uuid'], 'h5_path': stream['h5path'], 'sample_rate': 10000.,
+ 'sample_count': 4, 'units': 'pA'}, count=4)
+
+ def test_trace_rejects_external_links_in_any_path_component(self):
+ for placement in ('data', 'stream', 'epoch', 'soft_chain'):
+ with self.subTest(placement=placement):
+ self.doc = fixture(self.path)
+ stream_path = next(workspace.epochs(self.doc))[3]['responses']['Amp1']['h5path']
+ with h5py.File(self.path, 'r+') as h5:
+ if placement == 'data':
+ target = stream_path + '/data'
+ elif placement == 'epoch':
+ target = stream_path.rsplit('/', 2)[0]
+ elif placement == 'soft_chain':
+ h5['unsealed'] = h5py.ExternalLink('missing.h5', '/samples')
+ target = stream_path + '/data'
+ else:
+ target = stream_path
+ del h5[target]
+ h5[target] = (h5py.SoftLink('/unsealed') if placement == 'soft_chain'
+ else h5py.ExternalLink('missing.h5', '/samples'))
+ with self.assertRaisesRegex(ValueError, 'External H5 links are unsupported'):
+ self.trace()
+
+ def test_trace_rejects_virtual_and_external_storage(self):
+ for virtual in (True, False):
+ with self.subTest(virtual=virtual):
+ self.doc = fixture(self.path)
+ stream_path = next(workspace.epochs(self.doc))[3]['responses']['Amp1']['h5path']
+ with h5py.File(self.path, 'r+') as h5:
+ group = h5[stream_path]
+ del group['data']
+ if virtual:
+ layout = h5py.VirtualLayout((4,), dtype='f8')
+ layout[:] = h5py.VirtualSource('missing.h5', 'values', shape=(4,))
+ group.create_virtual_dataset('data', layout)
+ else:
+ group.create_dataset('data', (4,), dtype='f8', external=[('missing.raw', 0, 32)])
+ with self.assertRaisesRegex(ValueError, '(External|Virtual).*unsupported'):
+ self.trace()
+
+ def test_trace_allows_internal_soft_data_link_and_rejects_cycles(self):
+ stream_path = next(workspace.epochs(self.doc))[3]['responses']['Amp1']['h5path']
+ with h5py.File(self.path, 'r+') as h5:
+ h5['samples'] = h5[stream_path + '/data']
+ del h5[stream_path + '/data']
+ h5[stream_path + '/data'] = h5py.SoftLink('/samples')
+ self.assertEqual(self.trace()['values'], [0., 1., 2., 3.])
+ with h5py.File(self.path, 'r+') as h5:
+ del h5['samples']
+ h5['samples'] = h5py.SoftLink('/samples')
+ with self.assertRaisesRegex(ValueError, 'cyclic H5 soft link'):
+ self.trace()
+
+ def prepare(self):
+ repository = Path(__file__).resolve().parents[2] / '.rieke-runtime/retinanalysis'
+ if not (repository / 'src/retinanalysis/utils/parse_data.py').exists():
+ self.skipTest('Pinned RetinAnalysis parser is not installed')
+ _, parser_path = workspace.load_parser(repository)
+ sha = workspace.digest(self.path)
+ project = self.path.parent / 'project'
+ cache = project / 'imports' / (self.path.stem + '-' + sha[:12])
+ cache.mkdir(parents=True, exist_ok=True)
+ raw = cache / 'metadata.raw.json'
+ workspace.write_json(raw, self.doc)
+ workspace.write_json(cache / 'parse-manifest.json', {'status': 'parsed', 'sha256': sha,
+ 'metadata_sha256': workspace.digest(raw), 'parser_sha256': workspace.digest(parser_path)})
+ return workspace.prepare(self.path, project, repository)
+
+ def test_prepare_and_projection_retain_empty_cells_and_same_label_ownership(self):
+ with h5py.File(self.path, 'r+') as h5:
+ sources = h5['/experiment-' + uid(1) + '/sources/animal-' + uid(2) +
+ '/sources/preparation-' + uid(3) + '/sources']
+ sources.create_group('empty-cell-' + uid(40)).attrs['uuid'] = uid(40)
+ empty = copy.deepcopy(self.cells()[0])
+ empty.update(uuid=uid(40), epoch_groups=[])
+ self.cells().append(empty)
+ doc, rows, manifest, _ = self.prepare()
+ self.assertEqual(manifest['counts']['cells'], 3)
+ self.assertEqual(manifest['cell_count_semantics'], 'all-source-cells-v1')
+ self.assertEqual({r['cell_uuid'] for r in rows}, {uid(10), uid(20)})
+ projection = WorkspaceService.__new__(WorkspaceService)._read_source_metadata(
+ {'source_sha256': manifest['source_sha256'], 'manifest': manifest}, doc, self.path, self.signature())
+ self.assertEqual(set(projection['cells']), {uid(10), uid(20), uid(40)})
+ self.assertEqual(projection['rows'][uid(23)]['cell_uuid'], uid(20))
+
+ # Legacy manifests counted only cells encountered while iterating epochs.
+ legacy = copy.deepcopy(manifest)
+ legacy.pop('cell_count_semantics')
+ legacy['counts']['cells'] = 2
+ reopened = WorkspaceService.__new__(WorkspaceService)._read_source_metadata(
+ {'source_sha256': legacy['source_sha256'], 'manifest': legacy}, doc, self.path, self.signature())
+ self.assertEqual(set(reopened['cells']), {uid(10), uid(20), uid(40)})
+ self.assertEqual(reopened['rows'], projection['rows'])
+ # A convention mismatch must still fail, never guess either count.
+ for bad in (dict(legacy, cell_count_semantics='all-source-cells-v1'),
+ dict(manifest, cell_count_semantics='future-unknown'),
+ {key: value for key, value in manifest.items() if key != 'cell_count_semantics'}):
+ with self.subTest(bad=bad.get('cell_count_semantics')):
+ with self.assertRaisesRegex(ValueError, 'count|semantics'):
+ WorkspaceService.__new__(WorkspaceService)._read_source_metadata(
+ {'source_sha256': bad['source_sha256'], 'manifest': bad}, doc, self.path, self.signature())
+
+ def test_prepare_validates_parameters_on_streamless_epochs(self):
+ with h5py.File(self.path, 'r+') as h5:
+ params = h5['/experiment-' + uid(1) + '/epochGroups/group-' + uid(21) +
+ '/epochBlocks/block-' + uid(22) + '/epochs/epoch-' + uid(23) + '/protocolParameters']
+ params.attrs['amplitude'] = 17.
+ with self.assertRaisesRegex(ValueError, 'Source parameter mismatch'):
+ self.prepare()
+
+ def test_prepare_retains_empty_block_protocol_parameters_and_verified_metadata(self):
+ with h5py.File(self.path, 'r+') as h5:
+ block = h5['/experiment-' + uid(1) + '/epochGroups/group-' + uid(21) + '/epochBlocks/empty-' + uid(30)]
+ block.attrs['protocolID'] = 'example.EmptyProtocol'
+ block.create_group('protocolParameters').attrs['sampleRate'] = 10000.
+ original = workspace.digest(self.path)
+ doc, rows, manifest, folder = self.prepare()
+ blocks = doc['animals'][0]['preparations'][0]['cells'][1]['epoch_groups'][0]['epoch_blocks']
+ empty = next(block for block in blocks if block['uuid'] == uid(30))
+ self.assertEqual(empty['protocolID'], 'example.EmptyProtocol')
+ self.assertEqual(empty['parameters'], {'sampleRate': 10000.})
+ self.assertEqual(empty['epochs'], [])
+ self.assertEqual(len(rows), 2)
+ self.assertEqual(workspace.digest(self.path), original)
+ self.assertEqual(workspace.digest(folder / 'metadata.catalog.json'), manifest['metadata_sha256'])
+ self.assertTrue(any(warning['code'] == 'empty_epoch_block_restored' for warning in manifest['warnings']))
+ again = self.prepare()
+ self.assertEqual(again[0], doc)
+
+ def test_changed_group_source_link_to_other_existing_cell_rejected(self):
+ with h5py.File(self.path, 'r+') as h5:
+ groups = h5['/experiment-' + uid(1) + '/epochGroups']
+ first, second = groups['group-' + uid(11)], groups['group-' + uid(21)]
+ del first['source']
+ first['source'] = second['source']
+ with self.assertRaisesRegex(ValueError, 'group ownership.*source cell'):
+ self.validate()
+
+ def test_dependencies_rejected_before_reading_data(self):
+ for kind in ('external_link', 'external_storage', 'virtual', 'external_group'):
+ with self.subTest(kind=kind):
+ self.doc = fixture(self.path)
+ stream = next(workspace.epochs(self.doc))[3]['responses']['Amp1']
+ with h5py.File(self.path, 'r+') as h5:
+ obj = h5[stream['h5path']]
+ del obj['data']
+ if kind == 'external_link':
+ obj['data'] = h5py.ExternalLink('nonexistent.h5', '/values')
+ elif kind == 'external_group':
+ h5['unparsed_dependency'] = h5py.ExternalLink('nonexistent.h5', '/')
+ elif kind == 'external_storage':
+ obj.create_dataset('data', (4,), dtype='f8', external=[('nonexistent.raw', 0, 32)])
+ else:
+ layout = h5py.VirtualLayout((4,), dtype='f8')
+ layout[:] = h5py.VirtualSource('nonexistent.h5', 'values', shape=(4,))
+ obj.create_virtual_dataset('data', layout)
+ with self.assertRaisesRegex(ValueError, '(External|Virtual).*unsupported'):
+ self.validate()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_workspace_startup.py b/python/tests/test_workspace_startup.py
index 2eb010e..0b092b1 100644
--- a/python/tests/test_workspace_startup.py
+++ b/python/tests/test_workspace_startup.py
@@ -1,4 +1,5 @@
import json
+import os
from pathlib import Path
import tempfile
import unittest
@@ -16,6 +17,8 @@ def setUp(self):
self.temp=tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root=Path(self.temp.name)/'managed'
+ isolated_index=patch.dict(os.environ,{'RIEKE_PROJECT_INDEX':str(Path(self.temp.name)/'user-state/project-index.json')})
+ isolated_index.start();self.addCleanup(isolated_index.stop)
native_start=patch('workspace_native_mysql.ensure_native_database',side_effect=AssertionError('Unit startup tests must not start a native server'))
native_start.start();self.addCleanup(native_start.stop)
@@ -148,7 +151,8 @@ def test_open_route_passes_uuid_and_remains_separate_from_creation(self):
self.assertEqual(response.status_code,200)
self.assertEqual(response.get_json(),expected)
self.assertEqual(opened.call_args.args[1],project['uuid'])
- self.assertTrue(opened.call_args.kwargs['managed'])
+ self.assertEqual(opened.call_args.args[0], Path(project['path']))
+ self.assertNotIn('managed', opened.call_args.kwargs)
def _legacy_project(self,name):
"""Legacy tests explicitly retain Docker descriptors after the default changed."""
diff --git a/python/tests/test_workspace_startup_registry.py b/python/tests/test_workspace_startup_registry.py
new file mode 100644
index 0000000..fc46169
--- /dev/null
+++ b/python/tests/test_workspace_startup_registry.py
@@ -0,0 +1,161 @@
+"""Local discovery index has no dependency on an application or project root."""
+import concurrent.futures
+import json
+import os
+from pathlib import Path
+import tempfile
+import threading
+import unittest
+from unittest.mock import patch
+
+from workspace_projects import create_project, create_project_at, list_managed_projects
+from workspace_startup_registry import project_index_path, read_project_index, remember_project_path, remember_project_paths_order, remember_project_result
+
+
+class LocalProjectIndexTests(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp.cleanup)
+ self.base = Path(self.temp.name).resolve()
+ self.index = self.base / 'profile/preferences/project-index.json'
+ environment = patch.dict(os.environ, {'RIEKE_PROJECT_INDEX': str(self.index)})
+ environment.start()
+ self.addCleanup(environment.stop)
+
+ def test_empty_inventory_does_not_create_user_or_preferred_folders(self):
+ preferred = self.base / 'preferred'
+ self.assertEqual(read_project_index()['projects'], [])
+ self.assertEqual(list_managed_projects(preferred)['projects'], [])
+ self.assertFalse(self.index.parent.exists())
+ self.assertFalse(preferred.exists())
+
+ def test_created_registration_and_last_open_are_separate_without_parent_writes(self):
+ first = create_project_at(str(self.base / 'research/one'), 'Independent study')
+ second = create_project_at(str(self.base / 'research/two'), 'Independent study')
+ remember_project_path(first['path'], first['uuid'], set_last=True)
+ registered = remember_project_path(second['path'], second['uuid'])
+ self.assertEqual(registered['last_project_path'], first['path'])
+ self.assertEqual(len(registered['projects']), 2)
+ self.assertEqual(set(path.name for path in (self.base / 'research').iterdir()), {'one', 'two'})
+ self.assertNotIn('credentials', self.index.read_text())
+
+ def test_existing_exact_folder_does_not_require_listing_or_writing_its_parent(self):
+ parent = self.base / 'external-parent'
+ selected = parent / 'selected-project'
+ selected.mkdir(parents=True)
+ original = Path.iterdir
+ def prohibit_parent_inventory(path):
+ if path == parent:
+ raise PermissionError('The project parent is not an application workspace')
+ return original(path)
+ with patch.object(Path, 'iterdir', prohibit_parent_inventory):
+ project = create_project_at(str(selected), 'Selected folder')
+ self.assertEqual(project['path'], str(selected))
+ self.assertEqual(set(path.name for path in parent.iterdir()), {'selected-project'})
+ self.assertTrue((selected / 'project.json').is_file())
+
+ def test_atomic_concurrent_registration_preserves_both_project_references(self):
+ projects = [create_project_at(str(self.base / f'research/{index}'), f'Study {index}') for index in range(2)]
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
+ list(pool.map(lambda project: remember_project_path(project['path']), projects))
+ self.assertEqual({project['path'] for project in read_project_index()['projects']}, {project['path'] for project in projects})
+ self.assertFalse(list(self.index.parent.glob('.project-index-*')))
+
+ def test_managed_and_exact_creation_of_same_folder_share_one_lock(self):
+ from workspace_storage import initialize_layout
+ preferred = self.base / 'preferred'
+ selected = preferred / 'study'
+ first_entered, finish_first = threading.Event(), threading.Event()
+ second_started, second_finished = threading.Event(), threading.Event()
+ def paused_layout(*args):
+ if not first_entered.is_set():
+ first_entered.set()
+ if not finish_first.wait(5):
+ raise RuntimeError('Test did not release project creation')
+ return initialize_layout(*args)
+ def exact_attempt():
+ second_started.set()
+ try:
+ return create_project_at(str(selected), 'Exact study')
+ finally:
+ second_finished.set()
+ with patch('workspace_storage.initialize_layout', side_effect=paused_layout), concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
+ first = pool.submit(create_project, preferred, 'Managed study', directory='study')
+ try:
+ self.assertTrue(first_entered.wait(5))
+ second = pool.submit(exact_attempt)
+ self.assertTrue(second_started.wait(5))
+ self.assertFalse(second_finished.wait(.15), 'Exact route bypassed the managed route target lock')
+ finally:
+ finish_first.set()
+ created = first.result(5)
+ with self.assertRaisesRegex(ValueError, 'already contains files'):
+ second.result(5)
+ self.assertEqual(json.loads((selected / 'project.json').read_text())['project_uuid'], created['uuid'])
+
+ def test_invalid_index_or_duplicate_order_preserves_prior_file(self):
+ project = create_project_at(str(self.base / 'research/one'), 'Study')
+ remember_project_path(project['path'])
+ before = self.index.read_bytes()
+ with self.assertRaisesRegex(ValueError, 'duplicate'):
+ remember_project_paths_order([project['path'], project['path']])
+ self.assertEqual(self.index.read_bytes(), before)
+ for changes in ({'version': 2}, {'version': True}, {'last_project_path': '/not-registered'},
+ {'project_order': ['../relative']}, {'projects': [{'path': '/somewhere', 'password': 'no'}]}):
+ invalid = {**json.loads(before), **changes}
+ self.index.write_text(json.dumps(invalid))
+ with self.assertRaises((ValueError, TypeError)):
+ remember_project_path(project['path'])
+ self.assertEqual(json.loads(self.index.read_text()), invalid)
+
+ def test_completed_operation_survives_corrupt_profile_without_overwriting_evidence(self):
+ project = create_project_at(str(self.base / 'research/one'), 'Completed study')
+ self.index.write_text('corrupt profile evidence')
+ before_project = (Path(project['path']) / 'project.json').read_bytes()
+ completed = {'project_uuid': project['uuid'], 'directory': project['path'], 'verified': True,
+ 'url': 'http://127.0.0.1:8123/'}
+ preserved = remember_project_result(project['path'], completed, set_last=True)
+ self.assertEqual({key: preserved[key] for key in completed}, completed)
+ self.assertIn(project['path'], preserved['registry_warning'])
+ self.assertNotIn('registry_warning', completed)
+ self.assertEqual(self.index.read_text(), 'corrupt profile evidence')
+ self.assertEqual((Path(project['path']) / 'project.json').read_bytes(), before_project)
+
+ def test_completed_operation_reports_profile_write_failure_and_success_stays_unchanged(self):
+ project = create_project_at(str(self.base / 'research/one'), 'Completed study')
+ completed = {'directory': project['path'], 'moved': True}
+ with patch('workspace_startup_registry.remember_project_path', side_effect=OSError('Disk full')):
+ preserved = remember_project_result(project['path'], completed, previous_directory=self.base / 'old')
+ self.assertTrue(preserved['moved'])
+ self.assertIn('Disk full', preserved['registry_warning'])
+ self.assertIs(remember_project_result(project['path'], completed), completed)
+
+ def test_index_and_project_leaf_symlinks_are_rejected_without_following(self):
+ project = create_project_at(str(self.base / 'research/one'), 'Study')
+ alias = self.base / 'alias'
+ alias.symlink_to(project['path'], target_is_directory=True)
+ with self.assertRaisesRegex(ValueError, 'regular project'):
+ remember_project_path(alias)
+ outside = self.base / 'outside-index'
+ outside.write_text('keep this file')
+ self.index.symlink_to(outside)
+ with self.assertRaisesRegex(ValueError, 'symbolic'):
+ read_project_index()
+ self.assertEqual(outside.read_text(), 'keep this file')
+
+ def test_desktop_profile_and_system_alias_parent_resolve_consistently(self):
+ desktop = self.base / 'desktop-user'
+ with patch.dict(os.environ, {'RIEKE_PROJECT_INDEX': '', 'RIEKE_DESKTOP_USER_STATE': str(desktop)}):
+ self.assertEqual(project_index_path(), desktop / 'preferences/project-index.json')
+ actual = self.base / 'actual-profile'
+ actual.mkdir()
+ alias = self.base / 'profile-alias'
+ alias.symlink_to(actual, target_is_directory=True)
+ with patch.dict(os.environ, {'RIEKE_PROJECT_INDEX': str(alias / 'project-index.json')}):
+ self.assertEqual(project_index_path(), actual / 'project-index.json')
+ remember_project_paths_order([])
+ self.assertTrue((actual / 'project-index.json').is_file())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_workspace_state_generation.py b/python/tests/test_workspace_state_generation.py
new file mode 100644
index 0000000..842f506
--- /dev/null
+++ b/python/tests/test_workspace_state_generation.py
@@ -0,0 +1,289 @@
+"""Opt-in authority proofs against a newly created disposable native database."""
+import os
+import contextlib
+from pathlib import Path
+import tempfile
+import unittest
+import uuid
+import contextvars
+import threading
+from dataclasses import replace
+from types import SimpleNamespace
+
+from workspace_projects import create_project
+import workspace_native_mysql as native
+from workspace_state_generation import bootstrap,MANIFEST,SCHEMA,GENERATION,CHANGES,verify_export_triggers,StateGenerationAuthority
+
+
+class GenerationReadScopeTests(unittest.TestCase):
+ def setUp(self):
+ self.project,self.protocol,self.other=[str(uuid.uuid4()) for _ in range(3)]
+ self.connection=SimpleNamespace(_conn=object(),in_transaction=False)
+ self.authority=StateGenerationAuthority(self.connection,self.project)
+ self.authority.ready=True
+ self.contract_calls=[]
+ self.authority._contract=lambda:self.contract_calls.append(True) or 'verified authority'
+ self.authority._scope=lambda kind,identity:('verified epoch',1)
+
+ def test_response_contract_attests_twice_but_generations_stay_live(self):
+ del self.authority._contract
+ self.authority._attest_contract=lambda:self.contract_calls.append(True) or 'verified authority'
+ generation=[1]
+ self.authority._scope=lambda kind,identity:('verified epoch',generation[0])
+ with self.authority.response_contract():
+ first=self.authority.token(self.protocol)
+ generation[0]=2
+ second=self.authority.token(self.protocol)
+ self.assertNotEqual(first,second)
+ self.assertEqual(len(self.contract_calls),1)
+ escaped=contextvars.copy_context()
+ self.assertEqual(len(self.contract_calls),2)
+ escaped.run(self.authority.token,self.protocol)
+ self.assertEqual(len(self.contract_calls),4)
+
+ def test_response_contract_cannot_cross_threads_or_transactions(self):
+ del self.authority._contract
+ self.authority._attest_contract=lambda:self.contract_calls.append(True) or 'verified authority'
+ with self.authority.response_contract():
+ copied=contextvars.copy_context()
+ worker=threading.Thread(target=lambda:copied.run(self.authority.token,self.protocol))
+ worker.start();worker.join(5)
+ self.assertFalse(worker.is_alive())
+ self.assertEqual(len(self.contract_calls),3)
+ self.connection.in_transaction=True
+ self.authority._contract()
+ self.assertEqual(len(self.contract_calls),4)
+ self.connection.in_transaction=False
+ self.assertEqual(len(self.contract_calls),5)
+
+ def test_response_contract_rejects_closing_change_and_releases_lease_on_error(self):
+ del self.authority._contract
+ contract=['before']
+ self.authority._attest_contract=lambda:self.contract_calls.append(True) or contract[0]
+ with self.assertRaisesRegex(ValueError,'contract changed'):
+ with self.authority.response_contract():
+ self.authority.token(self.protocol)
+ contract[0]='after'
+ self.assertIsNone(self.authority._verified_read_token)
+ with self.assertRaisesRegex(RuntimeError,'interrupted'):
+ with self.authority.response_contract():raise RuntimeError('interrupted')
+ self.assertIsNone(self.authority._response_contract.get())
+
+ def test_scoped_shared_token_is_reused_but_other_protocol_and_final_reads_are_fresh(self):
+ token=self.authority.token(self.protocol)
+ self.assertEqual(len(self.contract_calls),2)
+ with self.authority.read_scope(token):
+ self.assertEqual(self.authority.token(self.protocol),token)
+ self.assertEqual(self.authority.token(),replace(token,protocol_uuid=None,protocol_epoch=None,protocol_generation=None))
+ self.assertEqual(len(self.contract_calls),2)
+ with self.assertRaisesRegex(ValueError,'nested'):
+ with self.authority.read_scope(token):pass
+ self.authority.token(self.other)
+ self.assertEqual(len(self.contract_calls),4)
+ self.authority.token(self.protocol)
+ self.assertEqual(len(self.contract_calls),6)
+
+ def test_scope_cannot_cross_threads_outlive_context_or_reuse_unverified_authority(self):
+ token=self.authority.token(self.protocol)
+ with self.authority.read_scope(token):
+ saved=contextvars.copy_context()
+ copied=contextvars.copy_context()
+ worker=threading.Thread(target=lambda:copied.run(self.authority.token,self.protocol))
+ worker.start();worker.join(5)
+ self.assertFalse(worker.is_alive())
+ self.assertEqual(len(self.contract_calls),4)
+ saved.run(self.authority.token,self.protocol)
+ self.assertEqual(len(self.contract_calls),6)
+ with self.assertRaisesRegex(ValueError,'different native authority'):
+ with self.authority.read_scope(replace(token,authority='unverified')):pass
+
+ def test_exception_and_connection_change_do_not_leave_a_reusable_generation(self):
+ token=self.authority.token(self.protocol)
+ with self.assertRaisesRegex(RuntimeError,'interrupted'):
+ with self.authority.read_scope(token):
+ self.connection._conn=object()
+ self.authority.token(self.protocol)
+ self.assertEqual(len(self.contract_calls),4)
+ raise RuntimeError('interrupted')
+ self.authority.token(self.protocol)
+ self.assertEqual(len(self.contract_calls),6)
+
+
+class NativeConnection:
+ def __init__(self,connection):self._conn=connection;self.in_transaction=False
+ @property
+ @contextlib.contextmanager
+ def transaction(self):
+ if self.in_transaction:raise RuntimeError('Nested test transaction')
+ self._conn.begin();self.in_transaction=True
+ try:
+ yield
+ self._conn.commit()
+ except Exception:
+ self._conn.rollback();raise
+ finally:self.in_transaction=False
+
+ def query(self,sql,args=(),as_dict=False,reconnect=False):
+ import pymysql
+ cursor=self._conn.cursor(pymysql.cursors.DictCursor if as_dict else pymysql.cursors.Cursor)
+ cursor.execute(sql,args)
+ return cursor
+
+
+@unittest.skipUnless(os.environ.get('RIEKE_TEST_NATIVE_MYSQL')=='1','opt-in isolated native authority proof')
+class NativeGenerationTests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ import pymysql
+ cls.temporary=tempfile.TemporaryDirectory(prefix='rieke-private-authority-test-')
+ cls.root=Path(create_project(Path(cls.temporary.name)/'projects','Disposable authority fixture')['path'])
+ native.ensure_native_database(cls.root)
+ cls.connection=NativeConnection(pymysql.connect(**native.connection_parameters(cls.root),autocommit=True))
+ cls.connection.query('CREATE DATABASE recording_workspace')
+ for sql in (
+ 'CREATE TABLE recording_workspace.curation (project_uuid varchar(36),protocol_uuid varchar(36),epoch_uuid varchar(36),tags JSON,revision int, PRIMARY KEY(project_uuid,protocol_uuid,epoch_uuid)) ENGINE=InnoDB',
+ 'CREATE TABLE recording_workspace.shared_annotation (project_uuid varchar(36),target_kind varchar(16),target_uuid varchar(36),profile_uuid varchar(36),tags JSON,revision int,PRIMARY KEY(project_uuid,target_kind,target_uuid,profile_uuid)) ENGINE=InnoDB',
+ 'CREATE TABLE recording_workspace.annotation_profile (project_uuid varchar(36),profile_uuid varchar(36),display_name varchar(120),PRIMARY KEY(project_uuid,profile_uuid)) ENGINE=InnoDB'):
+ cls.connection.query(sql)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.connection._conn.close();native.stop_native_database(cls.root);cls.temporary.cleanup()
+
+ def setUp(self):
+ self.project,self.protocol,self.epoch,self.profile=[str(uuid.uuid4()) for _ in range(4)]
+ self.authority=bootstrap(self.connection,self.project)
+ self.assertTrue(self.authority.ready,self.authority.reason)
+
+ def token(self):
+ token=self.authority.token(self.protocol)
+ self.assertIsNotNone(token,self.authority.reason)
+ return token
+
+ def test_response_contract_fences_ddl_and_disabled_observers_at_exit(self):
+ with self.assertRaisesRegex(ValueError,'contract changed'):
+ try:
+ with self.authority.response_contract():
+ self.assertIsNotNone(self.authority.token(self.protocol))
+ self.connection.query('CREATE TABLE recording_workspace.response_contract_probe (id INT PRIMARY KEY) ENGINE=InnoDB')
+ finally:
+ self.connection.query('DROP TABLE IF EXISTS recording_workspace.response_contract_probe')
+ try:
+ with self.assertRaisesRegex(ValueError,'disabled'):
+ with self.authority.response_contract():
+ self.assertIsNotNone(self.authority.token(self.protocol))
+ self.connection.query("UPDATE performance_schema.setup_consumers SET ENABLED='NO' WHERE NAME='events_statements_current'")
+ finally:
+ self.connection.query("UPDATE performance_schema.setup_consumers SET ENABLED='YES' WHERE NAME='events_statements_current'")
+ self.assertIsNotNone(self.authority.token(self.protocol))
+
+ def test_read_scope_defers_shared_rechecks_but_fresh_exit_observes_external_commit(self):
+ import pymysql
+ writer=NativeConnection(pymysql.connect(**native.connection_parameters(self.root),autocommit=True))
+ try:
+ before=self.token()
+ with self.authority.read_scope(before):
+ writer.query('INSERT INTO recording_workspace.shared_annotation VALUES (%s,%s,%s,%s,%s,1)',
+ (self.project,'epoch',self.epoch,self.profile,'["external write during read"]'))
+ self.assertEqual(self.authority.token(self.protocol),before)
+ self.assertEqual(self.authority.token(),replace(before,protocol_uuid=None,protocol_epoch=None,protocol_generation=None))
+ after=self.token()
+ self.assertGreater(after.shared_generation,before.shared_generation)
+ with self.authority.read_scope(after):
+ self.assertEqual(self.authority.token(self.protocol),after)
+ self.assertEqual(self.token(),after)
+ with self.authority.read_scope(after):
+ writer._conn.begin()
+ writer.query('UPDATE recording_workspace.shared_annotation SET tags=%s WHERE project_uuid=%s',
+ ('["rolled back"]',self.project))
+ self.assertEqual(self.authority.token(self.protocol),after)
+ writer._conn.rollback()
+ self.assertEqual(self.token(),after)
+ finally:
+ writer._conn.rollback();writer._conn.close()
+
+ def test_same_revision_dml_moves_rollback_replace_and_feed(self):
+ before=self.token()
+ self.connection.query('INSERT INTO recording_workspace.curation VALUES (%s,%s,%s,%s,1)',
+ (self.project,self.protocol,self.epoch,'["one"]'))
+ inserted=self.token();self.assertGreater(inserted.protocol_generation,before.protocol_generation)
+ self.connection.query('UPDATE recording_workspace.curation SET tags=%s WHERE project_uuid=%s',('["two"]',self.project))
+ changed=self.token();self.assertGreater(changed.protocol_generation,inserted.protocol_generation)
+ self.connection._conn.begin()
+ self.connection.query('DELETE FROM recording_workspace.curation WHERE project_uuid=%s',(self.project,))
+ self.connection._conn.rollback();self.assertEqual(self.token(),changed)
+ moved=str(uuid.uuid4())
+ self.connection.query('UPDATE recording_workspace.curation SET protocol_uuid=%s WHERE project_uuid=%s',(moved,self.project))
+ self.assertGreater(self.token().protocol_generation,changed.protocol_generation)
+ self.assertGreater(self.authority.token(moved).protocol_generation,0)
+ shared=self.authority.token()
+ self.connection.query('INSERT INTO recording_workspace.shared_annotation VALUES (%s,%s,%s,%s,%s,1)',
+ (self.project,'epoch',self.epoch,self.profile,'["shared"]'))
+ changes,after=self.authority.shared_changes(shared)
+ self.assertEqual(len(changes),1);self.assertEqual(changes[0]['deleted'],0)
+ self.connection.query('REPLACE INTO recording_workspace.shared_annotation VALUES (%s,%s,%s,%s,%s,1)',
+ (self.project,'epoch',self.epoch,self.profile,'["replaced"]'))
+ changes,replaced=self.authority.shared_changes(after)
+ self.assertEqual(len(changes),1);self.assertEqual(changes[0]['deleted'],0)
+ self.assertGreater(replaced.shared_generation,after.shared_generation)
+ self.connection.query('DELETE FROM recording_workspace.shared_annotation WHERE project_uuid=%s',(self.project,))
+ changes,deleted=self.authority.shared_changes(replaced)
+ self.assertEqual(changes[0]['deleted'],1)
+ self.connection.query('INSERT INTO recording_workspace.annotation_profile VALUES (%s,%s,%s)',(self.project,self.profile,'author'))
+ changes,_=self.authority.shared_changes(deleted)
+ self.assertEqual(changes[0]['target_kind'],'profile')
+
+ def test_disabled_witnesses_invalidate_and_transaction_lock_checks_reject_stale_tokens(self):
+ before=self.token()
+ try:
+ self.connection.query("UPDATE performance_schema.setup_consumers SET ENABLED='NO' WHERE NAME='events_statements_current'")
+ self.assertIsNone(self.authority.token(self.protocol))
+ finally:
+ self.connection.query("UPDATE performance_schema.setup_consumers SET ENABLED='YES' WHERE NAME='events_statements_current'")
+ after=self.token();self.assertNotEqual(before.authority,after.authority)
+ try:
+ self.connection.query("UPDATE performance_schema.setup_instruments SET ENABLED='NO' WHERE NAME='statement/com/Execute'")
+ self.assertIsNone(self.authority.token(self.protocol))
+ finally:
+ self.connection.query("UPDATE performance_schema.setup_instruments SET ENABLED='YES' WHERE NAME='statement/com/Execute'")
+ self.connection.query('INSERT INTO recording_workspace.curation VALUES (%s,%s,%s,%s,1)',
+ (self.project,self.protocol,self.epoch,'["before"]'))
+ before=self.token()
+ self.connection._conn.begin();self.connection.in_transaction=True
+ try:
+ self.authority.assert_current_locked(before)
+ self.connection.query('UPDATE recording_workspace.curation SET tags=%s WHERE project_uuid=%s',('["after"]',self.project))
+ self.connection._conn.commit()
+ finally:
+ self.connection._conn.rollback();self.connection.in_transaction=False
+ self.assertNotEqual(before,self.token())
+ from workspace_curation import RevisionConflict
+ self.connection._conn.begin();self.connection.in_transaction=True
+ try:
+ with self.assertRaises(RevisionConflict):self.authority.assert_current_locked(before)
+ finally:
+ self.connection._conn.rollback();self.connection.in_transaction=False
+
+ def test_trigger_gap_missing_row_truncation_and_export_contract(self):
+ before=self.token()
+ name='rieke_state_v1_curation_au';trigger=MANIFEST[name]
+ self.connection.query(f'DROP TRIGGER recording_workspace.{name}')
+ self.assertIsNone(self.authority.token(self.protocol))
+ self.connection.query(f'CREATE TRIGGER recording_workspace.{name} AFTER UPDATE ON recording_workspace.curation FOR EACH ROW '+trigger['body'])
+ self.assertNotEqual(before.authority,self.token().authority)
+ before=self.token()
+ self.connection.query(f'DELETE FROM recording_workspace.{GENERATION} WHERE project_uuid=%s',(self.project,))
+ self.assertNotEqual(before.protocol_epoch,self.token().protocol_epoch)
+ before=self.token()
+ self.connection.query('TRUNCATE TABLE recording_workspace.curation')
+ self.assertNotEqual(before.authority,self.token().authority)
+ before=self.token()
+ self.connection.query(f'TRUNCATE TABLE recording_workspace.{CHANGES}')
+ self.assertNotEqual(before.authority,self.token().authority)
+ self.assertTrue(verify_export_triggers(self.connection,[SCHEMA])['safe_to_omit'])
+ self.connection.query('CREATE TRIGGER recording_workspace.user_owned AFTER INSERT ON recording_workspace.curation FOR EACH ROW SET @user_test=1')
+ self.assertFalse(verify_export_triggers(self.connection,[SCHEMA])['safe_to_omit'])
+ self.connection.query('DROP TRIGGER recording_workspace.user_owned')
+
+
+if __name__=='__main__':unittest.main()
diff --git a/python/tests/test_workspace_updates.py b/python/tests/test_workspace_updates.py
index 451fe71..f052805 100644
--- a/python/tests/test_workspace_updates.py
+++ b/python/tests/test_workspace_updates.py
@@ -74,6 +74,27 @@ def test_offline_malformed_and_prerelease_checks_fail_without_mutation(self):
self.assertEqual(updates.check_for_updates(self.root, force=True)['state'], 'error')
self.assertFalse((self.installation / 'active.json').exists())
+ def test_known_update_survives_failed_refresh_but_not_release_withdrawal(self):
+ with patch.object(updates, '_download', return_value=json.dumps(self.release()).encode()):
+ available = updates.check_for_updates(self.root)
+ for failure in (OSError('offline'), HTTPError(updates.API, 503, '', {}, None)):
+ with patch.object(updates, '_download', side_effect=failure):
+ result = updates.check_for_updates(self.root, force=True)
+ self.assertEqual(result['state'], 'update_available')
+ self.assertEqual(result['available'], '0.2.0')
+ self.assertEqual(result['release_url'], available['release_url'])
+ self.assertEqual(result['checked_at'], available['checked_at'])
+ self.assertIn('check_error', result)
+ self.assertEqual(updates.check_for_updates(self.root), result)
+ with patch.object(updates, '_download', return_value=json.dumps(self.release('0.1.0')).encode()):
+ result = updates.check_for_updates(self.root, force=True)
+ self.assertEqual(result['state'], 'up_to_date')
+ self.assertNotIn('check_error', result)
+ with patch.object(updates, '_download', return_value=json.dumps(self.release()).encode()):
+ updates.check_for_updates(self.root, force=True)
+ with patch.object(updates, '_download', side_effect=HTTPError(updates.API, 404, '', {}, None)):
+ self.assertEqual(updates.check_for_updates(self.root, force=True)['state'], 'unavailable')
+
def test_managed_status_requires_ready_release_inside_managed_installation(self):
release = self.ready('0.1.0')
(release / updates.TRUST_KEY).write_text('trusted public key')
@@ -122,6 +143,41 @@ def test_activation_switches_atomically_and_keeps_previous_release(self):
updates.activate_staged(self.installation)
self.assertEqual(updates._read(self.installation / 'active.json'), {'version': '0.2.0', 'previous': '0.1.0'})
self.assertTrue((self.installation / 'releases/0.1.0').exists())
+ self.assertFalse((self.installation / 'staged.json').exists())
+
+ def test_real_manager_launch_applies_prepared_update_and_preserves_preferences(self):
+ for version in ('0.1.0', '0.2.0'):
+ release = self.ready(version)
+ (release / 'rieke.py').write_text(f'import sys, json; print(json.dumps({{"version":{version!r},"args":sys.argv[1:]}}))')
+ self.write(self.installation / 'active.json', {'version': '0.1.0'})
+ self.write(self.installation / 'staged.json', {'version': '0.2.0'})
+ self.write(self.installation / 'preferences/workspace-selection.json', {'managed_root': '/research'})
+ command = [updates.sys.executable, str(Path(updates.__file__)), '--installation',
+ str(self.installation), 'launch', '--', '--port', '8910']
+ # Another open project defers activation and still launches the old app.
+ with updates.installation_lock(self.installation):
+ held = subprocess.run(command, check=True, capture_output=True, text=True)
+ self.assertEqual(json.loads(held.stdout)['version'], '0.1.0')
+ self.assertIn('Update deferred', held.stderr)
+ launched = subprocess.run(command, check=True, capture_output=True, text=True)
+ self.assertEqual(json.loads(launched.stdout), {'version': '0.2.0', 'args': ['launch', '--port', '8910']})
+ self.assertEqual(updates._read(self.installation / 'active.json'), {'version': '0.2.0', 'previous': '0.1.0'})
+ self.assertEqual(updates._read(self.installation / 'preferences/workspace-selection.json'), {'managed_root': '/research'})
+ self.assertTrue((self.installation / 'releases/0.1.0').exists())
+ # Subsequent launches use the new release without reapplying its receipt.
+ self.assertEqual(json.loads(subprocess.check_output(command, text=True))['version'], '0.2.0')
+
+ def test_manager_defers_incompatible_update_and_refuses_stale_staged_version(self):
+ self.ready('0.1.0')
+ self.ready('0.2.0', compatibility=2)
+ self.write(self.installation / 'active.json', {'version': '0.1.0'})
+ self.write(self.installation / 'staged.json', {'version': '0.2.0'})
+ with patch.object(updates.subprocess, 'call', return_value=0) as launch:
+ self.assertEqual(updates.main(['--installation', str(self.installation), 'launch']), 0)
+ self.assertIn('0.1.0/rieke.py', launch.call_args.args[0][1])
+ self.write(self.installation / 'staged.json', {'version': '0.1.0'})
+ with self.assertRaisesRegex(ValueError, 'newer'):
+ updates.activate_staged(self.installation)
@unittest.skipUnless(shutil.which('openssl'), 'OpenSSL signature verification unavailable')
def test_real_signature_accepts_trusted_payload_rejects_tampering(self):
@@ -180,6 +236,41 @@ def test_wrong_checksum_cannot_execute_setup_or_create_release(self):
execute.assert_not_called()
self.assertFalse((self.installation / 'releases').exists())
+ def test_multiple_services_reuse_only_matching_verified_staged_release(self):
+ release_dir = self.ready('0.2.0')
+ prefix = f'https://github.com/{updates.REPOSITORY}/releases/download/v0.2.0/'
+ release = {**self.release(), 'assets': [{'name': updates.MANIFEST_ASSET, 'browser_download_url': prefix + updates.MANIFEST_ASSET}]}
+ artifact = {'platform': f'{updates.sys.platform}-{updates.platform.machine().lower()}',
+ 'url': prefix + 'app.tar.gz', 'size': 3, 'sha256': 'a' * 64}
+ manifest = {'version': '0.2.0', 'commit': 'b' * 40, 'database_compatibility': 1, 'artifacts': [artifact]}
+ self.write(release_dir / '.release-ready.json', {'version': '0.2.0', 'commit': 'b' * 40,
+ 'database_compatibility': 1, 'sha256': 'a' * 64})
+ with patch.object(updates, '_download', side_effect=[json.dumps(release).encode(), b'envelope']) as download, \
+ patch.object(updates, 'verify_manifest', return_value=manifest), patch.object(updates.subprocess, 'run') as setup:
+ self.assertEqual(updates.stage_release(self.installation)['version'], '0.2.0')
+ self.assertEqual(download.call_count, 2)
+ setup.assert_not_called()
+ self.write(release_dir / '.release-ready.json', {'version': '0.2.0'})
+ with patch.object(updates, '_download', side_effect=[json.dumps(release).encode(), b'envelope']), \
+ patch.object(updates, 'verify_manifest', return_value=manifest):
+ with self.assertRaisesRegex(ValueError, 'inspect'):
+ updates.stage_release(self.installation)
+
+ def test_incompatible_signed_release_is_rejected_before_artifact_download(self):
+ self.ready('0.1.0')
+ self.write(self.installation / 'active.json', {'version': '0.1.0'})
+ prefix = f'https://github.com/{updates.REPOSITORY}/releases/download/v0.2.0/'
+ release = {**self.release(), 'assets': [{'name': updates.MANIFEST_ASSET, 'browser_download_url': prefix + updates.MANIFEST_ASSET}]}
+ manifest = {'version': '0.2.0', 'database_compatibility': 2,
+ 'artifacts': [{'platform': f'{updates.sys.platform}-{updates.platform.machine().lower()}',
+ 'url': prefix + 'app.tar.gz', 'size': 3}]}
+ with patch.object(updates, '_download', side_effect=[json.dumps(release).encode(), b'envelope']) as download, \
+ patch.object(updates, 'verify_manifest', return_value=manifest), patch.object(updates.subprocess, 'run') as setup:
+ with self.assertRaisesRegex(ValueError, 'migration'):
+ updates.stage_release(self.installation)
+ self.assertEqual(download.call_count, 2)
+ setup.assert_not_called()
+
def test_managed_server_rejects_old_release_even_with_same_project_uuid(self):
from workspace_project_servers import write_server_record, ready_url
project = Path(self.temp.name) / 'project'
diff --git a/python/workspace_annotation_checkpoint.py b/python/workspace_annotation_checkpoint.py
new file mode 100644
index 0000000..2508376
--- /dev/null
+++ b/python/workspace_annotation_checkpoint.py
@@ -0,0 +1,199 @@
+"""Sealed shared-tag checkpoints, reusable only after fresh canonical proof.
+
+Working indexes remain private and disposable. Immutable checkpoints are written
+at preparation/clean-close boundaries, never for an interactive annotation save.
+An old server counter or witness nonce is insufficient: reuse requires the fresh
+recovery mirror's table-content seals, source identities and this code contract.
+"""
+from __future__ import annotations
+
+import contextlib
+import copy
+import fcntl
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import sqlite3
+import tempfile
+
+from workspace_annotation_preparation import _ordered_rows,_links
+from workspace_recipes import checksum
+from workspace_shared_tag_index import SharedTagIndex,SCOPE_CACHE_ROWS
+
+FORMAT='rieke-shared-tag-checkpoint'
+VERSION=1
+SUMMARY_TABLES=('summary_cells','summary_cell_tags','summary_tags','summary_totals')
+HEX=re.compile(r'^[0-9a-f]{64}$')
+
+
+def _contract():
+ import workspace_annotations,workspace_shared_tag_index,workspace_shared_vocabulary
+ import workspace_annotation_preparation
+ paths=[Path(module.__file__) for module in (workspace_annotations,workspace_shared_tag_index,
+ workspace_shared_vocabulary,workspace_annotation_preparation)]+[Path(__file__)]
+ return checksum({path.name:_sha(path) for path in paths})
+
+
+def _sha(path):
+ with Path(path).open('rb') as stream:return hashlib.file_digest(stream,'sha256').hexdigest()
+
+
+def _signature(path):
+ stat=Path(path).stat()
+ return stat.st_dev,stat.st_ino,stat.st_size,stat.st_mtime_ns,stat.st_ctime_ns
+
+
+def _regular(path):
+ path=Path(path)
+ if any(parent.is_symlink() for parent in (path,*path.parents)):
+ raise ValueError('Annotation checkpoints cannot follow symbolic links')
+ if path.exists() and not path.is_file():raise ValueError('Expected a regular annotation checkpoint file')
+
+
+@contextlib.contextmanager
+def _locked(root):
+ folder=Path(root)/'cache'/'annotation-index'
+ for path in (folder.parent,folder):
+ if path.is_symlink():raise ValueError('Annotation checkpoint directories cannot be symbolic links')
+ path.mkdir(exist_ok=True)
+ handle=os.fdopen(os.open(folder/'.checkpoint.lock',os.O_CREAT|os.O_RDWR|getattr(os,'O_NOFOLLOW',0),0o600),'a+b')
+ try:
+ fcntl.flock(handle,fcntl.LOCK_EX)
+ yield folder
+ finally:handle.close()
+
+
+def _sync_directory(folder):
+ descriptor=os.open(folder,os.O_RDONLY)
+ try:os.fsync(descriptor)
+ finally:os.close(descriptor)
+
+
+def _proof(service):
+ from workspace_recovery_store import verified_table_content
+ tracker=getattr(service,'_recovery_tracker',None)
+ return verified_table_content(service.project_dir,tracker) if tracker is not None else None
+
+
+def _source(service):
+ service._ready()
+ index=getattr(service,'disk_index',None)
+ if index is None:raise ValueError('A sealed metadata index is required')
+ index._check()
+ return {'project_uuid':service.project['project_uuid'],'metadata_generation':index.generation,
+ 'links_sha256':checksum(_links(_ordered_rows(service))),'contract':_contract()}
+
+
+def _same_source(service,expected):
+ return _source(service)==expected
+
+
+def _metadata(folder):
+ pointer=folder/'current.json';_regular(pointer)
+ if not pointer.exists():return None
+ if pointer.stat().st_size>65536:raise ValueError('Annotation checkpoint receipt is oversized')
+ record=json.loads(pointer.read_text())
+ if (not isinstance(record,dict) or set(record)!={'format','version','source','proof','sha256','file'}
+ or record['format']!=FORMAT or type(record['version']) is not int or record['version']!=VERSION
+ or not isinstance(record['sha256'],str) or not HEX.fullmatch(record['sha256'])
+ or record['file']!=record['sha256']+'.sqlite'):
+ raise ValueError('Unsupported annotation checkpoint receipt')
+ return record
+
+
+def restore_shared_checkpoint(service,shared):
+ """Copy a verified immutable checkpoint into a fresh private working DB."""
+ result={'restored':False}
+ index=None
+ try:
+ previous=getattr(shared,'_shared_tag_index',None)
+ if previous is not None and previous.connection is not None:
+ return {**result,'reason':'A working shared index is already open'}
+ source=_source(service);before=shared.generation_token();proof=_proof(service)
+ if before is None or proof is None:
+ return {**result,'reason':'Fresh canonical table-content proof is unavailable'}
+ with _locked(service.project_dir) as folder:
+ record=_metadata(folder)
+ if record is None:return {**result,'reason':'No shared-tag checkpoint is saved'}
+ if record['source']!=source or record['proof']!=proof:
+ return {**result,'reason':'Canonical tags, source identities or index code changed'}
+ path=folder/record['file'];_regular(path);signature=_signature(path)
+ if _sha(path)!=record['sha256']:raise ValueError('Shared-tag checkpoint checksum differs')
+ index=shared._membership_index([],refresh=False)
+ if type(index) is not SharedTagIndex:raise ValueError('Unsupported shared-tag index adapter')
+ db=index._open()
+ db.create_function('shared_casefold',1,str.casefold,deterministic=True)
+ with contextlib.closing(sqlite3.connect(path.absolute().as_uri()+'?mode=ro',uri=True)) as saved:
+ saved.backup(db)
+ with db:
+ for table in SUMMARY_TABLES:
+ db.execute(f'INSERT INTO temp.{table} SELECT * FROM main.checkpoint_{table}')
+ db.execute(f'DROP TABLE main.checkpoint_{table}')
+ for kind in ('filter','summary'):
+ links=tuple(db.execute(f'SELECT epoch_uuid,cell_uuid FROM {kind}_scope ORDER BY position'))
+ index.scopes[kind]=links if len(links)<=SCOPE_CACHE_ROWS else None
+ from workspace_shared_vocabulary import SharedVocabulary
+ vocabulary=SharedVocabulary.__new__(SharedVocabulary)
+ vocabulary.index=index
+ vocabulary.sequence=db.execute('SELECT COALESCE(MAX(used),0) FROM shared_vocabulary_ready').fetchone()[0]
+ index.shared_vocabulary=vocabulary
+ index.token=copy.deepcopy(before)
+ index.stats['rows_indexed']=db.execute('SELECT COUNT(*) FROM records').fetchone()[0]
+ if (_signature(path)!=signature or not _same_source(service,source)
+ or shared.generation_token()!=before or _proof(service)!=proof):
+ raise ValueError('Canonical state changed while reopening the shared-tag checkpoint')
+ return {'restored':True,'rows_indexed':index.stats['rows_indexed'],'sha256':record['sha256']}
+ except (OSError,ValueError,KeyError,TypeError,sqlite3.Error) as error:
+ if index is not None:
+ with contextlib.suppress(Exception):index.close()
+ return {**result,'reason':str(error)}
+
+
+def save_shared_checkpoint(service,shared):
+ """Seal a prepared index after the caller has durably captured SQL state."""
+ index=getattr(shared,'_shared_tag_index',None)
+ if type(index) is not SharedTagIndex or index.connection is None:
+ return {'saved':False,'reason':'No prepared shared-tag index'}
+ temporary=None
+ with index._lock:
+ source=_source(service);before=shared.generation_token();proof=_proof(service)
+ if before is None or proof is None or before!=index.token:
+ return {'saved':False,'reason':'Prepared index does not match a fresh canonical checkpoint'}
+ links=_links(_ordered_rows(service))
+ if index.scopes['filter']!=links or index.scopes['summary']!=links or index.shared_vocabulary is None:
+ return {'saved':False,'reason':'Shared filter, summary and vocabulary preparation is incomplete'}
+ if index.connection.in_transaction:raise ValueError('Annotation checkpoint cannot interrupt an index transaction')
+ with _locked(service.project_dir) as folder:
+ try:
+ descriptor,name=tempfile.mkstemp(prefix='.checkpoint-',suffix='.sqlite',dir=folder)
+ os.close(descriptor);temporary=Path(name)
+ with contextlib.closing(sqlite3.connect(temporary)) as saved:
+ index.connection.backup(saved)
+ attached=False
+ try:
+ index.connection.execute('ATTACH DATABASE ? AS annotation_checkpoint',(str(temporary),));attached=True
+ with index.connection:
+ for table in SUMMARY_TABLES:
+ index.connection.execute(f'CREATE TABLE annotation_checkpoint.checkpoint_{table} AS SELECT * FROM temp.{table}')
+ index.connection.execute('DELETE FROM annotation_checkpoint.catalog_scope')
+ finally:
+ if attached:index.connection.execute('DETACH DATABASE annotation_checkpoint')
+ with temporary.open('rb') as stream:os.fsync(stream.fileno())
+ sha=_sha(temporary)
+ if (not _same_source(service,source) or shared.generation_token()!=before or _proof(service)!=proof):
+ return {'saved':False,'reason':'Canonical state changed while sealing the shared-tag checkpoint'}
+ target=folder/(sha+'.sqlite');_regular(target)
+ os.replace(temporary,target);temporary=None
+ _sync_directory(folder)
+ from recording_workspace import write_json
+ write_json(folder/'current.json',{'format':FORMAT,'version':VERSION,'source':source,
+ 'proof':proof,'sha256':sha,'file':target.name})
+ for old in folder.glob('*.sqlite'):
+ if old!=target and HEX.fullmatch(old.stem) and not old.is_symlink():old.unlink()
+ _sync_directory(folder)
+ return {'saved':True,'rows_indexed':index.stats['rows_indexed'],'sha256':sha}
+ finally:
+ if temporary is not None:
+ with contextlib.suppress(FileNotFoundError):temporary.unlink()
diff --git a/python/workspace_annotation_preparation.py b/python/workspace_annotation_preparation.py
new file mode 100644
index 0000000..04a1732
--- /dev/null
+++ b/python/workspace_annotation_preparation.py
@@ -0,0 +1,185 @@
+"""Prepare discardable tag indexes during project/import preparation.
+
+Canonical SQL records are never rewritten here. Cell annotations remain one
+record per author and cell; the prepared index derives inheritance through the
+source's exact epoch-to-cell links. Unsupported authority contracts retain the
+ordinary canonical readers instead of declaring an unverified cache ready.
+"""
+from __future__ import annotations
+
+import contextlib
+import time
+
+from workspace_annotations import SharedAnnotations
+from workspace_shared_tag_index import SharedTagsChanged
+
+
+def _ordered_rows(service):
+ return sorted(service.rows.values(),key=lambda row:(row['date'],row['start_time'],row['epoch_uuid']))
+
+
+def _links(rows):
+ return tuple((row['epoch_uuid'],row['cell_uuid']) for row in rows)
+
+
+def prepare_annotation_indexes(service,store,shared,*,protocol_state=None,progress=None):
+ """Warm authoritative projections; caller owns the workspace database lock.
+
+ This runs after a verified startup checkpoint or successful metadata import,
+ before readiness is published. Failure discards derived work and reports the
+ failed phase; callers may surface a preparation warning and retry safely.
+ """
+ started=time.perf_counter()
+ report={'status':'preparing','phases':{},'project_uuid':service.project['project_uuid']}
+ service.annotation_preparation=report
+ shared_index=None;curation_index=None
+ def phase(name,operation):
+ report['phase']=name
+ if progress:progress(name)
+ before=time.perf_counter();result=operation()
+ report['phases'][name]={'seconds':round(time.perf_counter()-before,6)}
+ return result
+ try:
+ service._ready()
+ metadata=getattr(service,'disk_index',None)
+ if metadata is None or type(shared) is not SharedAnnotations:
+ report.update(status='unavailable',reason='Verified source metadata and native shared annotations are required')
+ return report
+ metadata._check();generation=metadata.generation
+ before=shared.generation_token()
+ if before is None:
+ report.update(status='unavailable',reason='Native annotation generation authority is unavailable')
+ return report
+ rows=_ordered_rows(service);links=_links(rows)
+ report.update(epochs=len(rows),cells=len(service.cells),metadata_generation=generation)
+ shared_index=phase('indexing_shared_annotations',lambda:shared._membership_index(rows))
+ if shared_index is None:
+ report.update(status='unavailable',reason='Native annotation generation authority could not be verified')
+ return report
+ phase('preparing_tag_filters',lambda:shared_index.matching(rows,{}))
+ phase('preparing_tag_summary',lambda:shared_index.summary(rows))
+ phase('preparing_tag_suggestions',lambda:shared_index.suggestions('',30))
+ report['shared_index']=dict(shared_index.stats)
+
+ recovery=getattr(service,'_recovery_tracker',None)
+ if recovery is not None and recovery.ready:
+ store.recovery_tracker=recovery
+ phase('indexing_dataset_tags',lambda:store.tag_suggestions('',30))
+ curation_index=getattr(store,'_curation_vocabulary_index',None)
+ if curation_index is not None:report['dataset_index']=dict(curation_index.stats)
+ if protocol_state is not None:
+ from workspace_protocol_state import STATE_CACHE_SCOPES
+ protocols=list(service.protocols)[:STATE_CACHE_SCOPES]
+ def prepare_protocols():
+ return sum(protocol_state.native_context(identity) is not None for identity in protocols)
+ report['protocols_prepared']=phase('preparing_protocol_reads',prepare_protocols)
+ report['protocols_deferred']=max(0,len(service.protocols)-len(protocols))
+
+ metadata._check()
+ if (service.disk_index is not metadata or metadata.generation!=generation
+ or service.project['project_uuid']!=report['project_uuid'] or _links(_ordered_rows(service))!=links):
+ raise ValueError('Source epoch/cell identities changed during annotation preparation')
+ if shared.generation_token()!=before:
+ raise SharedTagsChanged('Shared annotations changed during preparation; retry before using the prepared indexes')
+ report.update(status='ready',phase='complete')
+ return report
+ except BaseException as error:
+ for index in (shared_index,curation_index):
+ if index is not None:
+ with contextlib.suppress(Exception):index.close()
+ report.update(status='failed',reason=str(error))
+ raise
+ finally:
+ report['seconds']=round(time.perf_counter()-started,6)
+
+
+def prepare_project_annotations(service,store,shared,*,protocol_state=None,reuse=False,progress=None):
+ """Prepare the persistent SQL lookup at lifecycle boundaries, not each edit.
+
+ No SQLite membership/vocabulary snapshot is opened for ordinary native tag
+ filtering. A fresh canonical content proof permits unchanged reopen reuse.
+ """
+ from workspace_native_tag_lookup import bootstrap
+ from workspace_recovery_store import verified_table_content
+ from workspace_state_snapshot import save as save_app_state
+ started=time.perf_counter()
+ report={'status':'preparing','storage':'native_sql','phases':{},'project_uuid':service.project['project_uuid']}
+ service.annotation_preparation=report
+ try:
+ service._ready()
+ if type(shared) is not SharedAnnotations or getattr(service.dj.conn(),'_conn',None) is None:
+ report.update(status='unavailable',reason='Native SQL annotations are required')
+ return report
+ before=shared.generation_token()
+ if before is None:
+ report.update(status='unavailable',reason='Native annotation authority is unavailable')
+ return report
+ def fresh_proof():
+ tracker=getattr(service,'_recovery_tracker',None)
+ proof=verified_table_content(service.project_dir,tracker,tables=('shared_annotation',)) if tracker else None
+ if proof is None:
+ save_app_state(service.project_dir,service.dj.conn(),service=service)
+ tracker=getattr(service,'_recovery_tracker',None)
+ proof=verified_table_content(service.project_dir,tracker,tables=('shared_annotation',)) if tracker else None
+ if proof is None:raise ValueError('Fresh canonical annotation content proof is unavailable')
+ return proof
+ proof=fresh_proof()
+ expected=[before]
+ def locked_guard():
+ shared.state_generation.assert_current_locked(expected[0])
+ return True
+ def proof_after_installation():
+ # Lookup DDL intentionally changes authority. Capture a new exact
+ # authority and content proof after installation, then compare that
+ # same token under the source lock before publishing the marker.
+ opening=shared.generation_token()
+ if opening is None:raise SharedTagsChanged('Native authority is unavailable after lookup installation')
+ current=fresh_proof()
+ if shared.generation_token()!=opening:
+ raise SharedTagsChanged('Annotations changed while preparing the lookup content proof')
+ expected[0]=opening
+ return current
+ if progress:progress('preparing_native_tag_lookup')
+ lookup=getattr(shared,'native_tag_lookup',None)
+ same=lookup is not None and lookup.ready and getattr(lookup,'authority',None)==before.authority
+ if same:
+ lookup.checkpoint(proof,guard=locked_guard)
+ report['reused']=True
+ else:
+ # If this session observed a schema change, never checkpoint the old
+ # lookup blindly: missed source writes may have occurred in a gap.
+ lookup=bootstrap(service.dj.conn(),service.project['project_uuid'],proof,force=lookup is not None,
+ guard=locked_guard,proof_provider=proof_after_installation)
+ if not lookup.ready:raise ValueError(lookup.reason)
+ report['reused']=lookup.reused
+ if protocol_state is not None:
+ from workspace_protocol_state import STATE_CACHE_SCOPES
+ metadata=getattr(service,'disk_index',None)
+ metadata_generation=getattr(metadata,'generation',None)
+ if metadata is not None:metadata._check()
+ if progress:progress('preparing_protocol_reads')
+ phase_started=time.perf_counter()
+ protocols=list(service.protocols)[:STATE_CACHE_SCOPES]
+ report['protocols_prepared']=sum(protocol_state.native_context(identity) is not None for identity in protocols)
+ report['protocols_deferred']=max(0,len(service.protocols)-len(protocols))
+ report['phases']['preparing_protocol_reads']={'seconds':round(time.perf_counter()-phase_started,6)}
+ if metadata is not None:
+ metadata._check()
+ if service.disk_index is not metadata or metadata.generation!=metadata_generation:
+ raise ValueError('Source metadata changed during protocol preparation')
+ after=shared.generation_token()
+ lookup.validate_current()
+ final=shared.generation_token()
+ if after is None or final!=after or after!=expected[0]:
+ raise SharedTagsChanged('Annotations changed during lookup preparation; retry')
+ lookup.authority=after.authority
+ shared.native_tag_lookup=lookup
+ report.update(status='ready',phase='complete')
+ return report
+ except Exception as error:
+ old=getattr(shared,'native_tag_lookup',None)
+ if old is not None:old.ready=False
+ report.update(status='failed',reason=str(error))
+ return report
+ finally:
+ report['seconds']=round(time.perf_counter()-started,6)
diff --git a/python/workspace_annotations.py b/python/workspace_annotations.py
index fc22c0e..2e343f1 100644
--- a/python/workspace_annotations.py
+++ b/python/workspace_annotations.py
@@ -9,6 +9,8 @@
import datetime as dt
import getpass
import hashlib
+import json
+import re
import time
import uuid
from collections import Counter
@@ -96,7 +98,7 @@ def lock(self):
with contextlib.suppress(Exception):connection.query(f"SELECT RELEASE_LOCK('{name}')")
def _event(self,action,actor,payload):
- if action in {'shared_annotations_updated', 'annotation_profile_created'}:
+ if action == 'annotation_profile_created':
return None # Current annotations/profiles are saved state, not an action log.
event=str(uuid.uuid4())
self.Event.insert1({'event_uuid':event,'project_uuid':self.project_uuid,
@@ -163,33 +165,128 @@ def _scope(self,kind,ids,maximum=MAX_TARGETS):
ids=[identity(value) for value in ids]
if len(set(ids))!=len(ids):raise ValueError('Duplicate annotation target')
known=self.service.cells if kind=='cell' else self.service.rows
- if set(ids)-known.keys():raise ValueError('Annotation target is outside this registered project')
+ # Validate only requested identities: disk-backed keys() materializes
+ # the entire project and made small edits scale with all epochs.
+ if any(target not in known for target in ids):raise ValueError('Annotation target is outside this registered project')
return ids
- def _rows(self,kind=None,ids=None):
+ def _rows(self,kind=None,ids=None,*,selected_only=False):
relation=self.Annotation&{'project_uuid':self.project_uuid}
if kind is not None:relation=relation&{'target_kind':kind}
if ids is not None:
if not ids:return []
- if len(ids)<=250:relation=relation&[{'target_uuid':key} for key in ids]
+ if len(ids)<=250 or selected_only:
+ result=[]
+ for offset in range(0,len(ids),250):
+ result.extend((relation&[{'target_uuid':key} for key in ids[offset:offset+250]]).to_dicts())
+ return result
result=relation.to_dicts()
if ids is not None:
allowed=set(ids);result=[row for row in result if row['target_uuid'] in allowed]
return result
@staticmethod
- def _chips(row):
+ def _validated_tags(row):
values=tags(row['tags'])
identity(row['profile_uuid']);identity(row['target_uuid']);text(row['author_name'],120)
if type(row['revision']) is not int or row['revision']<1:raise ValueError('Invalid shared annotation revision')
+ return values
+
+ @staticmethod
+ def _chips(row):
+ values=SharedAnnotations._validated_tags(row)
return [{'tag':tag,'profile_uuid':row['profile_uuid'],'author_name':row['author_name'],
'target_kind':row['target_kind'],'target_uuid':row['target_uuid'],'revision':row['revision']}
for tag in sorted(values)]
+ def _index_record_tags(self,row):
+ if row['target_kind'] not in ('cell','epoch'):
+ raise ValueError('Invalid shared annotation target kind')
+ known=self.service.cells if row['target_kind']=='cell' else self.service.rows
+ if row['target_uuid'] not in known:
+ raise ValueError('Shared annotation target is no longer registered')
+ return self._validated_tags(row)
+
+ def _index_records(self,keys):
+ """Bounded canonical reads for a disposable tag membership index."""
+ if keys is not None:
+ relation=self.Annotation&{'project_uuid':self.project_uuid}
+ for offset in range(0,len(keys),250):
+ selected=[{name:key[name] for name in ('target_kind','target_uuid','profile_uuid')}
+ for key in keys[offset:offset+250]]
+ yield from (relation&selected).to_dicts()
+ return
+ full=self.Annotation.full_table_name
+ if not re.fullmatch(r'`[A-Za-z0-9_]+`\.`[A-Za-z0-9_]+`',full):
+ raise ValueError('Unsupported annotation table identity')
+ columns='project_uuid,target_kind,target_uuid,profile_uuid,tags,author_name,revision'
+ connection=self.dj.conn()
+ for kind in ('cell','epoch'):
+ last=None
+ while True:
+ condition='';params=[self.project_uuid,kind]
+ if last is not None:
+ condition=' AND (target_uuid>%s OR (target_uuid=%s AND profile_uuid>%s))'
+ params.extend((last[0],last[0],last[1]))
+ batch=connection.query(f'SELECT {columns} FROM {full} WHERE project_uuid=%s AND target_kind=%s'
+ +condition+' ORDER BY target_uuid,profile_uuid LIMIT 500',tuple(params),as_dict=True,reconnect=False).fetchall()
+ if not batch:break
+ for row in batch:
+ # Direct SQL projects bounded columns; DataJoint normally
+ # performs this JSON decoding in its to_dicts adapter.
+ if isinstance(row['tags'],str):row={**row,'tags':json.loads(row['tags'])}
+ yield row
+ last=(batch[-1]['target_uuid'],batch[-1]['profile_uuid'])
+
+ def _membership_index(self,rows,*,refresh=True):
+ tracker=getattr(self,'state_generation',None)
+ if type(self) is not SharedAnnotations or tracker is None:return None
+ from workspace_shared_tag_index import SharedTagIndex
+ if any(row['epoch_uuid'] not in self.service.rows
+ or row['cell_uuid']!=self.service.rows[row['epoch_uuid']]['cell_uuid'] for row in rows):
+ raise ValueError('Epoch/cell identity linkage does not match this project')
+ previous=getattr(self,'_shared_tag_index',None)
+ if previous is None or getattr(self,'_shared_tag_index_tracker',None) is not tracker:
+ if previous is not None:previous.close()
+ previous=self._shared_tag_index=SharedTagIndex(generation=tracker.token,
+ changes=tracker.shared_changes,records=self._index_records,validate=self._index_record_tags)
+ self._shared_tag_index_tracker=tracker
+ if refresh and not previous.refresh():return None
+ return previous
+
+ def generation_token(self):
+ tracker=getattr(self,'state_generation',None)
+ return tracker.token() if type(self) is SharedAnnotations and tracker is not None else None
+
+ def filter_epoch_ids(self,rows,filters):
+ """Indexed membership only; provenance is hydrated for visible rows."""
+ from workspace_service import validate_tag_filter_predicate
+ predicate=validate_tag_filter_predicate(filters['tag_predicate']) if 'tag_predicate' in filters else None
+ lookup=getattr(self,'native_tag_lookup',None)
+ if lookup is not None and lookup.ready:
+ before=self.generation_token()
+ if before is not None and before.authority==getattr(lookup,'authority',None):
+ for row in rows:
+ saved=self.service.rows.get(row['epoch_uuid'])
+ if saved is None or (saved is not row and saved['cell_uuid']!=row['cell_uuid']):
+ raise ValueError('Epoch/cell identity linkage does not match this project')
+ from workspace_native_tag_filter import matching
+ result=matching(rows,filters,predicate,lookup)
+ if self.generation_token()!=before:
+ from workspace_shared_tag_index import SharedTagsChanged
+ raise SharedTagsChanged('Shared tags changed while filtering; refresh and retry')
+ return result,before
+ previous=self._membership_index(rows)
+ return previous.matching(rows,filters,predicate) if previous is not None else None
+
+ def catalog_fields(self,rows,fields):
+ previous=self._membership_index(rows)
+ return previous.catalog(rows,fields) if previous is not None else None
+
def read_targets(self,kind,ids):
ids=self._scope(kind,ids)
output={key:{'target_kind':kind,'target_uuid':key,'tags':[],'revisions':{}} for key in ids}
- for row in self._rows(kind,ids):
+ for row in self._rows(kind,ids,selected_only=True):
output[row['target_uuid']]['tags'].extend(self._chips(row))
output[row['target_uuid']]['revisions'][row['profile_uuid']]=row['revision']
for item in output.values():item['tags'].sort(key=lambda r:(r['tag'],r['author_name'],r['profile_uuid']))
@@ -217,7 +314,10 @@ def for_epochs(self,rows):
return result
def summary(self,rows):
- rows=list(rows);ids={row['epoch_uuid'] for row in rows};cells={row['cell_uuid'] for row in rows}
+ rows=list(rows)
+ previous=self._membership_index(rows)
+ if previous is not None:return previous.summary(rows)
+ ids={row['epoch_uuid'] for row in rows};cells={row['cell_uuid'] for row in rows}
cell_epochs={key:set() for key in cells};epoch_cell={row['epoch_uuid']:row['cell_uuid'] for row in rows}
for row in rows:cell_epochs[row['cell_uuid']].add(row['epoch_uuid'])
tagged_cells=set();tagged_epochs=set();by_tag={}
@@ -239,7 +339,7 @@ def snapshot(self):
for row in self._rows():
known=self.service.cells if row['target_kind']=='cell' else self.service.rows
if row['target_uuid'] not in known:raise ValueError('Shared annotation target is no longer registered')
- self._chips(row)
+ self._validated_tags(row)
records.append({key:copy.deepcopy(row[key]) for key in ('target_kind','target_uuid','profile_uuid','author_name','revision','tags')})
records.sort(key=lambda r:(r['target_kind'],r['target_uuid'],r['profile_uuid']))
return {'version':1,'project_uuid':self.project_uuid,'revision':checksum({'project_uuid':self.project_uuid,'records':records}),'records':records,
@@ -288,7 +388,14 @@ def apply_batch(self,operations,actor,profiles=None,audit_context=None,external_
incoming.append(profile)
pending_profiles=[]
authors=self._ensure_profiles(incoming,actor,pending_profiles)
- saved={(row['target_kind'],row['target_uuid'],row['profile_uuid']):row for row in self._rows()}
+ # The transaction only needs the explicitly targeted author sets.
+ # Other targets/profiles cannot affect these optimistic revisions.
+ relation=self.Annotation&{'project_uuid':self.project_uuid}
+ requested=[{'target_kind':key[0],'target_uuid':key[1],'profile_uuid':key[2]} for key,_,_,_ in parsed]
+ saved={}
+ for offset in range(0,len(requested),250):
+ for row in (relation&requested[offset:offset+250]).to_dicts():
+ saved[(row['target_kind'],row['target_uuid'],row['profile_uuid'])]=row
for key,expected,add,remove in parsed:
if key[2] not in authors:raise ValueError('Select an existing local annotation profile')
old=saved.get(key);revision=old['revision'] if old else 0
@@ -315,11 +422,30 @@ def apply_batch(self,operations,actor,profiles=None,audit_context=None,external_
if external_receipt is not None:
# Receipt and additions commit together, even for an all-unchanged message.
event=self._event('external_annotation_received',actor,{'receipt':external_receipt})
- if changed:self._vocabulary=None
+ if changed:
+ self._vocabulary=None
+ on_commit=getattr(self,'on_commit',None)
+ if on_commit is not None:on_commit()
return {'changed':changed,'event_uuid':event,'annotations':after}
def suggestions(self,query='',limit=30):
if not isinstance(query,str) or len(query)>255 or type(limit) is not int or not 1<=limit<=100:raise ValueError('Use a tag prefix up to 255 characters and limit 1–100')
+ lookup=getattr(self,'native_tag_lookup',None)
+ if lookup is not None and lookup.ready and getattr(self._rows,'__func__',None) is _NATIVE_ANNOTATION_ROWS:
+ before=self.generation_token()
+ if before is not None and before.authority==getattr(lookup,'authority',None):
+ from workspace_native_tag_filter import suggestions
+ result=suggestions(lookup,query,limit)
+ if self.generation_token()!=before:
+ from workspace_shared_tag_index import SharedTagsChanged
+ raise SharedTagsChanged('Shared tags changed while reading suggestions; retry')
+ return result
+ # suggestions() owns its opening and closing authority checks itself.
+ index=(self._membership_index((),refresh=False)
+ if getattr(self._rows,'__func__',None) is _NATIVE_ANNOTATION_ROWS else None)
+ if index is not None:
+ result=index.suggestions(query,limit)
+ if result is not None:return result
if self._vocabulary is None or time.monotonic()>=self._vocabulary[0]:
values={}
for row in self._rows():
@@ -335,6 +461,9 @@ def suggestions(self,query='',limit=30):
'count_unit':'distinct_annotation_targets','match':'case_insensitive_prefix','has_more':len(selected)>limit}
+_NATIVE_ANNOTATION_ROWS=SharedAnnotations._rows
+
+
def register_annotation_routes(app,service,store,db_lock):
from flask import jsonify,request
def body(allowed):
diff --git a/python/workspace_api.py b/python/workspace_api.py
index 9ea819e..2ffc3ce 100644
--- a/python/workspace_api.py
+++ b/python/workspace_api.py
@@ -30,7 +30,7 @@
from recording_workspace import digest, now, write_json
from workspace_recipes import capture_query, checksum, compare_query, parse_splits, prepare_export, save_snapshot
-from workspace_storage import ManagedStorage, log_dir
+from workspace_storage import ManagedStorage, log_dir, managed_directory
from workspace_diff import summarize_diff
from workspace_protocol_identity import selection_protocols, protocol_compatibility, require_protocol_compatibility
from workspace_import_check import classify_source
@@ -70,7 +70,23 @@ def browser_safe(value):
return super().dumps(browser_safe(obj), **kwargs)
-def create_app(project_dir, retinanalysis_dir, *, service=None, store=None, explorer_history=None, data_stores=None, protocol_suggestions=None, shared_annotations=None):
+def import_worker_process(command, log, app):
+ """Keep desktop project ownership alive if its backend crashes mid-import.
+
+ POSIX descriptor inheritance shares the existing flock lease with the
+ writer. The worker closes its reference only when it exits. Source/browser
+ launches retain their original subprocess behavior.
+ """
+ options = {}
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1':
+ lease = app.extensions.get('app_state_session_lock')
+ if lease is None or lease.closed:
+ raise ValueError('Desktop recording import requires an active project lease')
+ options['pass_fds'] = (lease.fileno(),)
+ return subprocess.run(command, stdout=log, stderr=subprocess.STDOUT, **options)
+
+
+def create_app(project_dir, retinanalysis_dir, *, service=None, store=None, explorer_history=None, data_stores=None, protocol_suggestions=None, shared_annotations=None, desktop_session_lock=None):
from workspace_service import WorkspaceService
from workspace_curation import CurationStore, RevisionConflict
from workspace_explorer import ExplorerHistory
@@ -91,18 +107,21 @@ def create_app(project_dir, retinanalysis_dir, *, service=None, store=None, expl
state_lock_path = project_dir / '.app-state-session.lock'
if state_lock_path.is_symlink():
raise ValueError('App state lock cannot be a symbolic link')
- session_lock = state_lock_path.open('a')
- try:
- fcntl.flock(session_lock, fcntl.LOCK_SH | fcntl.LOCK_NB)
- except BlockingIOError:
- session_lock.close()
- raise ValueError('App state recovery is running; open the project after it finishes') from None
+ session_lock = desktop_session_lock or state_lock_path.open('a')
+ if desktop_session_lock is None:
+ try:
+ operation = fcntl.LOCK_EX if os.environ.get('RIEKE_DESKTOP_MODE') == '1' else fcntl.LOCK_SH
+ fcntl.flock(session_lock, operation | fcntl.LOCK_NB)
+ except BlockingIOError:
+ session_lock.close()
+ raise ValueError('Another app or state recovery owns this project; close it before opening') from None
app.extensions['app_state_session_lock'] = session_lock
db_lock = threading.RLock()
importing = threading.Lock()
from workspace_import_progress import ProgressReporter, read_jobs, recover_jobs, progress_for, load_json
owner_run_id = str(uuid.uuid4())
active_jobs = set()
+ app.extensions['project_active_writers'] = lambda: bool(active_jobs) or importing.locked()
recover_jobs(log_dir(project_dir, 'app-jobs'), owner_run_id)
service = service or WorkspaceService(project_dir)
store = store or CurationStore(service.dj, service.project["project_uuid"])
@@ -111,7 +130,8 @@ def create_app(project_dir, retinanalysis_dir, *, service=None, store=None, expl
app.extensions['explorer_history'] = explorer_history
protocol_suggestions = protocol_suggestions or ProtocolSuggestions(service, explorer_history)
app.extensions['protocol_suggestions'] = protocol_suggestions
- service.set_binding_provider(explorer_history.protocol_binding)
+ service.set_binding_provider(explorer_history.protocol_binding,
+ header_provider=explorer_history.protocol_binding_header)
store.binding_provider = explorer_history.protocol_binding
data_stores = data_stores or DataStores(service, store, explorer_history)
app.extensions["data_stores"] = data_stores
@@ -119,14 +139,18 @@ def create_app(project_dir, retinanalysis_dir, *, service=None, store=None, expl
if hasattr(service, 'set_annotation_provider'):
service.set_annotation_provider(lambda protocol_uuid, fingerprints:
store.read(protocol_uuid, list(fingerprints), fingerprints))
+ page_curation_provider=service.curation_provider
if shared_annotations is None and hasattr(service.dj,'Schema'):
from workspace_annotations import SharedAnnotations
shared_annotations=SharedAnnotations(service)
service.shared_annotations=shared_annotations
app.extensions['shared_annotations']=shared_annotations
- frontend = Path(__file__).resolve().parents[1] / "workspace-app/dist"
+ frontend = (Path(os.environ['RIEKE_DESKTOP_FRONTEND']) if os.environ.get('RIEKE_DESKTOP_MODE') == '1'
+ else Path(__file__).resolve().parents[1] / "workspace-app/dist")
app.extensions["workspace_service"] = service
app.extensions["curation_store"] = store
+ from workspace_project_preferences import register_project_preference_routes
+ register_project_preference_routes(app, project_dir, service.project['project_uuid'], db_lock)
def filters(allowed=()):
if any(len(request.args.getlist(key)) != 1 for key in ('tag', 'tagged', 'tag_predicate') if key in request.args):
@@ -136,7 +160,7 @@ def filters(allowed=()):
return {key: request.args[key] for key in ("epoch_uuid", "cell_uuid", "cell_type", "group_label", "tag", "tagged", "tag_predicate")
if key in request.args}
- def state(protocol_uuid):
+ def legacy_state(protocol_uuid):
result = service.query_result(protocol_uuid)
fingerprints = service.fingerprints(protocol_uuid)
for row in result["epochs"]:
@@ -149,6 +173,36 @@ def state(protocol_uuid):
revision = checksum({"query": {**result, "source_scope": {"revision": result["source_scope"]["revision"]}}, "curation": curation})
return result, curation, revision
+ from workspace_protocol_state import ProtocolStateReader
+ selected_state = ProtocolStateReader(service, store, shared_annotations, legacy_state)
+ app.extensions['protocol_state_reader'] = selected_state
+
+ def prepare_annotations(*,reuse=False,progress=None):
+ if not hasattr(service.dj,'Schema'):return {'status':'unavailable','reason':'Native SQL is unavailable'}
+ from workspace_annotation_preparation import prepare_project_annotations
+ return prepare_project_annotations(service,store,shared_annotations,
+ protocol_state=selected_state,reuse=reuse,progress=progress)
+
+
+ if hasattr(service.dj,'Schema'):
+ from workspace_state_generation import bootstrap
+ generation=bootstrap(service.dj.conn(),service.project['project_uuid'])
+ store.state_generation=generation
+ store.binding_header_provider=explorer_history.protocol_binding_header
+ if shared_annotations is not None:shared_annotations.state_generation=generation
+ app.extensions['state_generation']=generation
+
+ def state(protocol_uuid):
+ return selected_state.materialized_state(protocol_uuid)
+
+ def revision_guard(protocol_uuid):
+ # Bind invokes this callback inside its transaction. Native scope locks
+ # validate the outside-transaction v3 receipt without silently switching
+ # to a legacy checksum or pairing an old SQL snapshot with a new token.
+ context=selected_state.native_context(protocol_uuid)
+ return ((lambda:selected_state.assert_context_locked(protocol_uuid,context)) if context is not None
+ else (lambda:state(protocol_uuid)[2]))
+
def status(value):
return {**value, "reviewed": value["review_state"] == "approved"}
@@ -168,6 +222,7 @@ def enrich_protocol(payload, protocol_uuid, query_filters, export_memberships=No
'propagation_required': bool(affected),
'source_scope_revision': result['source_scope']['revision']}
payload["query_revision"] = revision
+ if revision.startswith('protocol-state-v3:'):payload['query_revision_contract']='protocol-state-v3'
payload["expected_query_revision"] = revision
payload["binding"] = compact_binding(result.get('dataset_binding'))
payload["expected_binding_version"] = result.get('dataset_binding', {}).get('version', 0)
@@ -196,6 +251,132 @@ def enrich_protocol(payload, protocol_uuid, query_filters, export_memberships=No
add_cell_annotations(payload.get('cells',[]))
return payload
+ def native_protocol_summary(protocol_uuid, query_filters, *, overview_context=False, export_memberships=None):
+ """Protocol UI summary without loading whole-dataset saved tag JSON."""
+ from workspace_service import validate_filters
+ query_filters=validate_filters(query_filters)
+ context=selected_state.native_context(protocol_uuid)
+ if context is None:return None
+ result=service.query_result(protocol_uuid)
+ identities=[row['uuid'] for row in result['epochs']]
+ selected,shared_receipt=service._epoch_page_identities(protocol_uuid,query_filters)
+ selected_set=set(selected)
+ # Preserve canonical query order for floating-point duration sums.
+ rows=[service.rows[key] for key in identities if key in selected_set]
+ ids={row['epoch_uuid'] for row in rows}
+ excluded,approved=store.summary_decisions(protocol_uuid,identities,service._fingerprints)
+ excluded &= ids;approved &= ids
+ included=ids-excluded
+ export_memberships=store.export_memberships() if export_memberships is None else export_memberships
+ exported={key for key,links in export_memberships.items()
+ if any(link['protocol_uuid']==protocol_uuid for link in links)}
+ memberships=({key:set(identities if key==protocol_uuid else
+ [member['uuid'] for member in service.query_result(key)['epochs']]) for key in service.protocols}
+ if not overview_context else {})
+ bins={}
+ duration=0
+ for row in rows:
+ duration+=row['duration_seconds']
+ cell=bins.setdefault(row['cell_uuid'],{'ids':set(),'duration_seconds':0,'group_labels':set()})
+ cell['ids'].add(row['epoch_uuid']);cell['duration_seconds']+=row['duration_seconds']
+ cell['group_labels'].add(row['group_label'])
+ cells=[]
+ for key,group in (bins.items() if not overview_context else ()):
+ members=group['ids']
+ cells.append({**service.cells[key],'epochs':len(members),'duration_seconds':group['duration_seconds'],
+ 'reviewed':len(members & approved),'included':len(members & included),
+ 'protocol_uuids':[identity for identity,scope in memberships.items() if members & scope],
+ 'group_labels':sorted(group['group_labels'],key=str)})
+ cells.sort(key=lambda row:(row['cell_type'] or '',row['date'],row['label']))
+ cell_counts=summarize_cell_membership(rows,approved,exported,included)
+ for cell in cells:cell.update(cell_counts[cell['cell_uuid']])
+ scope=service.source_scope()
+ archived=set(scope['excluded_source_revisions'])
+ affected=[service.rows[key] for key in identities if service.rows[key]['source_sha256'] in archived]
+ definition=service.protocols[protocol_uuid]['definition']
+ starter=definition['query']
+ counts={'cells':len(bins),'epochs':len(rows),'duration_seconds':duration,
+ 'included':len(included),'reviewed':len(approved),'unreviewed':len(ids-approved),
+ 'exported':len(ids & exported),'exportable':len(included),
+ 'approved_exportable':len(included & approved),'approved':len(approved),'excluded':len(excluded)}
+ payload={'definition':definition,'starter_query':starter,
+ 'effective_query':result.get('effective_query',starter),
+ 'binding':compact_binding(result.get('dataset_binding')),
+ 'counts':counts,'cells':cells,'groups':sorted({service.rows[key]['group_label'] for key in identities},key=str),
+ 'filters':dict(query_filters),'source_sha256s':sorted({row['source_sha256'] for row in rows}),
+ 'source_eligibility':{'excluded_epoch_count':len(affected),
+ 'excluded_sources':sorted({row['source_sha256'] for row in affected}),
+ 'propagation_required':bool(affected),'source_scope_revision':scope['revision']},
+ 'query_revision':context['query_revision'],'expected_query_revision':context['query_revision'],
+ 'query_revision_contract':'protocol-state-v3','expected_binding_version':context['binding_version']}
+ if shared_annotations:
+ summary=shared_annotations.summary(rows)
+ counts.update({key:summary[key] for key in ('shared_tagged_cells','shared_tagged_epochs')})
+ payload['annotation_summary']={key:summary[key] for key in ('tags','total_tags','truncated')}
+ if not overview_context:add_cell_annotations(cells)
+ after=selected_state.native_context(protocol_uuid)
+ if after is None or after['query_revision']!=context['query_revision']:
+ raise StaleWorkspace('Saved decisions changed while reading this protocol. Refresh and retry.')
+ if shared_receipt is not None and shared_annotations.state_generation.token()!=shared_receipt:
+ raise StaleWorkspace('Shared annotations changed while reading this protocol. Refresh and retry.')
+ if overview_context:
+ return payload,{'ids':ids,'approved':approved,'context':context,
+ 'view':result.get('effective_view',definition.get('view',{}))}
+ return payload
+
+ def native_overview():
+ """Refresh project counts using exact decision projections, not tags."""
+ if not service.protocols:return None
+ export_memberships=store.export_memberships()
+ protocols=[];proofs={};covered=set();not_approved=set()
+ for identity,entry in service.protocols.items():
+ summary=native_protocol_summary(identity,{},overview_context=True,export_memberships=export_memberships)
+ if summary is None:return None
+ payload,proof=summary;proofs[identity]=proof
+ covered.update(proof['ids']);not_approved.update(proof['ids']-proof['approved'])
+ definition=entry['definition']
+ protocols.append({'protocol_uuid':identity,'name':definition['name'],
+ 'acquisition_protocol':entry['result']['protocol_name'],'counts':payload['counts'],
+ 'query':payload['effective_query'],'starter_query':definition['query'],
+ 'binding':payload['binding'],'view':proof['view'],'query_revision':payload['query_revision']})
+ approved=covered-not_approved
+ rows=list(service.rows.values());exported=set(export_memberships)&service.rows.keys()
+ bins={};duration=0
+ for row in rows:
+ duration+=row['duration_seconds']
+ cell=bins.setdefault(row['cell_uuid'],{'ids':set(),'duration_seconds':0,'groups':set()})
+ cell['ids'].add(row['epoch_uuid']);cell['duration_seconds']+=row['duration_seconds']
+ cell['groups'].add(row['group_label'])
+ cells=[]
+ for identity,group in bins.items():
+ members=group['ids']
+ cells.append({**service.cells[identity],'epochs':len(members),'duration_seconds':group['duration_seconds'],
+ 'included':len(members),'reviewed':len(members & approved),'unreviewed':len(members-approved),
+ 'exported':len(members & exported),
+ 'protocol_uuids':[key for key,proof in proofs.items() if members & proof['ids']],
+ 'group_labels':sorted(group['groups'],key=str)})
+ cells.sort(key=lambda row:(row['cell_type'] or '',row['date'],row['label']))
+ scope=service.source_scope();active=set(scope['active_source_revisions'])
+ active_rows=[row for row in rows if row['source_sha256'] in active]
+ counts={'cells':len(cells),'epochs':len(rows),'reviewed':len(approved),'included':len(rows),
+ 'duration_seconds':duration,'protocols':len(protocols),'sources':len(service.sources),
+ 'exported':len(exported),'approved':len(approved),'unreviewed':len(rows)-len(approved)}
+ payload={'active_source_counts':{'cells':len({row['cell_uuid'] for row in active_rows}),
+ 'epochs':len(active_rows),'sources':len(active)},'source_scope':scope,
+ 'project':service.project,'catalog':{key:value for key,value in service.config.items() if key!='connection'},
+ 'counts':counts,'protocols':protocols,'cells':cells,'sources':service.sources,
+ 'events':service.events(25),'exports':store.list_dataset_revisions()}
+ add_cell_annotations(cells)
+ if shared_annotations:
+ summary=shared_annotations.summary(rows)
+ counts.update({key:summary[key] for key in ('shared_tagged_cells','shared_tagged_epochs')})
+ payload['annotation_summary']={key:summary[key] for key in ('tags','total_tags','truncated')}
+ for identity,proof in proofs.items():
+ after=selected_state.native_context(identity)
+ if after is None or after['query_revision']!=proof['context']['query_revision']:
+ raise StaleWorkspace('Saved decisions changed while reading this project. Refresh and retry.')
+ return payload
+
def add_cell_annotations(cells):
if not shared_annotations or not cells:return
records={}
@@ -239,7 +420,13 @@ def headers(response):
def failure(error):
if isinstance(error, HTTPException):
return jsonify(error=error.description), error.code
- if isinstance(error, (RevisionConflict, StaleWorkspace)):
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1':
+ from workspace_desktop import DesktopProjectCompatibilityError
+ if isinstance(error, DesktopProjectCompatibilityError):
+ return jsonify(error=str(error), code=error.code, requires_migration=True,
+ migration_endpoint='/api/projects/migrate-source'), 409
+ from workspace_shared_tag_index import SharedTagsChanged
+ if isinstance(error, (RevisionConflict, StaleWorkspace, SharedTagsChanged)):
return jsonify(error=str(error), code="stale_workspace"), 409
if isinstance(error, (ValueError, KeyError, FileNotFoundError)):
return jsonify(error=str(error)), 400
@@ -254,14 +441,31 @@ def failure(error):
def health():
from workspace_updates import release_metadata
return jsonify(status="ready", project_uuid=service.project["project_uuid"],
- app_release=release_metadata()['version'], project_path=str(project_dir))
+ app_release=release_metadata()['version'], project_path=str(project_dir),
+ backup=backup_status())
+
+ def backup_status():
+ scheduler=app.extensions.get('backup_scheduler')
+ return scheduler.status() if scheduler is not None else {'status':'unavailable'}
+
+ @app.get('/api/backup/status')
+ def backup_health():
+ return jsonify(backup_status())
@app.get('/api/metadata/status')
def metadata_status():
return jsonify(status='ready' if service._loaded else 'needs_refresh',
masks=app.extensions['mask_refresh'].latest if 'mask_refresh' in app.extensions else None,
last_refresh=getattr(service, 'last_successful_refresh', None),
- latest_attempt=getattr(service, 'last_refresh', None))
+ latest_attempt=getattr(service, 'last_refresh', None),
+ annotation_preparation=getattr(service,'annotation_preparation',None))
+
+ @app.get('/api/metadata/fields')
+ def metadata_fields():
+ if request.args:
+ raise ValueError('Registered metadata fields do not accept display filters')
+ with db_lock:
+ return jsonify(service.metadata_fields())
@app.post('/api/metadata/refresh')
def refresh_metadata():
@@ -288,7 +492,9 @@ def refresh_metadata():
except Exception as error:
warnings.append('Metadata refreshed, but its SQL audit record could not be saved: ' + str(error))
app.logger.exception('Metadata refresh audit failed')
- return jsonify(refresh=result, warnings=warnings, masks=masks)
+ preparation=prepare_annotations()
+ if preparation['status']=='failed':warnings.append('Annotation preparation failed: '+preparation.get('reason',''))
+ return jsonify(refresh=result, warnings=warnings, masks=masks, annotation_preparation=preparation)
except Exception as error:
with db_lock:
try:
@@ -414,7 +620,7 @@ def apply_source_propagation(source_sha256):
project_dir / 'catalog.json', os.environ.get('USER', 'local-user'),
protocol_uuid=item['protocol_uuid'], expected_version=item['binding_version'],
expected_query_revision=item['expected_query_revision'],
- current_query_revision=lambda: state(item['protocol_uuid'])[2],
+ current_query_revision=revision_guard(item['protocol_uuid']),
diff=item['diff'], previous_count=item['previous_count'], diff_summary=item['diff_summary'],
name=item['name'], parent_revision_uuid=item['current_revision_uuid'])
return jsonify(protocol_uuid=item['protocol_uuid'], revision_uuid=record['revision_uuid'],
@@ -424,6 +630,8 @@ def apply_source_propagation(source_sha256):
@app.get("/api/overview")
def overview():
with db_lock:
+ native=native_overview()
+ if native is not None:return jsonify(native)
payload = copy.deepcopy(service.overview())
memberships = {}
export_memberships = store.export_memberships()
@@ -457,13 +665,18 @@ def tags():
if not raw_limit.isascii() or not raw_limit.isdecimal() or len(raw_limit)>3:
raise ValueError('Tag suggestion limit must be an integer from 1 to 100')
with db_lock:
+ tracker=getattr(service,'_recovery_tracker',None)
+ if tracker is not None:store.recovery_tracker=tracker
return jsonify(store.tag_suggestions(request.args.get('q',''),int(raw_limit)))
@app.get("/api/protocols/")
def protocol(protocol_uuid):
with db_lock:
- return jsonify(enrich_protocol(copy.deepcopy(service.protocol(protocol_uuid, filters())),
- protocol_uuid, filters()))
+ query_filters=filters()
+ native=native_protocol_summary(protocol_uuid,query_filters)
+ if native is not None:return jsonify(native)
+ return jsonify(enrich_protocol(copy.deepcopy(service.protocol(protocol_uuid, query_filters)),
+ protocol_uuid, query_filters))
def tree_layout_store():
if 'tree_layouts' not in app.extensions:
@@ -502,10 +715,24 @@ def protocol_tree_layout(protocol_uuid):
@app.get("/api/protocols//epochs")
def epoch_page(protocol_uuid):
- with db_lock:
- page = service.epoch_page(protocol_uuid, filters({'anchor_uuid'}), int(request.args.get("offset", 0)),
- int(request.args.get("limit", 80)), anchor_uuid=request.args.get('anchor_uuid'))
- _, curation, revision = state(protocol_uuid)
+ include_cells=request.args.get('include_cells','false')
+ if len(request.args.getlist('include_cells'))>1 or include_cells not in ('true','false'):
+ raise ValueError('include_cells must be true or false')
+ with db_lock, selected_state.native_read(protocol_uuid,provider=page_curation_provider) as native:
+ tracker=getattr(store,'state_generation',None)
+ page_generation=tracker.token(protocol_uuid) if tracker and native is None else None
+ arguments={'anchor_uuid':request.args.get('anchor_uuid')}
+ if native is not None:arguments['include_curation']=False
+ if include_cells=='true':arguments['include_cells']=True
+ page = service.epoch_page(protocol_uuid, filters({'anchor_uuid','include_cells'}), int(request.args.get("offset", 0)),
+ int(request.args.get("limit", 80)), **arguments)
+ shared_receipt=page.pop('_shared_annotation_generation',None)
+ identities=[row['epoch_uuid'] for row in page['epochs']]
+ if native is None:
+ curation, revision, binding_version = selected_state.read_selected(protocol_uuid,identities)
+ else:
+ curation=native.selected(identities);revision=native.context['query_revision']
+ binding_version=native.context['binding_version']
page = copy.deepcopy(page)
export_memberships = store.export_memberships()
shared=shared_annotations.for_epochs([service.rows[row['epoch_uuid']] for row in page['epochs']]) if shared_annotations else {}
@@ -514,6 +741,13 @@ def epoch_page(protocol_uuid):
row["curation"] = status(curation[row["epoch_uuid"]])
row["export_count"] = len(export_memberships.get(row["epoch_uuid"], []))
page["query_revision"] = revision
+ page['expected_binding_version']=binding_version
+ if revision.startswith('protocol-state-v3:'):page['query_revision_contract']='protocol-state-v3'
+ if page_generation is not None and tracker.token(protocol_uuid)!=page_generation:
+ raise StaleWorkspace('Saved annotations changed while reading this page. Refresh and retry.')
+ if shared_receipt is not None and (not native.accepts_shared(shared_receipt) if native is not None
+ else shared_annotations.state_generation.token()!=shared_receipt):
+ raise StaleWorkspace('Shared annotations changed while reading this page. Refresh and retry.')
return jsonify(page)
@app.get("/api/protocols//tree")
@@ -558,7 +792,7 @@ def explorer_predicate_fields():
@app.post('/api/explore/preview')
def explorer_preview():
- body = explorer_request({'predicate', 'splits', 'summary_only', 'baseline_revision_uuid', 'focused_uuid'}, {'predicate', 'splits'})
+ body = explorer_request({'predicate', 'splits', 'summary_only', 'catalog_summary', 'baseline_revision_uuid', 'focused_uuid'}, {'predicate', 'splits'})
if type(body.get('summary_only', False)) is not bool:
raise ValueError('summary_only must be a boolean')
for key in ('baseline_revision_uuid', 'focused_uuid'):
@@ -567,7 +801,7 @@ def explorer_preview():
raise ValueError(key + ' must be a UUID string')
body[key] = str(uuid.UUID(body[key]))
with db_lock, data_stores.registration_locks(), annotation_locks(service, body['predicate']):
- preview = service.explore_preview(body['predicate'], body['splits'], include_tree=not body.get('summary_only', False))
+ preview = service.explore_preview(body['predicate'], body['splits'], include_tree=not body.get('summary_only', False), include_catalog_summary=body.get('catalog_summary', True))
if 'baseline_revision_uuid' in body:
baseline = explorer_history.get(body['baseline_revision_uuid'])['recipe']
before = {item['uuid']: item['metadata_hash'] for item in baseline['epochs']}
@@ -614,12 +848,12 @@ def compact_revision(result):
@app.post('/api/explore/revisions')
def explorer_apply():
- body = explorer_request({'predicate', 'splits', 'name', 'parent_revision_uuid', 'summary_only'}, {'predicate', 'splits'})
+ body = explorer_request({'predicate', 'splits', 'name', 'parent_revision_uuid', 'summary_only', 'catalog_summary'}, {'predicate', 'splits'})
if type(body.get('summary_only', False)) is not bool:
raise ValueError('summary_only must be a boolean')
with db_lock, data_stores.registration_locks(), annotation_locks(service, body['predicate']):
service.refresh()
- preview = service.explore_preview(body['predicate'], body['splits'], include_tree=not body.get('summary_only', False))
+ preview = service.explore_preview(body['predicate'], body['splits'], include_tree=not body.get('summary_only', False), include_catalog_summary=body.get('catalog_summary', True))
result = explorer_history.create(preview, service.sources, project_dir / 'catalog.json',
os.environ.get('USER', 'local-user'), name=body.get('name'),
parent_revision_uuid=body.get('parent_revision_uuid'))
@@ -651,9 +885,9 @@ def search_preset_versions(preset_uuid):
@app.post('/api/explore/run')
def run_search_query():
- body = explorer_request({'predicate', 'splits'}, {'predicate', 'splits'})
+ body = explorer_request({'predicate', 'splits', 'catalog_summary'}, {'predicate', 'splits'})
with db_lock, data_stores.registration_locks(), annotation_locks(service, body['predicate']):
- preview = service.explore_preview(body['predicate'], body['splits'], include_tree=False)
+ preview = service.explore_preview(body['predicate'], body['splits'], include_tree=False, include_catalog_summary=body.get('catalog_summary', True))
result = search_preset_store().record_run(preview, service.rows, os.environ.get('USER', 'local-user'))
return jsonify(**compact_preview(preview), last_run=result)
@@ -778,26 +1012,28 @@ def compare_protocol_candidate(revision_uuid, protocol_uuid):
def protocol_options(revision_uuid):
if request.args:
raise ValueError('Protocol choices do not accept query options')
- recipe = explorer_history.get(revision_uuid)['recipe']
- with db_lock, data_stores.registration_locks(), annotation_locks(service, recipe['predicate']):
- record, candidate = validated_candidate(revision_uuid)
- return jsonify(**selection_protocols(service, candidate),
- expected_recipe_sha256=record['recipe']['content_sha256'])
+ with db_lock:
+ recipe = explorer_history.get(revision_uuid)['recipe']
+ with data_stores.registration_locks(), annotation_locks(service, recipe['predicate']):
+ record, candidate = validated_candidate(revision_uuid)
+ return jsonify(**selection_protocols(service, candidate),
+ expected_recipe_sha256=record['recipe']['content_sha256'])
@app.post('/api/explore/revisions//create-protocol')
def create_protocol_from_candidate(revision_uuid):
from workspace_protocol_identity import create_pinned_protocol
body = explorer_request({'name', 'protocol_id', 'expected_recipe_sha256'},
{'name', 'protocol_id', 'expected_recipe_sha256'})
- recipe = explorer_history.get(revision_uuid)['recipe']
- with db_lock, data_stores.registration_locks(), annotation_locks(service, recipe['predicate']):
- service.refresh()
- record, candidate = validated_candidate(revision_uuid)
- if body['expected_recipe_sha256'] != record['recipe']['content_sha256']:
- raise StaleWorkspace('The saved selection changed. Review it again before creating a protocol.')
- result = create_pinned_protocol(service, explorer_history, record, body['name'],
- body['protocol_id'], os.environ.get('USER', 'local-user'))
- return jsonify(result), 201 if result['created'] else 200
+ with db_lock:
+ recipe = explorer_history.get(revision_uuid)['recipe']
+ with data_stores.registration_locks(), annotation_locks(service, recipe['predicate']):
+ service.refresh()
+ record, candidate = validated_candidate(revision_uuid)
+ if body['expected_recipe_sha256'] != record['recipe']['content_sha256']:
+ raise StaleWorkspace('The saved selection changed. Review it again before creating a protocol.')
+ result = create_pinned_protocol(service, explorer_history, record, body['name'],
+ body['protocol_id'], os.environ.get('USER', 'local-user'))
+ return jsonify(result), 201 if result['created'] else 200
@app.post('/api/explore/revisions//compare-to-protocol')
def compare_protocol_revision(revision_uuid):
@@ -824,7 +1060,7 @@ def apply_protocol_revision(revision_uuid):
saved = explorer_history.bind(revision_uuid, body['protocol_uuid'], body['expected_binding_version'],
os.environ.get('USER', 'local-user'), comparison['diff'], comparison['previous_count'],
expected_query_revision=body['expected_query_revision'],
- current_query_revision=lambda: state(body['protocol_uuid'])[2],
+ current_query_revision=revision_guard(body['protocol_uuid']),
diff_summary=comparison['diff_summary'])
protocol = enrich_protocol(copy.deepcopy(service.protocol(body['protocol_uuid'])), body['protocol_uuid'], {})
return jsonify(binding=protocol['binding'], event_uuid=saved['event_uuid'], diff=comparison['diff'],
@@ -837,9 +1073,7 @@ def epoch(epoch_uuid):
row = copy.deepcopy(service.epoch(epoch_uuid))
protocol_uuid = request.args.get("protocol_uuid")
if protocol_uuid:
- _, curation, _ = state(protocol_uuid)
- if epoch_uuid not in curation:
- raise ValueError("Epoch is outside the protocol query")
+ curation, _, _ = selected_state.read_selected(protocol_uuid,[epoch_uuid])
row["curation"] = status(curation[epoch_uuid])
row["catalog_ref"] = {"database": service.config["database"],
"project_uuid": service.project["project_uuid"], "protocol_uuid": protocol_uuid}
@@ -873,6 +1107,51 @@ def trace(epoch_uuid):
return jsonify(service.trace(epoch_uuid, request.args["stream_uuid"],
int(request.args.get("start", 0)), int(request.args.get("count", 20000))))
+ def curation_selection_scope(ids, scope):
+ """Validate only the selected rows; never load their detail metadata."""
+ from workspace_service import validate_filters
+ if not isinstance(scope, dict) or set(scope) - {'filters', 'cell_uuid'}:
+ raise ValueError('Curation selection scope accepts only filters and cell_uuid')
+ query_filters = validate_filters(scope.get('filters', {}))
+ cell = scope.get('cell_uuid')
+ if cell is not None and (not isinstance(cell, str) or str(uuid.UUID(cell)) != cell):
+ raise ValueError('Curation cell focus must be an exact cell UUID')
+ rows = [service.rows[key] for key in ids]
+ if cell is not None and any(row['cell_uuid'] != cell for row in rows):
+ raise ValueError('Curation includes epochs outside the current cell focus')
+ if len(service._filter_rows(rows, query_filters)) != len(ids):
+ raise ValueError('Curation includes epochs outside the current view filters')
+ return rows
+
+ @app.post("/api/protocols//curation/read")
+ def curation_batch_read(protocol_uuid):
+ body = request.get_json()
+ if (request.args or not isinstance(body, dict) or set(body) != {
+ 'epoch_uuids', 'query_revision', 'expected_binding_version', 'selection_scope'}):
+ raise ValueError('Curation read requires epoch_uuids, query_revision, expected_binding_version and selection_scope')
+ ids = body['epoch_uuids']
+ if (not isinstance(ids, list) or not 1 <= len(ids) <= 1000
+ or any(not isinstance(key, str) for key in ids)
+ or len(set(ids)) != len(ids)):
+ raise ValueError('Choose a unique list of 1–1,000 epoch UUIDs')
+ if any(str(uuid.UUID(key)) != key for key in ids):
+ raise ValueError('Curation read requires exact epoch UUIDs')
+ binding_version = body['expected_binding_version']
+ if type(binding_version) is not int or binding_version < 0:
+ raise ValueError('Expected binding version must be a nonnegative integer')
+ with db_lock:
+ current, revision, current_binding_version = selected_state.read_selected(protocol_uuid,ids)
+ if (body['query_revision'] != revision or
+ binding_version != current_binding_version):
+ raise StaleWorkspace('The inspected query or dataset binding changed. Refresh before saving.')
+ if set(ids) - current.keys():
+ raise ValueError('Curation includes epochs outside this protocol query')
+ rows = curation_selection_scope(ids, body['selection_scope'])
+ return jsonify(protocol_uuid=protocol_uuid, query_revision=revision,
+ expected_binding_version=binding_version,
+ epochs=[{'epoch_uuid': row['epoch_uuid'], 'cell_uuid': row['cell_uuid'],
+ 'curation_revision': current[row['epoch_uuid']]['revision']} for row in rows])
+
@app.post("/api/protocols//curation")
def curate(protocol_uuid):
body = request.get_json()
@@ -882,16 +1161,31 @@ def curate(protocol_uuid):
with db_lock:
if body.get("changes", {}).get("review_state") == "approved":
service.refresh()
- result, _, revision = state(protocol_uuid)
+ context=selected_state.native_context(protocol_uuid,ids)
+ if context is None:
+ result, _, revision = state(protocol_uuid)
+ fingerprints = {r["uuid"]: r["metadata_hash"] for r in result["epochs"]}
+ else:
+ revision=context['query_revision'];fingerprints=context['fingerprints']
+ result={'source_revisions':context['source_revisions'],
+ 'dataset_binding':{'version':context['binding_version']}}
if body.get("query_revision") != revision:
raise StaleWorkspace("The inspected query or metadata changed. Refresh before saving.")
- fingerprints = {r["uuid"]: r["metadata_hash"] for r in result["epochs"]}
if set(ids) - fingerprints.keys():
raise ValueError("Curation includes epochs outside this protocol query")
+ if 'expected_binding_version' in body:
+ expected_binding = body['expected_binding_version']
+ if type(expected_binding) is not int or expected_binding < 0:
+ raise ValueError('Expected binding version must be a nonnegative integer')
+ if expected_binding != result.get('dataset_binding', {}).get('version', 0):
+ raise StaleWorkspace('The dataset binding changed. Refresh before saving.')
+ if 'selection_scope' in body:
+ curation_selection_scope(ids, body['selection_scope'])
changed = store.update(protocol_uuid, ids, body["changes"], body["expected_revisions"],
{key: fingerprints[key] for key in ids}, os.environ.get("USER", "local-user"),
audit_context={"query_revision": revision, "source_revisions": result["source_revisions"]},
- expected_binding_version=result.get("dataset_binding", {}).get("version", 0))
+ expected_binding_version=result.get("dataset_binding", {}).get("version", 0),
+ generation_preflight=(lambda:selected_state.assert_context_locked(protocol_uuid,context)) if context else None)
return jsonify(changed)
@app.get("/api/protocols//masks/export")
@@ -1025,6 +1319,7 @@ def export(protocol_uuid):
export_format = body.get('format', 'reference-json')
if not isinstance(export_format, str) or export_format not in {'reference-json', 'epictree-mat', 'wheeler-sqlite'}:
raise ValueError('Unsupported export format')
+ managed_directory(project_dir, 'exports') # Recheck live links before any query/publication work.
with db_lock, data_stores.registration_locks(), (shared_annotations.lock() if shared_annotations else contextlib.nullcontext()):
service.refresh() # Recheck source identity and live query before publication.
result, curation, revision = state(protocol_uuid)
@@ -1059,7 +1354,7 @@ def export(protocol_uuid):
"tree_view": {"format": "recording-tree-view", "version": 1,
"fields": [{key: field_catalog[field][key] for key in ("id", "label", "path", "category", "components") if key in field_catalog[field]}
for field in grouping]}})
- output = project_dir / "exports" / recipe["export_uuid"]
+ output = managed_directory(project_dir, 'exports') / recipe["export_uuid"]
output.mkdir(parents=True, exist_ok=False)
save_snapshot(output / "recipe.json", recipe)
eligible = {r["uuid"] for r in recipe["epochs"]}
@@ -1211,8 +1506,15 @@ def import_job(source, job_file):
job.update(status="checking_duplicates", started_at=now(), catalog_committed=False,
progress_path=str(progress_file))
write_json(job_file, job)
- if job.get('origin') == 'h5-inbox' and (source.is_symlink() or source.parent != project_dir / 'raw-uploads'):
- raise ValueError('Folder imports must be regular files inside the managed H5 folder')
+ if job.get('origin') == 'h5-inbox':
+ upload_root = managed_directory(project_dir, 'raw-uploads')
+ if source.is_symlink() or source.parent != upload_root:
+ raise ValueError('Folder imports must be regular files inside the managed H5 folder')
+ if job.get('managed_upload'):
+ upload_uuid = str(uuid.UUID(job['upload_directory_uuid']))
+ folder = managed_directory(project_dir, 'raw-uploads/' + upload_uuid)
+ if source.is_symlink() or source.parent != folder:
+ raise ValueError('Staged uploads must remain regular files in their managed upload folder')
reporter.emit('checking_duplicates', completed=0, total=source.stat().st_size, unit='bytes')
def registered_sources():
with db_lock:
@@ -1229,8 +1531,9 @@ def registered_sources():
message='Already imported. No parsing or new catalog records; registration and query participation are unchanged.')
reporter.emit('duplicate', outcome='completed', catalog_committed=False)
if job.get('managed_upload'):
- upload_root = (project_dir / 'raw-uploads').resolve()
- if source.parent.parent.resolve() != upload_root or source.parent.name != job.get('upload_directory_uuid'):
+ upload_root = managed_directory(project_dir, 'raw-uploads')
+ managed_directory(project_dir, 'raw-uploads/' + str(uuid.UUID(job['upload_directory_uuid'])))
+ if source.parent.parent != upload_root or source.parent.name != job.get('upload_directory_uuid'):
raise ValueError('Duplicate staging cleanup path failed validation')
try:
source.unlink()
@@ -1270,7 +1573,7 @@ def registered_sources():
command.extend(['--container', provider['container']])
with log_file.open("w") as log:
job['catalog_committed'] = None
- completed = subprocess.run(command, stdout=log, stderr=subprocess.STDOUT)
+ completed = import_worker_process(command, log, app)
child, progress_error = progress_for(job_file, job)
if child:
reporter.current = child
@@ -1309,6 +1612,10 @@ def registered_sources():
write_json(job_file, job)
proposals = protocol_suggestions.rerun(baselines, check['source_sha256'],
source.name, os.environ.get('USER', 'local-user'))
+ preparation=prepare_annotations(progress=lambda phase:reporter.emit(phase))
+ job['annotation_preparation']=preparation
+ if preparation['status']=='failed':
+ job['warnings'].append({'stage':'annotation_preparation','message':preparation.get('reason','Preparation failed')})
job['protocol_suggestions'] = proposals['counts']
job['warnings'].extend(proposals['warnings'])
except Exception as query_error:
@@ -1424,7 +1731,7 @@ def import_recording():
if Path(name).suffix.lower() not in {".h5", ".hdf5"}:
raise ValueError("Select a Symphony .h5 recording")
managed_upload, upload_directory_uuid = True, str(uuid.uuid4())
- folder = project_dir / "raw-uploads" / upload_directory_uuid
+ folder = managed_directory(project_dir, 'raw-uploads') / upload_directory_uuid
folder.mkdir(parents=True)
source = folder / name
upload.save(source)
@@ -1432,7 +1739,10 @@ def import_recording():
body = request.get_json(silent=True)
if not isinstance(body, dict) or set(body) != {'source_path'} or not isinstance(body['source_path'], str) or not body['source_path'].strip():
raise ValueError('Provide exactly one nonempty source_path string or upload one recording')
- source = Path(body["source_path"]).expanduser().resolve()
+ candidate = Path(body["source_path"]).expanduser()
+ if not candidate.is_absolute():
+ raise ValueError('Choose an absolute recording file path')
+ source = candidate.resolve()
if not source.is_file() or source.suffix.lower() not in {".h5", ".hdf5"}:
raise ValueError("Choose an existing .h5 recording file")
identity = str(uuid.uuid4())
@@ -1485,15 +1795,37 @@ def ui(path="index.html"):
# route tests have no SQL schema; real servers always enable these backups.
if hasattr(service.dj, 'Schema'):
from workspace_state_snapshot import save as save_app_state
+ from workspace_backup_scheduler import BackupScheduler
with db_lock:
save_app_state(project_dir, service.dj.conn(), service=service)
+ prepare_annotations(reuse=True)
+ scheduler=BackupScheduler(lambda:save_app_state(project_dir,service.dj.conn(),service=service),
+ db_lock,logger=app.logger)
+ app.extensions['backup_scheduler']=scheduler
+ if shared_annotations is not None:shared_annotations.on_commit=scheduler.request
@app.after_request
def backup_saved_state(response):
- if request.path != '/api/project/close' and request.method in {'POST', 'PUT', 'PATCH', 'DELETE'} and 200 <= response.status_code < 300:
+ if request.endpoint=='annotation_update' and 200<=response.status_code<300:
+ # Rows and their immutable event are already committed together.
+ # The post-commit callback queued the independent recovery mirror.
+ result=response.get_json()
+ result['persistence']={'database':'committed','backup':scheduler.status()}
+ response.set_data(app.json.dumps(result))
+ return response
+ # These POST handlers only read state. Default to checkpointing all
+ # other writes, including idempotent/no-op mutations: a previous
+ # committed write may still need protection after a backup failure.
+ # In particular, explore/run records last-run state and is a write.
+ read_posts = {'explorer_preview', 'tree_page', 'matching_epochs',
+ 'annotation_batch_read', 'curation_batch_read', 'tag_import_preview',
+ 'preview_source_propagation', 'resolve_search_preset',
+ 'compare_protocol_revision'}
+ readonly = request.method == 'POST' and request.endpoint in read_posts
+ desktop_control = os.environ.get('RIEKE_DESKTOP_MODE') == '1' and request.path.startswith('/api/desktop/')
+ if not readonly and not desktop_control and request.path != '/api/project/close' and request.method in {'POST', 'PUT', 'PATCH', 'DELETE'} and 200 <= response.status_code < 300:
try:
- with db_lock:
- save_app_state(project_dir, service.dj.conn(), service=service)
+ scheduler.flush()
except Exception:
app.logger.exception('App state was saved to SQL but its recovery snapshot failed')
failure = jsonify(error='Saved to the database, but the app-state backup failed. '
@@ -1510,6 +1842,13 @@ def stop_project_database():
inbox.start()
raise ValueError('An inbox import started while closing; wait for it to finish')
with db_lock:
+ scheduler=app.extensions.get('backup_scheduler')
+ try:
+ if scheduler is not None:scheduler.flush()
+ prepare_annotations()
+ except Exception:
+ inbox.start()
+ raise
with contextlib.suppress(Exception):
service.dj.conn().close()
from workspace_native_mysql import stop_native_database
@@ -1517,8 +1856,11 @@ def stop_project_database():
owner = json.loads((project_dir / 'database/native-owner.json').read_text())
if owner.get('clean_shutdown') is not True:
raise ValueError('A clean database shutdown could not be verified')
- register_project_lifecycle(app, busy=lambda: bool(active_jobs) or importing.locked(),
- stop_database=stop_project_database)
+ if scheduler is not None:scheduler.close(flush=False)
+ if os.environ.get('RIEKE_DESKTOP_MODE') != '1':
+ register_project_lifecycle(app, busy=lambda: bool(active_jobs) or importing.locked(),
+ stop_database=stop_project_database)
+ app.extensions['desktop_stop_database'] = stop_project_database
return app
diff --git a/python/workspace_app_routes.py b/python/workspace_app_routes.py
index 0338305..5ad67c0 100644
--- a/python/workspace_app_routes.py
+++ b/python/workspace_app_routes.py
@@ -14,8 +14,37 @@ def register_app_routes(app, *, application_dir=None):
update_token = secrets.token_urlsafe(32)
update_job = {'state': 'idle'}
+ def active_writers():
+ with lock:
+ return any(job['state'] == 'running' for job in jobs.values()) or update_job['state'] == 'running'
+ app.extensions['app_active_writers'] = active_writers
+
+ def start_update(version=None, *, automatic=False):
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1':
+ raise ValueError('Desktop updates are owned by the application coordinator.')
+ from workspace_updates import stage_release
+ with lock:
+ if update_job['state'] == 'running':
+ return False
+ if automatic and update_job.get('version') == version:
+ return False # One automatic attempt per version; manual retry remains available.
+ update_job.clear()
+ update_job.update(state='running', version=version)
+ def run():
+ try:
+ result = stage_release(os.environ['RIEKE_INSTALLATION_ROOT'], root=root)
+ with lock:
+ update_job.update(state='complete', version=result.get('version'), result=result)
+ except Exception as error:
+ app.logger.exception('Update staging failed')
+ with lock:
+ update_job.update(state='failed', error=str(error) if isinstance(error, ValueError) else 'Update download failed. Check the launcher log; the active release was not changed.')
+ threading.Thread(target=run, name='rieke-update-download', daemon=False).start()
+ return True
+
def update_payload(value):
- return {**value, 'update_token': update_token, 'download': dict(update_job)}
+ with lock:
+ return {**value, 'update_token': update_token, 'download': dict(update_job)}
def local_request():
if (request.remote_addr not in {'127.0.0.1', '::1'}
@@ -28,8 +57,10 @@ def local_request():
@app.before_request
def app_operation_boundary():
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1' and request.path.startswith('/api/app/updates'):
+ return jsonify(error='Desktop updates are owned by the application coordinator.', desktop=True), 409
if request.path.startswith(('/api/app/', '/api/projects/transfers/',
- '/api/projects/prepare-transfer', '/api/projects/restore-transfer', '/api/projects/relocate')):
+ '/api/projects/prepare-transfer', '/api/projects/restore-transfer', '/api/projects/migrate-source', '/api/projects/relocate')):
return local_request()
@app.get('/api/app/updates')
@@ -43,31 +74,23 @@ def check_updates():
if request.args or body not in ({}, {'force': True}):
return jsonify(error='Update check accepts an empty object or force: true.'), 400
from workspace_updates import check_for_updates
- return jsonify(update_payload(check_for_updates(root=root, force=bool(body.get('force')))))
+ result = check_for_updates(root=root, force=bool(body.get('force')))
+ if (result.get('state') == 'update_available' and result.get('can_stage')
+ and not result.get('check_error') and os.environ.get('RIEKE_INSTALLATION_ROOT')
+ and result.get('staged_version') != result.get('available')):
+ start_update(result.get('available'), automatic=True)
+ return jsonify(update_payload(result))
@app.post('/api/app/updates/stage')
def stage_update():
if (request.args or request.get_json(silent=True) != {}
or not secrets.compare_digest(request.headers.get('X-Rieke-Update-Token', ''), update_token)):
return jsonify(error='Reopen the updates panel before downloading an update.'), 403
- from workspace_updates import installation_status, stage_release
+ from workspace_updates import installation_status
if not installation_status(root=root).get('can_stage') or not os.environ.get('RIEKE_INSTALLATION_ROOT'):
return jsonify(error='Verified downloads require a managed installation with a trusted release key.'), 409
- with lock:
- if update_job['state'] == 'running':
- return jsonify(error='An update download is already running.'), 409
- update_job.clear()
- update_job['state'] = 'running'
- def run():
- try:
- result = stage_release(os.environ['RIEKE_INSTALLATION_ROOT'], root=root)
- with lock:
- update_job.update(state='complete', result=result)
- except Exception as error:
- app.logger.exception('Update staging failed')
- with lock:
- update_job.update(state='failed', error=str(error) if isinstance(error, ValueError) else 'Update download failed. Check the launcher log; the active release was not changed.')
- threading.Thread(target=run, name='rieke-update-download', daemon=False).start()
+ if not start_update():
+ return jsonify(error='An update download is already running.'), 409
return jsonify(state='running'), 202
@app.get('/api/app/updates/download')
@@ -93,8 +116,15 @@ def transfer(operation):
def run():
try:
from workspace_portability import prepare_project, restore_project
- function = prepare_project if operation == 'prepare' else restore_project
+ if operation == 'migrate-source':
+ from workspace_migration import migrate_source_project
+ function = migrate_source_project
+ else:
+ function = prepare_project if operation == 'prepare' else restore_project
result = function(body['directory'].strip(), body['destination'].strip())
+ if operation == 'restore':
+ from workspace_startup_registry import remember_project_result
+ result = remember_project_result(result['directory'], result)
with lock:
jobs[identity] = {'job_id': identity, 'state': 'complete', 'result': result}
except Exception as error:
@@ -114,6 +144,12 @@ def prepare_transfer():
def restore_transfer():
return transfer('restore')
+ @app.post('/api/projects/migrate-source')
+ def migrate_source():
+ if os.environ.get('RIEKE_DESKTOP_MODE') != '1':
+ return jsonify(error='Create source copies through the desktop application.'), 409
+ return transfer('migrate-source')
+
@app.post('/api/projects/relocate')
def relocate_project_folder():
body = request.get_json(silent=True)
@@ -127,6 +163,8 @@ def relocate_project_folder():
try:
from workspace_portability import relocate_project
result = relocate_project(body['directory'].strip(), body['destination'].strip())
+ from workspace_startup_registry import remember_project_result
+ result = remember_project_result(result['directory'], result, previous_directory=body['directory'].strip())
except (ValueError, OSError) as error:
return jsonify(error=str(error)), 400
return jsonify(result)
diff --git a/python/workspace_backup_scheduler.py b/python/workspace_backup_scheduler.py
new file mode 100644
index 0000000..a9ae761
--- /dev/null
+++ b/python/workspace_backup_scheduler.py
@@ -0,0 +1,83 @@
+"""Coalesce recovery mirrors after committed SQL edits; flush before shutdown."""
+from __future__ import annotations
+
+import threading
+import time
+from datetime import datetime, timezone
+
+
+class BackupScheduler:
+ def __init__(self, capture, database_lock, *, delay=1.0, max_delay=5.0, retry_delay=5.0, logger=None):
+ self.capture=capture; self.database_lock=database_lock
+ self.delay=delay; self.max_delay=max_delay; self.retry_delay=retry_delay; self.logger=logger
+ self.condition=threading.Condition(); self.requested=0; self.completed=0
+ self.first_dirty=None; self.last_dirty=None; self.retry_at=0.0
+ self.running=False; self.stopped=False; self.last_error=None; self.last_success=None
+ self.worker=threading.Thread(target=self._work,name='workspace-state-backup',daemon=True)
+ self.worker.start()
+
+ def request(self):
+ with self.condition:
+ self.requested+=1
+ now=time.monotonic()
+ if self.first_dirty is None:self.first_dirty=now
+ self.last_dirty=now
+ if self.stopped:self.last_error='Backup worker is stopped; flush before closing'
+ self.condition.notify_all()
+
+ def status(self):
+ with self.condition:
+ pending=self.requested>self.completed
+ return {'status':'degraded' if self.last_error else 'running' if self.running else 'pending' if pending else 'current',
+ 'pending':pending,'running':self.running,'last_success':self.last_success,
+ 'last_error':self.last_error,'requested_sequence':self.requested,'completed_sequence':self.completed}
+
+ def _capture_locked(self):
+ # Always acquire the database lock before the condition. The close path
+ # already holds the reentrant database lock when it calls flush().
+ with self.condition:
+ goal=self.requested; self.running=True
+ try:
+ self.capture()
+ except Exception as error:
+ with self.condition:
+ self.last_error=str(error); self.retry_at=time.monotonic()+self.retry_delay
+ raise
+ else:
+ with self.condition:
+ self.completed=goal; self.last_error=None; self.retry_at=0.0
+ self.last_success=datetime.now(timezone.utc).isoformat()
+ if self.completed==self.requested:self.first_dirty=self.last_dirty=None
+ finally:
+ with self.condition:self.running=False; self.condition.notify_all()
+
+ def flush(self):
+ """Force a complete current mirror, including writes outside this queue."""
+ with self.database_lock:self._capture_locked()
+ return self.status()
+
+ def close(self, *, flush=True):
+ if flush:self.flush()
+ with self.condition:self.stopped=True; self.condition.notify_all()
+ # Do not join here: callers may hold database_lock while a worker waits
+ # for it. The worker checks stopped again before any database operation.
+
+ def _work(self):
+ while True:
+ with self.condition:
+ if self.stopped:return
+ if self.requested==self.completed:
+ self.condition.wait(); continue
+ deadline=max(self.retry_at,min(self.last_dirty+self.delay,self.first_dirty+self.max_delay))
+ remaining=deadline-time.monotonic()
+ if remaining>0:self.condition.wait(remaining); continue
+ with self.database_lock:
+ with self.condition:
+ if self.stopped:return
+ if self.requested==self.completed:continue
+ # A newer edit may have arrived while waiting for SQL.
+ deadline=max(self.retry_at,min(self.last_dirty+self.delay,self.first_dirty+self.max_delay))
+ if time.monotonic()16384:return True
+ record=json.loads(seal.read_text())
+ except (OSError,ValueError):return False
+ return isinstance(record,dict) and record.get('project_uuid') not in (None,self.owner)
+
+ def _collect_locked(self,record):
+ keep=set(record['keep']);groups={};removed=[];leased=[]
+ for path in self.directory.iterdir():
+ key=self._key(path.name)
+ if key and path.is_file() and not path.is_symlink():groups.setdefault(key,[]).append(path)
+ for path in self.leases.iterdir():
+ if path.name.endswith('.lock'):
+ key=path.name[:-5]
+ if self._key(key)==key:groups.setdefault(key,[])
+ for key,paths in groups.items():
+ if key in keep or self._foreign_metadata(key):continue
+ lock_path=self.leases/(key+'.lock')
+ try:handle=_open_lock(lock_path)
+ except OSError:continue
+ try:
+ try:fcntl.flock(handle.fileno(),fcntl.LOCK_EX|fcntl.LOCK_NB)
+ except BlockingIOError:leased.append(key);continue
+ for path in paths:
+ try:path.unlink();removed.append(path.name)
+ except OSError:pass
+ # Removing a lease inode is safe only under the directory guard
+ # and its exclusive lease: no reader can reopen the old inode.
+ if not any(path.exists() for path in paths):
+ with contextlib.suppress(OSError):lock_path.unlink()
+ finally:handle.close()
+ return {'removed_files':sorted(removed),'leased_generations':sorted(leased)}
diff --git a/python/workspace_candidate_exports.py b/python/workspace_candidate_exports.py
index ddad3f3..95897a3 100644
--- a/python/workspace_candidate_exports.py
+++ b/python/workspace_candidate_exports.py
@@ -15,6 +15,7 @@
from recording_workspace import digest, now, write_json
from workspace_recipes import checksum, member_map, prepare_export, save_snapshot, seal
+from workspace_storage import managed_directory
FORMATS = {'reference-json','wheeler-sqlite','epictree-mat'}
@@ -52,6 +53,7 @@ def _export_candidate_locked(service, store, history, revision_uuid, *, format,
raise ValueError('Expected the saved candidate recipe SHA256')
if name is not None and (not isinstance(name,str) or len(name)>120):
raise ValueError('Export name must be text of at most 120 characters')
+ managed_directory(service.project_dir, 'exports') # Reject live redirects before reads or publication.
record=history.get(revision_uuid)
candidate=record['recipe']
if candidate['content_sha256']!=expected_recipe_sha256:
@@ -104,7 +106,7 @@ def _export_candidate_locked(service, store, history, revision_uuid, *, format,
'tree_view':{'format':'recording-tree-view','version':1,
'fields':[{key:fields[field][key] for key in ('id','label','path','category','components') if key in fields[field]}
for field in grouping]}})
- output=service.project_dir/'exports'/recipe['export_uuid']
+ output=managed_directory(service.project_dir,'exports')/recipe['export_uuid']
output.mkdir(parents=True,exist_ok=False)
artifact=output/'recordings.json'
try:
diff --git a/python/workspace_catalog_collision.py b/python/workspace_catalog_collision.py
new file mode 100644
index 0000000..dcda167
--- /dev/null
+++ b/python/workspace_catalog_collision.py
@@ -0,0 +1,132 @@
+"""Bound catalog identity checks without altering persistent acquisition tables.
+
+Create scratch indexes before the import transaction, while its advisory lock is
+held. Use the catalog column's actual collation; Python string normalization must
+never decide whether a SQL population routine would reuse an existing identity.
+"""
+from __future__ import annotations
+
+import re
+import uuid
+
+
+KINDS=('Animal','Preparation','Cell','EpochGroup','EpochBlock','Epoch','Response','Stimulus')
+_TABLE=re.compile(r'^`([A-Za-z0-9_]+)`\.`([A-Za-z0-9_]+)`$')
+_NAME=re.compile(r'^[A-Za-z0-9_]+$')
+
+
+def _original_collision(table, values, batch_size):
+ for offset in range(0,len(values),batch_size):
+ found=(table & [{'h5_uuid':key} for key in values[offset:offset+batch_size]]).fetch('h5_uuid',limit=1)
+ if len(found):return found[0]
+ return None
+
+
+class CatalogCollisionLookup:
+ """Connection-local scratch relations, owned by one locked import session.
+
+ Unsupported table metadata uses the original DataJoint lookup. SQL setup,
+ insert, query and cleanup failures are errors, never permission to skip a
+ collision check. The caller must close after its transaction has ended.
+ """
+ def __init__(self,catalog):
+ self.catalog=catalog
+ self.connection=getattr(getattr(catalog,'schema',None),'connection',None)
+ self.tables={};self.temporary={};self.owner=None;self.prepared=False
+
+ def _query(self,sql,args=(),**options):
+ # Reconnecting would silently lose temporary tables and the import lock.
+ return self.connection.query(sql,args,reconnect=False,**options)
+
+ def _assert_owner(self,transaction=None):
+ if transaction is not None and self.connection.in_transaction is not transaction:
+ raise RuntimeError('Catalog identity scratch lookup used outside its required transaction boundary')
+ owner,locked=self._query("SELECT CONNECTION_ID(),IS_USED_LOCK('recording_workspace_import')").fetchone()
+ if owner!=self.owner or locked!=owner:
+ raise RuntimeError('Catalog identity lookup lost its owning connection or import lock')
+
+ def prepare(self):
+ if self.prepared:raise RuntimeError('Catalog identity lookup is already prepared')
+ self.prepared=True
+ # Lightweight relation doubles and non-MySQL adapters retain the exact
+ # existing check. Real pinned DataJoint exposes a Boolean transaction flag.
+ if self.connection is None or type(getattr(self.connection,'in_transaction',None)) is not bool:
+ return self
+ if self.connection.in_transaction:
+ raise RuntimeError('Prepare catalog identity scratch indexes before starting the import transaction')
+ self.owner=self._query('SELECT CONNECTION_ID()').fetchone()[0]
+ self._assert_owner(transaction=False)
+ try:
+ for kind in KINDS:
+ relation=getattr(self.catalog,kind)
+ full=getattr(relation,'full_table_name',None)
+ match=_TABLE.fullmatch(full) if isinstance(full,str) else None
+ if not match:continue
+ column=self._query(f'SHOW FULL COLUMNS FROM {full} WHERE Field=%s',('h5_uuid',),as_dict=True).fetchone()
+ if not column or str(column['Type']).lower()!='varchar(255)':continue
+ indexes=self._query(f'SHOW INDEX FROM {full}',as_dict=True).fetchall()
+ if any(row.get('Seq_in_index')==1 and row.get('Column_name')=='h5_uuid'
+ and row.get('Visible','YES')!='NO' for row in indexes):
+ continue # Existing UUID indexes already serve bounded probes.
+ collation=column.get('Collation')
+ if not isinstance(collation,str) or not _NAME.fullmatch(collation):continue
+ charset=self._query('SELECT CHARACTER_SET_NAME FROM information_schema.COLLATIONS WHERE COLLATION_NAME=%s',(collation,)).fetchone()
+ if not charset or not _NAME.fullmatch(charset[0]):continue
+ # Each collation receives a compatible scratch index, including
+ # older utf8mb3 catalogs and catalogs with per-table overrides.
+ key=(match[1],charset[0],collation)
+ if key not in self.temporary:
+ temporary=f'`{match[1]}`.`_workspace_identity_{uuid.uuid4().hex}`'
+ self._query(f'CREATE TEMPORARY TABLE {temporary} (h5_uuid VARCHAR(255) CHARACTER SET {charset[0]} COLLATE {collation} NOT NULL, KEY candidate_uuid(h5_uuid)) ENGINE=InnoDB')
+ self.temporary[key]=temporary
+ self.tables[kind]=(full,self.temporary[key])
+ except Exception as error:
+ try:self.close()
+ except Exception as cleanup:error.add_note(f'Identity scratch cleanup also failed: {cleanup}')
+ raise
+ return self
+
+ def first_collision(self,kind,values,batch_size=200):
+ if not self.prepared:raise RuntimeError('Prepare catalog identity lookup before checking identities')
+ if kind not in KINDS:raise ValueError('Unknown acquisition identity table')
+ if type(batch_size) is not int or not 1<=batch_size<=500:
+ raise ValueError('Identity lookup batches must contain 1–500 identities')
+ if not values:return None
+ if self.owner is not None:self._assert_owner(transaction=True)
+ if kind not in self.tables or len(values)<=batch_size:
+ return _original_collision(getattr(self.catalog,kind),values,batch_size)
+ full,temporary=self.tables[kind]
+ # An empty catalog needs neither staging nor repeated empty scans.
+ if self._query(f'SELECT h5_uuid FROM {full} LIMIT 1').fetchone() is None:return None
+ self._query(f'DELETE FROM {temporary}')
+ for offset in range(0,len(values),batch_size):
+ batch=values[offset:offset+batch_size]
+ self._query(f'INSERT INTO {temporary} (h5_uuid) VALUES '+','.join(['(%s)']*len(batch)),tuple(batch))
+ # Catalog-first join plus indexed probes bounds an unindexed existing
+ # catalog to one scan; the server retains its native equality semantics.
+ found=self._query(f'SELECT existing.h5_uuid FROM {full} AS existing STRAIGHT_JOIN {temporary} AS incoming FORCE INDEX(candidate_uuid) ON incoming.h5_uuid=existing.h5_uuid LIMIT 1').fetchone()
+ self._query(f'DELETE FROM {temporary}')
+ return found[0] if found else None
+
+ def close(self):
+ if not self.temporary:return
+ self._assert_owner(transaction=False)
+ failures=[]
+ for key,temporary in list(self.temporary.items()):
+ try:self._query(f'DROP TEMPORARY TABLE {temporary}')
+ except Exception as error:failures.append(error)
+ else:del self.temporary[key]
+ if failures:
+ # Closing this exact connection lets MySQL discard all remaining
+ # session-local tables. Never reconnect to perform cleanup.
+ self.connection.close();self.temporary.clear()
+ raise RuntimeError('Catalog identity scratch cleanup failed; owning connection was closed') from failures[0]
+
+ def __enter__(self):return self.prepare()
+
+ def __exit__(self,kind,error,traceback):
+ try:self.close()
+ except Exception as cleanup:
+ if error is None:raise
+ error.add_note(f'Identity scratch cleanup also failed: {cleanup}')
+ return False
diff --git a/python/workspace_catalog_identity.py b/python/workspace_catalog_identity.py
new file mode 100644
index 0000000..1fbab19
--- /dev/null
+++ b/python/workspace_catalog_identity.py
@@ -0,0 +1,174 @@
+"""Independently verify populated acquisition relationships before SQL commit.
+
+Names and numeric database IDs are never used as acquisition identity. Numeric
+IDs only resolve the foreign-key ownership of exact recorded UUIDs. One fetch
+per hierarchy table replaces per-epoch stream fetches for verification.
+"""
+from __future__ import annotations
+
+import json
+import datetime as dt
+from decimal import Decimal, InvalidOperation
+
+
+class CatalogIdentityConflict(ValueError):
+ def __init__(self, kind, detail, *, identity=None):
+ self.conflict = {'kind':kind,'detail':detail,'resolution':'explicit_source_reconciliation',
+ **({'acquisition_uuid':identity} if identity is not None else {})}
+ super().__init__(detail + '; import stopped without merging, renumbering or overwriting acquisition records. Explicit source reconciliation is required.')
+
+
+def _exact(value):
+ return json.dumps(value,sort_keys=True,ensure_ascii=False,allow_nan=False,separators=(',',':'))
+
+
+def _same_rate(left,right):
+ try:
+ a,b=Decimal(str(left)),Decimal(str(right))
+ return a.is_finite() and b.is_finite() and a==b
+ except (InvalidOperation,ValueError,TypeError):
+ return False
+
+
+SCALAR_COLUMNS={
+ 'Experiment':{'experimenter':'experimenter','institution':'institution','lab':'lab','project':'project','rig':'rig','rig_type':'rig_type'},
+ 'Animal':{'props_id':'id','description':'description','sex':'sex','age':'age','weight':'weight','dark_adaptation':'darkAdaptation','species':'species'},
+ 'Preparation':{'bath_solution':'bathSolution','preparation_type':'preparationType','region':'region','array_pitch':'arrayPitch'},
+ 'Cell':{'type':'type'},
+ 'EpochBlock':{'array_pitch':'arrayPitch'},
+ 'Response':{'offset_hours':'inputTimeDotNetDateTimeOffsetOffsetHours','offset_ticks':'inputTimeDotNetDateTimeOffsetTicks'},
+}
+
+
+def _same_scalar(actual,expected):
+ if expected is None:return actual is None
+ if type(expected) in (int,float):return _same_rate(actual,expected)
+ if isinstance(expected,bool):return str(actual)==('1' if expected else '0')
+ return str(actual)==str(expected)
+
+
+def _same_datetime(actual,expected):
+ if expected is None:return actual is None
+ try:
+ source=expected if isinstance(expected,dt.datetime) else dt.datetime.strptime(expected,'%m/%d/%Y %H:%M:%S:%f')
+ recorded=actual if isinstance(actual,dt.datetime) else dt.datetime.fromisoformat(str(actual))
+ except (TypeError,ValueError):return False
+ # The pinned catalog uses DATETIME(0). Historical MySQL modes either truncate
+ # or round fractions; full precision remains in the verified source JSON.
+ # Accept only those documented encodings, never an arbitrary time tolerance.
+ allowed={source,source.replace(microsecond=0)}
+ if source.microsecond>=500000:
+ allowed.add(source.replace(microsecond=0)+dt.timedelta(seconds=1))
+ return recorded in allowed
+
+
+def validate_catalog_identity(experiment, catalog, experiment_id, progress=None):
+ """Check exact membership, multiplicity, parent links and recorded metadata.
+
+ Must run inside the import transaction for both new and same-source imports.
+ Supports repeated display labels and metadata, but never duplicate UUID rows
+ or a UUID linked to a different parent. No database writes are performed.
+ """
+ expected={name:{} for name in ('Experiment','Animal','Preparation','Cell','EpochGroup','EpochBlock','Epoch','Response','Stimulus')}
+ parents={'Animal':'Experiment','Preparation':'Animal','Cell':'Preparation','EpochGroup':'Cell',
+ 'EpochBlock':'EpochGroup','Epoch':'EpochBlock','Response':'Epoch','Stimulus':'Epoch'}
+
+ def add(kind,item,parent=None,device=None):
+ identity=item.get('uuid')
+ if not isinstance(identity,str) or not identity or identity in expected[kind]:
+ raise CatalogIdentityConflict('incoming_uuid_multiplicity',f'Incoming {kind} has missing or repeated acquisition UUID',identity=identity)
+ expected[kind][identity]=(item,parent,device)
+
+ add('Experiment',experiment)
+ for animal in experiment['animals']:
+ add('Animal',animal,experiment['uuid'])
+ for preparation in animal['preparations']:
+ add('Preparation',preparation,animal['uuid'])
+ for cell in preparation['cells']:
+ add('Cell',cell,preparation['uuid'])
+ for group in cell['epoch_groups']:
+ add('EpochGroup',group,cell['uuid'])
+ for block in group['epoch_blocks']:
+ add('EpochBlock',block,group['uuid'])
+ for epoch in block['epochs']:
+ add('Epoch',epoch,block['uuid'])
+ for kind,key in (('Response','responses'),('Stimulus','stimuli')):
+ for device,stream in epoch.get(key,{}).items():
+ add(kind,stream,epoch['uuid'],device)
+
+ indexed={};fetches=0
+ for kind,members in expected.items():
+ relation=getattr(catalog,kind)
+ if kind in ('Response','Stimulus'):
+ # Stream tables have no experiment_id column; restrict through the
+ # exact source epochs with a server-side parent-key semi-join.
+ restriction=(catalog.Epoch & {'experiment_id':experiment_id}).proj(parent_id='id')
+ else:
+ restriction={'id':experiment_id} if kind=='Experiment' else {'experiment_id':experiment_id}
+ records=(relation & restriction).to_dicts();fetches+=1
+ by_uuid={};numeric_ids=set()
+ for row in records:
+ identity=row.get('h5_uuid');number=row.get('id')
+ if identity in by_uuid or number is None or number in numeric_ids:
+ raise CatalogIdentityConflict('catalog_uuid_multiplicity',f'Catalog {kind} has duplicate UUID or numeric identity',identity=identity)
+ by_uuid[identity]=row;numeric_ids.add(number)
+ if set(by_uuid)!=set(members):
+ raise CatalogIdentityConflict('catalog_membership',f'Catalog {kind} UUID membership differs from the parsed source')
+ indexed[kind]=by_uuid
+ for identity,(item,parent,device) in members.items():
+ row=by_uuid[identity]
+ if kind=='Experiment':
+ if row['id']!=experiment_id:
+ raise CatalogIdentityConflict('experiment_identity','Catalog experiment identity differs from its source',identity=identity)
+ if row.get('is_mea') != (item.get('rig_type')=='MEA'):
+ raise CatalogIdentityConflict('catalog_metadata','Catalog Experiment is_mea differs from the recorded rig_type',identity=identity)
+ elif ((kind not in ('Response','Stimulus') and row.get('experiment_id')!=experiment_id)
+ or row.get('parent_id')!=indexed[parents[kind]][parent]['id']):
+ raise CatalogIdentityConflict('catalog_parent',f'Catalog {kind} parent ownership differs from its recorded acquisition',identity=identity)
+ fields=() if kind=='Stimulus' else ('label',) if kind=='Response' else ('label','properties','attributes','parameters')
+ for field in fields:
+ if _exact(row.get(field))!=_exact(item.get(field)):
+ raise CatalogIdentityConflict('catalog_metadata',f'Catalog {kind} {field} differs from the parsed source',identity=identity)
+ for column,key in SCALAR_COLUMNS.get(kind,{}).items():
+ if not _same_scalar(row.get(column),item.get(key)):
+ raise CatalogIdentityConflict('catalog_metadata',f'Catalog {kind} {column} differs from the parsed source',identity=identity)
+ if kind not in ('Response','Stimulus'):
+ time_fields=('start_time','end_time') if kind in ('EpochGroup','EpochBlock','Epoch') else ('start_time',)
+ for field in time_fields:
+ if not _same_datetime(row.get(field),item.get(field)):
+ raise CatalogIdentityConflict('catalog_metadata',f'Catalog {kind} {field} differs from the source timestamp encoding',identity=identity)
+ if kind in ('Response','Stimulus'):
+ if row.get('device_name')!=device or row.get('h5path')!=item.get('h5path'):
+ raise CatalogIdentityConflict('catalog_stream','Catalog stream identity/device/path differs from source',identity=identity)
+ if kind=='Response':
+ for column,key in (('sample_rate','sampleRate'),('sample_rate_units','sampleRateUnits')):
+ same=(_same_rate(row.get(column),item[key]) if column=='sample_rate' and item.get(key) is not None
+ else row.get(column)==item.get(key))
+ if not same:
+ raise CatalogIdentityConflict('catalog_stream',f'Catalog response {column} differs from source',identity=identity)
+ if progress:
+ progress('verifying_catalog',completed=fetches,total=len(expected),unit='tables')
+ # Both blocks and groups store scientific protocol classifications. Fetch
+ # only their distinct referenced protocol keys once, including the sentinel
+ # used for empty/mixed groups; never issue one protocol query per group.
+ protocols_by_object={}
+ for identity,(item,_,_) in expected['EpochBlock'].items():
+ name=item.get('protocolID')
+ if not isinstance(name,str):
+ raise CatalogIdentityConflict('catalog_protocol','Recorded block protocol identity is missing or invalid',identity=identity)
+ protocols_by_object['EpochBlock',identity]=name
+ for identity,(item,_,_) in expected['EpochGroup'].items():
+ names={protocols_by_object['EpochBlock',block['uuid']] for block in item['epoch_blocks']}
+ protocols_by_object['EpochGroup',identity]=next(iter(names)) if len(names)==1 else 'no_group_protocol'
+ if protocols_by_object:
+ protocol_ids={indexed[kind][identity].get('protocol_id') for kind,identity in protocols_by_object}
+ if None in protocol_ids:
+ raise CatalogIdentityConflict('catalog_protocol','Catalog acquisition protocol reference is missing')
+ protocols=(catalog.Protocol & [{'protocol_id':number} for number in sorted(protocol_ids)]).to_dicts()
+ names={row['protocol_id']:row['name'] for row in protocols}
+ if len(names)!=len(protocols):
+ raise CatalogIdentityConflict('catalog_protocol','Catalog protocol identities are repeated')
+ for (kind,identity),expected_name in protocols_by_object.items():
+ if names.get(indexed[kind][identity]['protocol_id'])!=expected_name:
+ raise CatalogIdentityConflict('catalog_protocol',f'Catalog {kind} protocol classification differs from its recorded blocks',identity=identity)
+ return {kind:len(values) for kind,values in expected.items()}
diff --git a/python/workspace_curation.py b/python/workspace_curation.py
index 96720c9..36d3889 100644
--- a/python/workspace_curation.py
+++ b/python/workspace_curation.py
@@ -137,6 +137,15 @@ def tag_suggestions(self, query='', limit=30):
raise ValueError('Tag suggestion limit must be an integer from 1 to 100')
query = query.strip()
prefix = query.casefold()
+ tracker=getattr(self,'recovery_tracker',None)
+ if tracker is not None and tracker.ready and tracker.token() is not None:
+ from workspace_curation_vocabulary import CurationVocabulary
+ index=getattr(self,'_curation_vocabulary_index',None)
+ if index is None or index.tracker is not tracker:
+ if index is not None:index.close()
+ index=self._curation_vocabulary_index=CurationVocabulary(self,tracker)
+ indexed=index.suggestions(query,limit)
+ if indexed is not None:return indexed
cached = getattr(self, '_tag_vocabulary', None)
if cached is None or cached[0] != self.project_uuid or time.monotonic() >= cached[1]:
rows = (self.Curation & {'project_uuid': self.project_uuid}).proj('tags').to_dicts()
@@ -158,19 +167,25 @@ def tag_suggestions(self, query='', limit=30):
'scope':'project_saved_curation', 'count_unit':'distinct_epochs',
'match':'case_insensitive_prefix', 'history_included':False}
- def _rows(self, protocol_uuid, ids):
+ def _rows(self, protocol_uuid, ids, *, selected_only=False):
if not ids:
return {}
relation = self.Curation & {"project_uuid": self.project_uuid,
"protocol_uuid": protocol_uuid}
- if len(ids) <= 250:
- rows = (relation & [{"epoch_uuid": key} for key in ids]).to_dicts()
- return {row['epoch_uuid']: row for row in rows}
+ if len(ids) <= 250 or selected_only:
+ rows = {}
+ for start in range(0,len(ids),250):
+ selected = (relation & [{"epoch_uuid": key} for key in ids[start:start+250]]).to_dicts()
+ rows.update((row['epoch_uuid'],row) for row in selected)
+ return rows
# Full protocol summaries commonly request tens of thousands of epochs.
# Fetch the sparse saved decisions through the project/protocol key once,
# instead of emitting an enormous OR expression for every epoch UUID.
+ saved = relation.to_dicts()
+ if not saved:
+ return {}
allowed = set(ids)
- return {row['epoch_uuid']: row for row in relation.to_dicts() if row['epoch_uuid'] in allowed}
+ return {row['epoch_uuid']: row for row in saved if row['epoch_uuid'] in allowed}
@staticmethod
def _state(row, fingerprint):
@@ -190,6 +205,24 @@ def read(self, protocol_uuid, epoch_ids, fingerprints):
rows = self._rows(protocol_uuid, ids)
return {key: self._state(rows.get(key), fingerprints[key]) for key in ids}
+ def summary_decisions(self, protocol_uuid, epoch_ids, fingerprints):
+ """Only decisions that differ from default summary counts; no tag JSON.
+
+ The caller supplies already verified protocol membership. Approval is
+ counted only against its exact current metadata fingerprint.
+ """
+ protocol_uuid=_uuid(protocol_uuid)
+ allowed=set(epoch_ids)
+ rows=(self.Curation & {'project_uuid':self.project_uuid,'protocol_uuid':protocol_uuid}
+ & [{'included':False},{'review_state':'approved'}]).proj('included','review_state','metadata_fingerprint').to_dicts()
+ excluded,approved=set(),set()
+ for row in rows:
+ key=row['epoch_uuid']
+ if key not in allowed:continue
+ if not row['included']:excluded.add(key)
+ if row['review_state']=='approved' and row['metadata_fingerprint']==fingerprints[key]:approved.add(key)
+ return excluded,approved
+
@contextlib.contextmanager
def _transaction(self, protocol_uuid):
# Serialize our read/check/write sequence, including concurrent first
@@ -218,7 +251,7 @@ def _event(self, actor, action, payload):
return identity
def update(self, protocol_uuid, epoch_ids, changes, expected_revisions, fingerprints, actor,
- *, inclusion_by_epoch=None, audit_context=None, expected_binding_version=None):
+ *, inclusion_by_epoch=None, audit_context=None, expected_binding_version=None, generation_preflight=None):
protocol_uuid = _uuid(protocol_uuid)
ids, fingerprints = _scope(epoch_ids, fingerprints)
if not ids:
@@ -235,12 +268,17 @@ def update(self, protocol_uuid, epoch_ids, changes, expected_revisions, fingerpr
actor = _actor(actor)
_expectations(ids, expected_revisions)
with self._transaction(protocol_uuid):
+ if generation_preflight is not None:generation_preflight()
provider = getattr(self, 'binding_provider', None)
if provider and expected_binding_version is not None:
- binding = provider(protocol_uuid)
+ header_provider=getattr(self,'binding_header_provider',None)
+ binding = (header_provider or provider)(protocol_uuid)
if (binding['version'] if binding else 0) != expected_binding_version:
raise RevisionConflict({'binding_version': binding['version'] if binding else 0})
- rows = self._rows(protocol_uuid, ids)
+ # Mutation preflight is proportional to its explicit targets, even
+ # for a batch larger than one SQL restriction chunk. Full summaries
+ # retain the sparse whole-protocol read path above.
+ rows = self._rows(protocol_uuid, ids, selected_only=True)
before = {key: self._state(rows.get(key), fingerprints[key]) for key in ids}
if any(before[key]["revision"] != expected_revisions[key] for key in ids):
raise RevisionConflict(before)
diff --git a/python/workspace_curation_vocabulary.py b/python/workspace_curation_vocabulary.py
new file mode 100644
index 0000000..9871d8d
--- /dev/null
+++ b/python/workspace_curation_vocabulary.py
@@ -0,0 +1,159 @@
+"""Exact, incremental dataset-tag suggestions backed by the bounded SQL feed.
+
+Tag strings and record/tag links use the same normalized disposable index as
+shared annotations. Counts are unions by epoch UUID across protocol records;
+changing one record updates only the affected epochs' vocabulary contributions.
+"""
+from __future__ import annotations
+
+import json
+
+from workspace_shared_tag_index import SharedTagIndex, _serialized
+
+
+class CurationVocabulary(SharedTagIndex):
+ def __init__(self, store, tracker):
+ self.store,self.tracker=store,tracker
+ self.previous_unions={}
+ if store.project_uuid!=tracker.project_uuid or store.dj.conn() is not tracker.connection:
+ raise ValueError('Curation vocabulary and recovery authority must share one project connection')
+ super().__init__(generation=tracker.token,changes=self._changes,
+ records=self._records,validate=self._validated_tags)
+
+ def _open(self):
+ opened=self.connection is not None
+ db=super()._open()
+ if not opened:
+ db.create_function('casefold',1,lambda value:value.casefold(),deterministic=True)
+ db.executescript('''
+ CREATE TABLE vocabulary_counts(tag_id INTEGER PRIMARY KEY REFERENCES tag_values(tag_id) ON DELETE CASCADE,
+ epoch_count INTEGER NOT NULL);
+ CREATE INDEX vocabulary_rank ON vocabulary_counts(epoch_count DESC);
+ CREATE INDEX vocabulary_prefix ON tag_values(casefold(tag));
+ CREATE TEMP TABLE vocabulary_changed_epochs(epoch TEXT COLLATE BINARY PRIMARY KEY) WITHOUT ROWID;
+ ''')
+ return db
+
+ @staticmethod
+ def _validated_tags(row):
+ values=row['tags']
+ if not isinstance(values,list) or any(not isinstance(tag,str) or not tag.strip()
+ or tag!=tag.strip() or len(tag)>255 for tag in values):
+ raise ValueError('Saved tag vocabulary contains an invalid tag record')
+ # The existing vocabulary endpoint counts duplicate saved tag strings
+ # once, and permits more than the shared-annotation100-tag limit.
+ return list(dict.fromkeys(values))
+
+ def _unions(self, keys):
+ db=self._open()
+ db.execute('DELETE FROM vocabulary_changed_epochs')
+ db.executemany('INSERT OR IGNORE INTO vocabulary_changed_epochs VALUES (?)',
+ ((key['target_uuid'],) for key in keys))
+ result={key['target_uuid']:set() for key in keys}
+ for epoch,tag in db.execute("SELECT r.target,v.tag FROM vocabulary_changed_epochs e "
+ "JOIN records r ON r.kind='epoch' AND r.target=e.epoch "
+ 'JOIN tag_memberships m ON m.record_id=r.record_id JOIN tag_values v ON v.tag_id=m.tag_id '
+ 'GROUP BY r.target,v.tag'):
+ result[epoch].add(tag)
+ return result
+
+ def _changes(self, previous):
+ with self.tracker.capture(previous) as plan:
+ if plan is None or not plan.complete:return None
+ names=plan.primary_keys.get('curation',[])
+ if names!=['project_uuid','protocol_uuid','epoch_uuid']:
+ raise ValueError('Unsupported curation identity layout')
+ keys=[{'target_kind':'epoch','target_uuid':key[2],'profile_uuid':key[1]}
+ for key in plan.keys.get('curation',[])]
+ current=plan.watermark
+ plan.verify()
+ self.previous_unions=self._unions(keys)
+ return keys,current
+
+ def _records(self, keys):
+ connection=self.tracker.connection
+ def translated(row):
+ values=row['tags']
+ if isinstance(values,(str,bytes,bytearray)):values=json.loads(values)
+ return {'target_kind':'epoch','target_uuid':row['epoch_uuid'],'profile_uuid':row['protocol_uuid'],
+ 'author_name':'','revision':0,'tags':values}
+ if keys is None:
+ last=None
+ while True:
+ restriction=' AND (protocol_uuid>%s OR (protocol_uuid=%s AND epoch_uuid>%s))' if last else ''
+ args=(self.store.project_uuid,*((last[0],last[0],last[1]) if last else ()))
+ rows=connection.query('SELECT protocol_uuid,epoch_uuid,tags FROM recording_workspace.curation '
+ 'WHERE project_uuid=%s'+restriction+' ORDER BY protocol_uuid,epoch_uuid LIMIT 500',
+ args,as_dict=True,reconnect=False).fetchall()
+ if not rows:return
+ for row in rows:yield translated(row)
+ last=(rows[-1]['protocol_uuid'],rows[-1]['epoch_uuid'])
+ else:
+ for start in range(0,len(keys),200):
+ batch=keys[start:start+200]
+ if not batch:continue
+ restriction=' OR '.join('(protocol_uuid=%s AND epoch_uuid=%s)' for _ in batch)
+ args=(self.store.project_uuid,*(value for key in batch for value in (key['profile_uuid'],key['target_uuid'])))
+ rows=connection.query('SELECT protocol_uuid,epoch_uuid,tags FROM recording_workspace.curation '
+ 'WHERE project_uuid=%s AND ('+restriction+')',args,as_dict=True,reconnect=False).fetchall()
+ for row in rows:yield translated(row)
+
+ def _update_summary(self, keys):
+ db=self.connection
+ if keys is None:
+ db.execute('DELETE FROM vocabulary_counts')
+ db.execute('INSERT INTO vocabulary_counts SELECT m.tag_id,COUNT(DISTINCT r.target) '
+ 'FROM records r JOIN tag_memberships m ON m.record_id=r.record_id GROUP BY m.tag_id')
+ self.previous_unions={}
+ return
+ if not keys:return
+ current=self._unions(keys)
+ delta={}
+ for epoch in self.previous_unions.keys()|current.keys():
+ before=self.previous_unions.get(epoch,set());after=current.get(epoch,set())
+ for tag in before-after:delta[tag]=delta.get(tag,0)-1
+ for tag in after-before:delta[tag]=delta.get(tag,0)+1
+ for tag,amount in delta.items():
+ if not amount:continue
+ found=db.execute('SELECT tag_id FROM tag_values WHERE tag=?',(tag,)).fetchone()
+ if found is None:
+ # Removing the last record for a tag already removed its count
+ # through the tag_values foreign-key cascade.
+ if amount>0:raise ValueError('Tag vocabulary is missing a new tag identity')
+ continue
+ tag_id=found[0]
+ db.execute('INSERT OR IGNORE INTO vocabulary_counts VALUES (?,0)',(tag_id,))
+ db.execute('UPDATE vocabulary_counts SET epoch_count=epoch_count+? WHERE tag_id=?',(amount,tag_id))
+ if db.execute('SELECT 1 FROM vocabulary_counts WHERE epoch_count<=0 LIMIT 1').fetchone():
+ raise ValueError('Tag vocabulary aggregate no longer matches exact memberships')
+ self.previous_unions={}
+
+ @staticmethod
+ def _upper_prefix(prefix):
+ for index in range(len(prefix)-1,-1,-1):
+ if ord(prefix[index])<0x10ffff:
+ following=ord(prefix[index])+1
+ if 0xd800<=following<=0xdfff:following=0xe000
+ return prefix[:index]+chr(following)
+ return None
+
+ @_serialized
+ def suggestions(self, query, limit):
+ if not self.refresh():return None
+ prefix=query.casefold();where='';params=[]
+ if any(0xd800<=ord(character)<=0xdfff for character in prefix):
+ where=' WHERE 0'
+ prefix='' # Canonical MySQL UTF-8 tags cannot contain surrogates.
+ if prefix:
+ where=' WHERE casefold(v.tag)>=?';params.append(prefix)
+ upper=self._upper_prefix(prefix)
+ if upper is not None:where+=' AND casefold(v.tag)';params.append(upper)
+ source=' FROM tag_values v JOIN vocabulary_counts c ON c.tag_id=v.tag_id'+where
+ total=self.connection.execute('SELECT COUNT(*)'+source,params).fetchone()[0]
+ rows=self.connection.execute('SELECT v.tag,c.epoch_count'+source+
+ ' ORDER BY c.epoch_count DESC,v.tag COLLATE TAG_ORDER LIMIT ?',(*params,limit)).fetchall()
+ self._check_generation()
+ return {'tags':[{'tag':tag,'count':count} for tag,count in rows],'q':query,'limit':limit,
+ 'total':total,'has_more':total>limit,'project_uuid':self.store.project_uuid,
+ 'scope':'project_saved_curation','count_unit':'distinct_epochs',
+ 'match':'case_insensitive_prefix','history_included':False}
diff --git a/python/workspace_desktop.py b/python/workspace_desktop.py
new file mode 100644
index 0000000..48bbdf6
--- /dev/null
+++ b/python/workspace_desktop.py
@@ -0,0 +1,760 @@
+"""Authenticated production WSGI services for the immutable desktop runtime.
+
+The Electron coordinator supplies the private capability through the environment.
+This entry point never installs dependencies or terminates scientific workers.
+"""
+from __future__ import annotations
+
+import argparse
+import contextlib
+import fcntl
+import hashlib
+import json
+import os
+from pathlib import Path
+import secrets
+import socket
+import subprocess
+import sys
+import threading
+import time
+import uuid
+from urllib.error import HTTPError
+from urllib.request import Request, urlopen
+
+sys.dont_write_bytecode = True
+# Factories import this module by name. Share the process-local supervisor when
+# the owned entry point was executed as a script rather than imported.
+if __name__ == '__main__':
+ sys.modules['workspace_desktop'] = sys.modules[__name__]
+
+from flask import jsonify, request
+from werkzeug.wrappers import Response
+from werkzeug.wsgi import ClosingIterator
+
+_CONTROL = '/api/desktop/'
+_SERVICES = None
+
+
+class DesktopProjectCompatibilityError(ValueError):
+ """A source project requires an explicit copy before desktop activation."""
+ code = 'legacy_project_requires_migration'
+
+ def __init__(self):
+ super().__init__('This project uses a source database. Create a desktop copy in a separate folder; the original project and database will remain unchanged.')
+
+
+def acquire_project_session(project):
+ """Exclude other app sessions before starting or changing a project DB."""
+ project_lock = Path(project) / '.app-state-session.lock'
+ if project_lock.is_symlink():
+ raise ValueError('Project session lock cannot be a symbolic link')
+ session_lock = project_lock.open('a')
+ try:
+ fcntl.flock(session_lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except BlockingIOError:
+ session_lock.close()
+ raise ValueError('Another app or recovery owns this project; close it before opening') from None
+ return session_lock
+
+
+def _atomic_json(path, value):
+ path = Path(path)
+ if path.is_symlink():
+ raise ValueError('Desktop state cannot be a symbolic link')
+ temporary = path.with_name('.' + path.name + '-' + uuid.uuid4().hex)
+ try:
+ with temporary.open('x') as output:
+ os.chmod(temporary, 0o600)
+ json.dump(value, output)
+ output.flush()
+ os.fsync(output.fileno())
+ os.replace(temporary, path)
+ directory = os.open(path.parent, os.O_RDONLY)
+ try:
+ os.fsync(directory)
+ finally:
+ os.close(directory)
+ finally:
+ temporary.unlink(missing_ok=True)
+
+
+class DesktopBoundary:
+ """Authenticate before Flask; retain admission through response iteration.
+
+ Every request is counted, including GETs that may write projection caches.
+ Asynchronous producers supply explicit busy/pause callbacks; request counts
+ alone never establish permission to stop a database.
+ """
+ def __init__(self, app, *, port, capability, identity, services=None, deadline=30):
+ if not isinstance(capability, str) or len(capability) < 32:
+ raise ValueError('A private desktop capability is required')
+ self.app, self.capability, self.identity = app, capability, identity
+ self.renderer_session = hashlib.sha256((capability + ':renderer').encode()).hexdigest()
+ self.origin = f'http://127.0.0.1:{port}'
+ self.condition = threading.Condition()
+ self.active = 0
+ self.draining = False
+ self.drained = False
+ self.deadline = deadline
+ self.services = services
+ self.stop_callback = None
+ self.stop_scheduled = False
+ self.operation_lock = threading.RLock()
+ self._routes()
+
+ def _busy(self):
+ return (any(self.app.extensions.get(name, lambda: False)()
+ for name in ('app_active_writers', 'project_active_writers'))
+ or bool(self.services and self.services.database_operation_records()))
+
+ def _pause(self):
+ inbox = self.app.extensions.get('h5_inbox')
+ if inbox:
+ inbox.stop()
+
+ def resume(self):
+ with self.operation_lock:
+ if self.stop_scheduled:
+ raise ValueError('Service shutdown was already acknowledged')
+ with self.condition:
+ self.draining = self.drained = False
+ if self.services:
+ with self.services.lock:
+ self.services.draining = False
+ inbox = self.app.extensions.get('h5_inbox')
+ if inbox:
+ inbox.start()
+
+ def drain(self):
+ with self.operation_lock:
+ with self.condition:
+ if self.drained:
+ return True
+ if self._busy():
+ return False
+ self.draining = True
+ if not self.condition.wait_for(lambda: self.active == 0, timeout=self.deadline):
+ self.draining = False
+ return False
+ try:
+ self._pause()
+ if self._busy():
+ self.resume()
+ return False
+ if self.services and not self.services.drain(self.deadline):
+ self.resume()
+ return False
+ except Exception:
+ self.resume()
+ raise
+ self.drained = True
+ return True
+
+ def _routes(self):
+ @self.app.get(_CONTROL + 'health')
+ def desktop_health():
+ return jsonify(**self.identity, ready=not self.draining,
+ draining=self.draining,
+ services=self.services.records() if self.services else [])
+
+ def empty_control():
+ return not request.args and request.get_json(silent=True) == {}
+
+ @self.app.post(_CONTROL + 'drain')
+ def desktop_drain():
+ if not empty_control():
+ return jsonify(error='Drain requires an empty object'), 400
+ try:
+ if not self.drain():
+ return jsonify(ready=False, busy=True, error='Scientific operations are still active'), 409
+ except Exception:
+ self.app.logger.exception('Desktop drain could not be acknowledged')
+ return jsonify(ready=False, error='Service drain could not be verified'), 409
+ return jsonify(ready=True, drained=True)
+
+ @self.app.post(_CONTROL + 'resume')
+ def desktop_resume():
+ if not empty_control() or self.stop_scheduled:
+ return jsonify(error='Service cannot resume'), 409
+ self.resume()
+ return jsonify(ready=True)
+
+ @self.app.post(_CONTROL + 'stop')
+ def desktop_stop():
+ if not empty_control() or not self.drained or self._busy() or not self.stop_callback:
+ return jsonify(error='All writers must acknowledge drain before stop'), 409
+ try:
+ stop_database = self.app.extensions.get('desktop_stop_database')
+ if stop_database:
+ stop_database()
+ if self.services and self.services.records():
+ raise ValueError('Owned project services have not exited')
+ except Exception:
+ self.app.logger.exception('Desktop database shutdown was not acknowledged')
+ return jsonify(error='Database shutdown could not be verified'), 409
+ # Give Waitress the response before closing its sockets. The main
+ # process awaits actual child exit, not this HTTP acknowledgement.
+ self.stop_scheduled = True
+ threading.Timer(.25, self.stop_callback).start()
+ return jsonify(ready=True, stopped=True), 202
+
+ @self.app.post(_CONTROL + 'open-project')
+ def desktop_open():
+ body = request.get_json(silent=True)
+ if not self.services or request.args or not isinstance(body, dict) or set(body) != {'directory', 'project_uuid'}:
+ return jsonify(error='Select an existing project directory and identity'), 400
+ from workspace_projects import _project_record
+ directory = Path(body['directory']).resolve(strict=True)
+ record = _project_record(directory)
+ if record['uuid'] != body['project_uuid']:
+ return jsonify(error='Project identity changed'), 409
+ return jsonify(self.services.open(directory, record['uuid'], None, 300))
+
+ @self.app.post(_CONTROL + 'authorize-project')
+ def desktop_authorize_project():
+ body = request.get_json(silent=True)
+ if (not self.services or request.args or not isinstance(body, dict)
+ or set(body) != {'port'} or type(body['port']) is not int):
+ return jsonify(error='Project authorization requires a registered port'), 400
+ record = next((item for item in self.services.records() if item['port'] == body['port']), None)
+ if not record or record.get('bound') is not True or record.get('recovery_required'):
+ return jsonify(error='Project service ownership is unavailable'), 409
+ try:
+ health = self.services.call(record, 'health')
+ if health.get('ready') is not True:
+ raise ValueError('Project service is not ready')
+ except Exception:
+ return jsonify(error='Project process and listener ownership could not be verified'), 409
+ return jsonify(record=record, health=health)
+
+ @self.app.post(_CONTROL + 'database-operation/start')
+ def desktop_database_start():
+ body = request.get_json(silent=True)
+ if not self.services or request.args or not isinstance(body, dict) or set(body) != {'project_path'}:
+ return jsonify(error='Database operation requires an owned project path'), 400
+ return jsonify(operation_id=self.services.begin_database_operation(body['project_path']))
+
+ @self.app.post(_CONTROL + 'database-operation/finish')
+ def desktop_database_finish():
+ body = request.get_json(silent=True)
+ if not self.services or request.args or not isinstance(body, dict) or set(body) != {'operation_id', 'failed'} or type(body['failed']) is not bool:
+ return jsonify(error='Database completion requires an operation identity'), 400
+ self.services.finish_database_operation(body['operation_id'], failed=body['failed'])
+ return jsonify(ready=True)
+
+ if 'desktop_stop_database' in self.app.extensions:
+ @self.app.post('/api/project/close')
+ def desktop_close_project():
+ if not empty_control():
+ return jsonify(error='Close requires an empty object'), 400
+ if not self.drain():
+ return jsonify(error='Scientific operations are still active'), 409
+ result = desktop_stop()
+ if isinstance(result, tuple) and result[1] == 202:
+ response = result[0]
+ response.set_data(json.dumps({**response.get_json(), 'state': 'closed',
+ 'launcher_url': 'http://127.0.0.1:' + os.environ['RIEKE_DESKTOP_LAUNCHER_PORT'] + '/'}))
+ return result
+
+ def __call__(self, environ, start_response):
+ host = environ.get('HTTP_HOST', '')
+ origin = environ.get('HTTP_ORIGIN')
+ supplied = environ.get('HTTP_X_RIEKE_DESKTOP_CAPABILITY', '')
+ renderer_session = environ.get('HTTP_X_RIEKE_DESKTOP_SESSION', '')
+ control_authorized = supplied.isascii() and secrets.compare_digest(supplied, self.capability)
+ session_authorized = renderer_session.isascii() and secrets.compare_digest(renderer_session, self.renderer_session)
+ if (environ.get('REMOTE_ADDR') != '127.0.0.1'
+ or host != self.origin.removeprefix('http://')
+ or origin not in (None, self.origin)
+ or not (control_authorized or session_authorized)
+ or (environ.get('PATH_INFO', '').startswith(_CONTROL) and not control_authorized)):
+ return Response('Desktop session authorization required', status=403)(environ, start_response)
+ control = environ.get('PATH_INFO', '') in {
+ _CONTROL + name for name in ('health', 'drain', 'resume', 'stop')
+ } or environ.get('PATH_INFO') == '/api/project/close'
+ admitted = False
+ if not control:
+ with self.condition:
+ if self.draining:
+ return Response('Desktop service is draining', status=503)(environ, start_response)
+ self.active += 1
+ admitted = True
+ try:
+ response = self.app(environ, start_response)
+ except BaseException:
+ if admitted:
+ with self.condition:
+ self.active -= 1
+ self.condition.notify_all()
+ raise
+ def finished():
+ if admitted:
+ with self.condition:
+ self.active -= 1
+ self.condition.notify_all()
+ return ClosingIterator(response, finished)
+
+
+class DesktopServices:
+ """Only supervise exact bundled children; never signal a reused PID."""
+ def __init__(self, user_state, resources, manifest, session_id, capability, identity):
+ self.user_state, self.resources = Path(user_state), Path(resources)
+ self.manifest, self.session_id, self.capability = Path(manifest), session_id, capability
+ self.identity = identity
+ self.path = self.user_state / 'desktop-services.json'
+ self.lock = threading.RLock()
+ self.children = {}
+ self.database_operations = {}
+ self.draining = False
+ if self.path.exists():
+ if self.path.is_symlink() or self.path.stat().st_size > 1024 * 1024:
+ raise ValueError('Desktop service registry is invalid')
+ previous = json.loads(self.path.read_text())
+ if not isinstance(previous, dict) or previous.get('version') != 1 or not isinstance(previous.get('services'), list):
+ raise ValueError('Desktop service registry is invalid')
+ import psutil
+ for record in previous['services']:
+ if not isinstance(record, dict) or type(record.get('pid')) is not int:
+ raise ValueError('Desktop service registry is invalid')
+ try:
+ process = psutil.Process(record['pid'])
+ # PID existence alone neither authorizes shutdown nor proves
+ # prior scientific operations finished. Matching processes
+ # require recovery; foreign/reused PIDs are not our services.
+ # A zombie cannot serve HTTP, but its independent database
+ # or inherited import worker may still be active. It must
+ # satisfy the same clean receipt checks as any exited child.
+ if (process.status() != psutil.STATUS_ZOMBIE
+ and process.create_time() == record.get('created_at')
+ and str(Path(process.exe()).resolve()) == record.get('executable')):
+ raise ValueError('A previous desktop project service is still alive; recovery must close it before opening projects')
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ if psutil.pid_exists(record['pid']):
+ raise ValueError('A previous service cannot be inspected; preserve it for recovery')
+ if not self._clean_database(record):
+ if self._rejected_legacy_preflight(record):
+ continue
+ raise ValueError('A previous project service has no clean database exit receipt; recovery is required before opening projects')
+ operations = previous.get('database_operations', [])
+ if not isinstance(operations, list):
+ raise ValueError('Desktop database operation registry is invalid')
+ for operation in operations:
+ if not isinstance(operation, dict) or not self._clean_database(operation):
+ raise ValueError('A prior transfer database has no clean exit receipt; recovery is required before opening projects')
+ self._save()
+
+ @staticmethod
+ def _clean_database(record):
+ try:
+ owner_path = Path(record['project_path']) / 'database/native-owner.json'
+ if owner_path.is_symlink():
+ return False
+ owner = json.loads(owner_path.read_text())
+ if ((Path(record['project_path']) / 'database/native-runtime.json').exists()
+ or (record.get('instance_uuid') is not None and owner.get('instance_uuid') != record['instance_uuid'])):
+ return False
+ return owner.get('clean_shutdown') is True and owner.get('project_uuid') == record['project_uuid']
+ except (OSError, ValueError, KeyError, TypeError):
+ return False
+
+ def _save(self):
+ _atomic_json(self.path, {'version': 1, 'services': [item['record'] for item in self.children.values()],
+ 'database_operations': list(self.database_operations.values())})
+
+ @staticmethod
+ def _rejected_legacy_preflight(record):
+ """Known entry-point legacy rejection happens before native startup."""
+ try:
+ if record.get('bound') is not False:
+ return False
+ root = Path(record['project_path'])
+ database = root / 'database'
+ if root.is_symlink() or database.is_symlink() or (database.exists() and not database.is_dir()):
+ return False
+ for name in ('native-owner.json', 'native-credentials.json', 'native-runtime.json', 'service.json'):
+ artifact = database / name
+ if artifact.exists() or artifact.is_symlink():
+ return False
+ data = database / 'mysql'
+ if data.is_symlink() or (data.exists() and (not data.is_dir() or any(data.iterdir()))):
+ return False
+ if (root / 'project.json').is_symlink() or (root / 'catalog.json').is_symlink():
+ return False
+ project = json.loads((root / 'project.json').read_text())
+ catalog = json.loads((root / 'catalog.json').read_text())
+ provider = catalog['connection']['credential_provider']
+ identity = str(uuid.UUID(record['project_uuid']))
+ return (project.get('format') == 'recording-project' and project.get('version') == 1
+ and catalog.get('format') == 'recording-catalog-reference' and catalog.get('version') == 1
+ and catalog.get('managed_database') is None
+ and project['project_uuid'] == identity
+ and catalog['project_uuid'] == record['project_uuid']
+ and provider['kind'] == 'docker-container-env'
+ and isinstance(provider.get('container'), str) and bool(provider['container'].strip()))
+ except (OSError, ValueError, KeyError, TypeError):
+ return False
+
+ def begin_database_operation(self, project):
+ from workspace_desktop_paths import require_external_data_path
+ root = require_external_data_path(project)
+ if root.is_relative_to(self.resources):
+ raise ValueError('Database operations must remain outside application resources')
+ from workspace_native_mysql import _configuration
+ _, descriptor = _configuration(root)
+ operation_id = str(uuid.uuid4())
+ record = {'operation_id': operation_id, 'project_path': str(root), 'project_uuid': descriptor['project_uuid'],
+ 'instance_uuid': descriptor['instance_uuid'], 'phase': 'running'}
+ with self.lock:
+ if self.draining or any(item['project_path'] == str(root) for item in self.database_operations.values()):
+ raise ValueError('A database operation is unacknowledged or the desktop is draining')
+ self.database_operations[operation_id] = record
+ self._save()
+ return operation_id
+
+ def finish_database_operation(self, operation_id, *, failed=False):
+ with self.lock:
+ record = self.database_operations.get(operation_id)
+ if record is None:
+ raise ValueError('Database operation ownership is unavailable')
+ if failed:
+ record['phase'] = 'failed_shutdown'
+ self._save()
+ return
+ if not self._clean_database(record):
+ raise ValueError('Transfer database did not acknowledge a clean shutdown')
+ del self.database_operations[operation_id]
+ self._save()
+
+ def database_operation_records(self):
+ with self.lock:
+ for identity, record in list(self.database_operations.items()):
+ if record['phase'] == 'failed_shutdown' and self._clean_database(record):
+ del self.database_operations[identity]
+ self._save()
+ return list(self.database_operations.values())
+
+ def records(self):
+ with self.lock:
+ removed = [key for key, item in self.children.items()
+ if item['process'].poll() is not None and (self._clean_database(item['record'])
+ or self._rejected_legacy_preflight(item['record']))]
+ for key in removed:
+ del self.children[key]
+ if removed:
+ self._save()
+ for item in self.children.values():
+ if item['process'].poll() is not None:
+ item['record']['recovery_required'] = True
+ return [dict(item['record']) for item in self.children.values()]
+
+ def call(self, record, operation, body=None, timeout=5):
+ if record.get('bound') is not True:
+ raise ValueError('Owned child has not acknowledged binding its listener')
+ import psutil
+ process = psutil.Process(record['pid'])
+ if (process.create_time() != record['created_at']
+ or str(Path(process.exe()).resolve()) != record['executable']):
+ raise ValueError('Desktop service process ownership changed')
+ url = f"http://127.0.0.1:{record['port']}/api/desktop/{operation}"
+ headers = {'X-Rieke-Desktop-Capability': self.capability}
+ data = None if body is None else json.dumps(body).encode()
+ if data is not None:
+ headers.update({'Content-Type': 'application/json', 'X-Workspace-Request': '1'})
+ with urlopen(Request(url, data=data, headers=headers), timeout=timeout) as response:
+ payload = json.load(response)
+ if operation == 'health':
+ for key in ('pid', 'session_id', 'project_uuid', 'project_path', 'application_version', 'source_commit'):
+ if payload.get(key) != record.get(key):
+ raise ValueError('Desktop service health identity changed')
+ return payload
+
+ def open(self, directory, identity, retinanalysis, timeout):
+ from workspace_desktop_paths import require_external_data_path
+ directory = require_external_data_path(directory)
+ catalog = json.loads((directory / 'catalog.json').read_text())
+ if catalog.get('connection', {}).get('credential_provider', {}).get('kind') != 'native-project':
+ # Reject before spawning/registering a child or touching a source DB.
+ raise DesktopProjectCompatibilityError()
+ with self.lock:
+ if self.draining:
+ raise ValueError('Desktop services are draining')
+ key = str(directory)
+ self.records()
+ if key in self.children:
+ record = self.children[key]['record']
+ if self.call(record, 'health').get('ready'):
+ from workspace_startup_registry import remember_project_path
+ remember_project_path(directory, identity, set_last=True)
+ return {'url': f"http://127.0.0.1:{record['port']}/", 'project_uuid': identity}
+ raise ValueError('Project service startup or shutdown is still active')
+ with socket.socket() as listener:
+ listener.bind(('127.0.0.1', 0))
+ port = listener.getsockname()[1]
+ log_path = directory / 'logs' / 'workspace-launch.log'
+ log_path.parent.mkdir(exist_ok=True)
+ if log_path.is_symlink():
+ raise ValueError('Project launch log cannot be a symbolic link')
+ command = [sys.executable, str(Path(__file__).resolve()), '--resources', str(self.resources),
+ '--manifest', str(self.manifest), '--user-state', str(self.user_state),
+ '--session-id', self.session_id, '--port', str(port), '--project-dir', str(directory)]
+ read_fd, ready_fd = os.pipe()
+ command.extend(['--ready-fd', str(ready_fd)])
+ try:
+ with log_path.open('ab') as log:
+ process = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=log, stderr=log,
+ pass_fds=(ready_fd,))
+ except BaseException:
+ os.close(read_fd)
+ raise
+ finally:
+ os.close(ready_fd)
+ import psutil
+ record = {**self.identity, 'pid': process.pid, 'port': port, 'session_id': self.session_id,
+ 'project_uuid': identity, 'project_path': key,
+ 'created_at': psutil.Process(process.pid).create_time(),
+ 'executable': str(Path(sys.executable).resolve()), 'bound': False}
+ self.children[key] = {'record': record, 'process': process}
+ self._save()
+ bound = threading.Event()
+ packet = {}
+ def read_bound_receipt():
+ with os.fdopen(read_fd, 'r') as pipe:
+ line = pipe.readline(65537)
+ try:
+ if not line.startswith('RIEKE_DESKTOP_BOUND=') or len(line) > 65536:
+ raise ValueError('Malformed owned child bind receipt')
+ value = json.loads(line.split('=', 1)[1])
+ required = ('pid', 'session_id', 'port', 'project_uuid', 'project_path',
+ 'application_version', 'source_commit')
+ if any(value.get(name) != record.get(name) for name in required):
+ raise ValueError('Owned child bind receipt identity mismatch')
+ packet['valid'] = True
+ except (ValueError, TypeError):
+ packet['valid'] = False
+ finally:
+ bound.set()
+ threading.Thread(target=read_bound_receipt, name='rieke-child-bind-receipt', daemon=True).start()
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ if process.poll() is not None:
+ # A startup exception does not prove native database recovery.
+ raise ValueError('Project startup failed; inspect its recovery log before retrying')
+ if not bound.is_set():
+ bound.wait(.2)
+ continue
+ if not packet.get('valid'):
+ raise ValueError('Project listener ownership was not acknowledged; service retained')
+ if not record['bound']:
+ with self.lock:
+ record['bound'] = True
+ self._save()
+ try:
+ if self.call(record, 'health', timeout=.5).get('ready'):
+ from workspace_startup_registry import remember_project_path
+ remember_project_path(directory, identity, set_last=True)
+ return {'url': f'http://127.0.0.1:{port}/', 'project_uuid': identity}
+ except (OSError, ValueError):
+ pass
+ time.sleep(.2)
+ # Startup may hold a database write; neither terminate nor kill it.
+ raise ValueError('Project startup timed out; its service was preserved for recovery')
+
+ def drain(self, timeout):
+ with self.lock:
+ self.draining = True
+ records = self.records()
+ prepared = []
+ try:
+ # Prepare ALL services before permitting any to shut down.
+ for record in records:
+ if not self.call(record, 'drain', {}, timeout=timeout + 2).get('ready'):
+ raise ValueError('Project service did not acknowledge drain')
+ prepared.append(record)
+ for record in prepared:
+ if not self.call(record, 'stop', {}, timeout=timeout + 2).get('stopped'):
+ raise ValueError('Project service did not acknowledge stop')
+ deadline = time.monotonic() + timeout
+ while self.records() and time.monotonic() < deadline:
+ time.sleep(.1)
+ if self.records():
+ raise ValueError('Owned services have not exited')
+ return True
+ except Exception:
+ for record in prepared:
+ with contextlib.suppress(Exception):
+ self.call(record, 'resume', {}, timeout=2)
+ with self.lock:
+ self.draining = False
+ return False
+
+
+def desktop_open_project(project_dir, identity, retinanalysis_dir, *, timeout=300, managed=False):
+ from workspace_projects import list_managed_projects, list_projects
+ inventory = list_managed_projects(project_dir) if managed else list_projects(project_dir)
+ selected_path = str(Path(project_dir).expanduser().resolve())
+ project = next((row for row in inventory['projects'] if row['uuid'] == identity and row['available']
+ and (managed or row['path'] == selected_path)), None)
+ if project is None:
+ raise ValueError('Select an available registered project')
+ if _SERVICES is None:
+ port = int(os.environ['RIEKE_DESKTOP_LAUNCHER_PORT'])
+ body = json.dumps({'directory': project['path'], 'project_uuid': identity}).encode()
+ headers = {'X-Rieke-Desktop-Capability': os.environ['RIEKE_DESKTOP_CAPABILITY'],
+ 'Content-Type': 'application/json', 'X-Workspace-Request': '1'}
+ try:
+ with urlopen(Request(f'http://127.0.0.1:{port}/api/desktop/open-project', data=body,
+ headers=headers), timeout=timeout + 5) as response:
+ return json.load(response)
+ except HTTPError as error:
+ payload = json.load(error)
+ if payload.get('code') == DesktopProjectCompatibilityError.code:
+ raise DesktopProjectCompatibilityError() from None
+ raise
+ return _SERVICES.open(project['path'], identity, retinanalysis_dir, timeout)
+
+
+def desktop_database_operation(project, operation_id=None, *, failed=False):
+ """Register/ack transient native services with the same global supervisor."""
+ if _SERVICES is not None:
+ if operation_id is None:
+ return _SERVICES.begin_database_operation(project)
+ _SERVICES.finish_database_operation(operation_id, failed=failed)
+ return operation_id
+ port = int(os.environ['RIEKE_DESKTOP_LAUNCHER_PORT'])
+ operation = 'start' if operation_id is None else 'finish'
+ body = {'project_path': str(Path(project).resolve())} if operation_id is None else {'operation_id': operation_id, 'failed': failed}
+ headers = {'X-Rieke-Desktop-Capability': os.environ['RIEKE_DESKTOP_CAPABILITY'],
+ 'X-Workspace-Request': '1', 'Content-Type': 'application/json'}
+ with urlopen(Request(f'http://127.0.0.1:{port}/api/desktop/database-operation/{operation}',
+ data=json.dumps(body).encode(), headers=headers), timeout=15) as response:
+ result = json.load(response)
+ return result['operation_id'] if operation_id is None else operation_id
+
+
+def main(argv=None):
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--resources', type=Path, required=True)
+ parser.add_argument('--manifest', type=Path, required=True)
+ parser.add_argument('--user-state', type=Path, required=True)
+ parser.add_argument('--session-id', required=True)
+ parser.add_argument('--host', default='127.0.0.1', choices=['127.0.0.1'])
+ parser.add_argument('--port', type=int, required=True)
+ parser.add_argument('--project-dir', type=Path)
+ parser.add_argument('--ready-fd', type=int)
+ args = parser.parse_args(argv)
+ session_id = str(uuid.UUID(args.session_id))
+ if not 1024 <= args.port <= 65535:
+ raise ValueError('Desktop port must be an unprivileged TCP port')
+ capability = os.environ.get('RIEKE_DESKTOP_CAPABILITY', '')
+ if len(capability) < 32:
+ raise ValueError('Desktop capability is required')
+ resources = args.resources.resolve(strict=True)
+ runtime = resources / 'runtime'
+ user_state = args.user_state.resolve()
+ if user_state.is_relative_to(resources) or resources.is_relative_to(user_state):
+ raise ValueError('Mutable desktop state must be outside the signed resources')
+ user_state.mkdir(parents=True, exist_ok=True)
+ os.environ.update(RIEKE_DESKTOP_MODE='1', RIEKE_DESKTOP_USER_STATE=str(user_state),
+ RIEKE_DESKTOP_RUNTIME=str(runtime), RIEKE_PARSER_CONFIG=str(user_state / 'parser/config.ini'),
+ RIEKE_DESKTOP_FRONTEND=str(runtime / 'frontend'), PYTHONDONTWRITEBYTECODE='1')
+ os.environ.pop('RIEKE_INSTALLATION_ROOT', None)
+ manifest_path = args.manifest.resolve(strict=True)
+ if manifest_path != (runtime / 'runtime-manifest.json').resolve():
+ raise ValueError('Desktop manifest must be the owned runtime manifest')
+ from workspace_bootstrap import validate_desktop_runtime, prepare_desktop_parser_config
+ manifest = validate_desktop_runtime(runtime, verify_hashes=True)
+ prepare_desktop_parser_config(user_state)
+ # Runtime validation checks the interpreter, resource hashes and installed
+ # parser. Probe all native executables before exposing the chooser.
+ from workspace_mysql_runtime import mysql_runtime
+ mysql_runtime()
+ from workspace_bootstrap import runtime_paths
+ runtime_config = runtime_paths()
+ preference = user_state / 'preferences/workspace-selection.json'
+ if preference.exists():
+ if preference.is_symlink() or preference.stat().st_size > 65536:
+ raise ValueError('Desktop workspace preference is invalid')
+ selected = json.loads(preference.read_text())
+ if selected.get('version') != 1 or not isinstance(selected.get('managed_root'), str):
+ raise ValueError('Desktop workspace preference is invalid')
+ runtime_config['managed_root'] = selected['managed_root']
+ retinanalysis = Path(runtime_config['retinanalysis'])
+ from recording_workspace import load_parser
+ load_parser(retinanalysis)
+ identity = {'pid': os.getpid(), 'session_id': session_id,
+ 'application_version': manifest['application_version'],
+ 'source_commit': manifest['source_commit'],
+ 'workspace_formats': manifest['workspace_formats'],
+ 'database_compatibility': manifest['database_compatibility'],
+ 'project_uuid': None, 'project_path': None}
+ global _SERVICES
+ session_lock = None
+ if args.project_dir:
+ project = args.project_dir.resolve(strict=True)
+ if project.is_relative_to(resources):
+ raise ValueError('Projects must be outside the signed resources')
+ catalog = json.loads((project / 'catalog.json').read_text())
+ if catalog.get('connection', {}).get('credential_provider', {}).get('kind') != 'native-project':
+ raise ValueError('Desktop requires a project-owned native database; migrate legacy projects before opening them')
+ session_lock = acquire_project_session(project)
+ from workspace_project_database import ensure_project_database
+ ensure_project_database(project)
+ from workspace_api import create_app
+ app = create_app(project, retinanalysis, desktop_session_lock=session_lock)
+ identity.update(project_uuid=app.extensions['workspace_service'].project['project_uuid'], project_path=str(project))
+ else:
+ lock_path = user_state / 'desktop-session.lock'
+ if lock_path.is_symlink():
+ raise ValueError('Desktop session lock cannot be a symbolic link')
+ session_lock = lock_path.open('a')
+ fcntl.flock(session_lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+ _SERVICES = DesktopServices(user_state, resources, manifest_path, session_id, capability, identity)
+ os.environ['RIEKE_DESKTOP_LAUNCHER_PORT'] = str(args.port)
+ from workspace_launcher import create_launcher
+ app = create_launcher(runtime_config['managed_root'], retinanalysis,
+ application_dir=runtime / 'application')
+ boundary = DesktopBoundary(app, port=args.port, capability=capability, identity=identity, services=_SERVICES)
+ from waitress import create_server
+ server = create_server(boundary, host=args.host, port=args.port, threads=8,
+ clear_untrusted_proxy_headers=True, max_request_body_size=16 * 1024**3)
+ stopped = threading.Event()
+ def close_server():
+ server.close()
+ # Scientific WSGI requests already acknowledged completion. Close idle
+ # connections so Waitress's event loop exits, then join its dispatcher.
+ # No process signal or writer cancellation is involved.
+ server.asyncore.close_all(map=server._map, ignore_all=True)
+ server.task_dispatcher.shutdown(cancel_pending=False, timeout=30)
+ stopped.set()
+ # Schedule closure on the event-loop thread. Closing sockets from a timer
+ # races select() and gives an otherwise orderly shutdown EBADF.
+ def request_server_stop():
+ server.trigger.pull_trigger(close_server)
+ boundary.stop_callback = request_server_stop
+ app.extensions['shutdown_project_server'] = request_server_stop
+ inbox = app.extensions.get('h5_inbox')
+ if inbox:
+ inbox.start()
+ # The private spawned-process channel proves this listener was successfully
+ # bound before any supervisor sends a capability over loopback HTTP.
+ receipt = 'RIEKE_DESKTOP_BOUND=' + json.dumps({**identity, 'port': args.port, 'ready': True}) + '\n'
+ if args.ready_fd is None:
+ print(receipt, end='', flush=True)
+ else:
+ if args.ready_fd < 3:
+ raise ValueError('Desktop readiness descriptor must be a private pipe')
+ with os.fdopen(args.ready_fd, 'w') as pipe:
+ pipe.write(receipt)
+ pipe.flush()
+ try:
+ server.run()
+ finally:
+ if inbox:
+ inbox.stop()
+ if session_lock:
+ session_lock.close()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/python/workspace_desktop_paths.py b/python/workspace_desktop_paths.py
new file mode 100644
index 0000000..24a9d75
--- /dev/null
+++ b/python/workspace_desktop_paths.py
@@ -0,0 +1,23 @@
+"""Keep desktop project and generated data outside the immutable app bundle."""
+import os
+from pathlib import Path
+
+
+def require_external_data_path(path, *, reject_ancestor=True):
+ """Check resolved parents before any creation, lock or scientific write."""
+ target = Path(path).expanduser().resolve()
+ runtime = os.environ.get('RIEKE_DESKTOP_RUNTIME')
+ if os.environ.get('RIEKE_DESKTOP_MODE') != '1':
+ return target
+ if not runtime:
+ raise ValueError('Desktop runtime ownership is unavailable; project writes are disabled.')
+ runtime = Path(runtime).resolve()
+ resources = runtime.parent
+ # Packaged resources are Resources/runtime. Protect the outer app including
+ # Electron code, not only the Python application subtree. Scratch runtime
+ # tests without an outer .app still protect their entire resource directory.
+ bundle = resources.parent.parent
+ protected = bundle if bundle.suffix == '.app' else resources
+ if target.is_relative_to(protected) or (reject_ancestor and protected.is_relative_to(target)):
+ raise ValueError('Project data and generated files must be stored outside the application bundle. Choose a separate project folder.')
+ return target
diff --git a/python/workspace_disk_index.py b/python/workspace_disk_index.py
index 98f57c2..7bbf1bc 100644
--- a/python/workspace_disk_index.py
+++ b/python/workspace_disk_index.py
@@ -11,8 +11,10 @@
import os
from pathlib import Path
import sqlite3
+import sys
import tempfile
import threading
+import weakref
import zlib
from collections import OrderedDict
from collections.abc import Mapping
@@ -21,10 +23,13 @@
import workspace_tree as tree
import workspace_predicates as predicates
+from workspace_cache_lifecycle import CacheNamespace
+from workspace_metadata_objects import Encoder as MetadataEncoder, Decoder as MetadataDecoder
-FORMAT = 2 # Epoch identity is now an indexed predicate field.
+FORMAT = 3 # Shared ancestors are referenced by source revision and exact UUID.
CHUNK_SIZE = 256
CACHE_SIZE = 64
+DETAIL_CACHE_BYTES = 8 * 1024 * 1024
def _json(value):
@@ -60,9 +65,24 @@ def _raw_definition(field):
class DiskMetadataIndex:
@classmethod
def build(cls, path, rows, details, sources, generation, project_uuid):
+ path=Path(path)
+ namespace=CacheNamespace(path.parent,'metadata',project_uuid)
+ with namespace.writer(path.name):
+ # Another process may have finished this immutable generation while
+ # we waited. Reuse its verified inode so existing lazy readers stay
+ # usable; a reader lease prevents reclamation after this open.
+ try:
+ return cls.open(path,generation,project_uuid)
+ except (OSError,ValueError,sqlite3.DatabaseError):
+ pass
+ return cls._build(path,rows,details,sources,generation,project_uuid)
+
+ @classmethod
+ def _build(cls, path, rows, details, sources, generation, project_uuid):
path = Path(path)
sources = tuple(sources)
path.parent.mkdir(parents=True, exist_ok=True)
+ builder_lease=CacheNamespace(path.parent,'metadata',project_uuid).lease(path.name)
fd, temporary = tempfile.mkstemp(prefix=path.name+'.', suffix='.building', dir=path.parent)
os.close(fd)
connection = sqlite3.connect(temporary)
@@ -82,6 +102,7 @@ def build(cls, path, rows, details, sources, generation, project_uuid):
CREATE INDEX values_reverse ON epoch_values(field_no,value_id,epoch_id);
CREATE TABLE sources(source_sha TEXT PRIMARY KEY,source_json TEXT NOT NULL);
''')
+ metadata_encoder = MetadataEncoder(connection)
connection.executemany('INSERT INTO sources VALUES(?,?)',[(s['source_sha256'],_json(s)) for s in sources])
row_list = list(rows.values()) if isinstance(rows, Mapping) else list(rows)
value_cache = OrderedDict()
@@ -101,7 +122,7 @@ def build(cls, path, rows, details, sources, generation, project_uuid):
identity = row['epoch_uuid']; datum=chunk_details[identity]
fingerprint = hashlib.sha256(json.dumps({'epoch':datum,'source_sha256':row.get('source_sha256')},sort_keys=True,allow_nan=False).encode()).hexdigest()
cursor=connection.execute('INSERT INTO epochs(epoch_uuid,source_sha,cell_uuid,row_json,detail_blob,fingerprint) VALUES(?,?,?,?,?,?)',
- (identity,row.get('source_sha256'),row.get('cell_uuid'),_json(row),zlib.compress(_json(datum).encode()),fingerprint))
+ (identity,row.get('source_sha256'),row.get('cell_uuid'),_json(row),metadata_encoder.encode(row, datum),fingerprint))
epoch_id=cursor.lastrowid
links=[]
for key,value in values[identity].items():
@@ -131,8 +152,12 @@ def build(cls, path, rows, details, sources, generation, project_uuid):
provisional._definitions=[dict(id=key,label=label,category=category,path=field_path) for key,(label,category,field_path) in tree.BASE_FIELDS.items()]
provisional._catalog_cache=None;provisional._predicate_cache=None
catalog=provisional.catalog();predicate_catalog=provisional.predicate_catalog()
- with sqlite3.connect(temporary) as summary_connection:
- summary_connection.executemany('INSERT INTO meta VALUES(?,?)',[('catalog',_json(catalog)),('predicate_catalog',_json(predicate_catalog))])
+ summary_connection=sqlite3.connect(temporary)
+ try:
+ with summary_connection:
+ summary_connection.executemany('INSERT INTO meta VALUES(?,?)',[('catalog',_json(catalog)),('predicate_catalog',_json(predicate_catalog))])
+ finally:
+ summary_connection.close()
seal=dict(format=FORMAT,generation=generation,project_uuid=project_uuid,sha256=_sha(temporary))
seal_path=temporary+'.json'
with open(seal_path,'w') as stream:
@@ -144,29 +169,67 @@ def build(cls, path, rows, details, sources, generation, project_uuid):
connection.close()
for leftover in (temporary,temporary+'.json'):
if os.path.exists(leftover): os.unlink(leftover)
+ builder_lease.close()
@classmethod
def open(cls,path,generation,project_uuid):
- instance=cls();instance.path=Path(path).resolve();instance.generation=generation;instance.project_uuid=project_uuid
- before=_signature(instance.path)
- seal=json.loads(Path(str(instance.path)+'.sha256.json').read_text())
- if any(seal.get(k)!=v for k,v in dict(format=FORMAT,generation=generation,project_uuid=project_uuid).items()):
- raise ValueError('Metadata index generation/project/format mismatch')
- if _sha(instance.path)!=seal.get('sha256') or before!=_signature(instance.path):
- raise ValueError('Metadata index checksum mismatch or changed during verification')
- instance._signature=before;instance._catalog_cache=None;instance._predicate_cache=None
- with instance._connect() as connection:
- metadata={key:json.loads(value) for key,value in connection.execute('SELECT key,value FROM meta')}
- if any(metadata.get(k)!=v for k,v in dict(format=FORMAT,generation=generation,project_uuid=project_uuid,complete=True).items()):
- raise ValueError('Incomplete or incompatible metadata index')
- if connection.execute('PRAGMA quick_check').fetchone()[0]!='ok': raise ValueError('Metadata index integrity check failed')
- instance._definitions=[json.loads(row[0]) for row in connection.execute('SELECT definition_json FROM fields ORDER BY field_no')]
- instance._catalog_cache=metadata.get('catalog');instance._predicate_cache=metadata.get('predicate_catalog')
- instance._epoch_count=connection.execute('SELECT COUNT(*) FROM epochs').fetchone()[0]
- instance.details=_Details(instance)
- return instance
+ location=Path(path)
+ if location.is_symlink():raise ValueError('Derived metadata indexes cannot be symbolic links')
+ namespace=CacheNamespace(location.parent,'metadata',project_uuid)
+ instance=cls();instance.path=location.resolve();instance.generation=generation;instance.project_uuid=project_uuid
+ instance._lease=namespace.lease(location.name)
+ instance._closed=False
+ try:
+ before=_signature(instance.path)
+ seal=json.loads(Path(str(instance.path)+'.sha256.json').read_text())
+ if any(seal.get(k)!=v for k,v in dict(format=FORMAT,generation=generation,project_uuid=project_uuid).items()):
+ raise ValueError('Metadata index generation/project/format mismatch')
+ if _sha(instance.path)!=seal.get('sha256') or before!=_signature(instance.path):
+ raise ValueError('Metadata index checksum mismatch or changed during verification')
+ instance._signature=before;instance._catalog_cache=None;instance._predicate_cache=None
+ with instance._connect() as connection:
+ metadata={key:json.loads(value) for key,value in connection.execute('SELECT key,value FROM meta')}
+ if any(metadata.get(k)!=v for k,v in dict(format=FORMAT,generation=generation,project_uuid=project_uuid,complete=True).items()):
+ raise ValueError('Incomplete or incompatible metadata index')
+ if connection.execute('PRAGMA quick_check').fetchone()[0]!='ok': raise ValueError('Metadata index integrity check failed')
+ instance._definitions=[json.loads(row[0]) for row in connection.execute('SELECT definition_json FROM fields ORDER BY field_no')]
+ instance._catalog_cache=metadata.get('catalog');instance._predicate_cache=metadata.get('predicate_catalog')
+ instance._epoch_count=connection.execute('SELECT COUNT(*) FROM epochs').fetchone()[0]
+ instance._detail_cache=OrderedDict();instance._detail_lock=threading.RLock();instance._details_reference=None
+ instance._detail_cache_costs={};instance._detail_cache_bytes=0
+ instance._metadata_decoder=MetadataDecoder()
+ return instance
+ except BaseException:
+ instance.close();raise
+
+ @property
+ def details(self):
+ # An exported lazy detail view pins this index. The index only holds a
+ # weak reference back to the wrapper, avoiding a reader-lifetime cycle.
+ self._check()
+ with self._detail_lock:
+ result=self._details_reference() if self._details_reference is not None else None
+ if result is None:
+ result=_Details(self);self._details_reference=weakref.ref(result)
+ return result
+
+ def publish(self):
+ """Mark current only after the owning service publishes a valid refresh."""
+ self._check()
+ return CacheNamespace(self.path.parent,'metadata',self.project_uuid).publish([self.path.name])
+
+ def close(self):
+ self._closed=True
+ lease=getattr(self,'_lease',None)
+ if lease is not None:lease.close();self._lease=None
+ cache=getattr(self,'_detail_cache',None)
+ if cache is not None:cache.clear()
+ self._detail_cache_costs={};self._detail_cache_bytes=0
+ decoder=getattr(self,'_metadata_decoder',None)
+ if decoder is not None:decoder.clear()
def _check(self):
+ if getattr(self,'_closed',False):raise ValueError('Metadata index reader is closed')
if _signature(self.path)!=self._signature: raise ValueError('Metadata index changed; reopen a verified generation')
@contextmanager
@@ -211,7 +274,7 @@ def source_projection(self,source_sha,lazy_details=False):
row=json.loads(raw);rows[identity]=row;fingerprints[identity]=fingerprint
datum=None
if not lazy_details:
- datum=json.loads(zlib.decompress(record[3]));details[identity]=datum
+ datum=self._metadata_decoder.decode(connection,row,record[3]);details[identity]=datum
cell_uuid=row.get('cell_uuid')
if cell_uuid in cells: continue
if datum is None: datum=self.details[identity]
@@ -294,6 +357,26 @@ def predicate_catalog(self,ids=None):
return result
def match(self,predicate,ids=None):
+ # Acquisition UUID equality has a dedicated unique index. Validation
+ # needs the known field type, not every recorded UUID decoded twice.
+ # Avoid constructing a project-wide temporary scope for a single ID.
+ epoch_types=next((field['types'] for field in (self._predicate_cache or {}).get('fields',[])
+ if field['id']=='epoch'),None)
+ if (isinstance(predicate,dict) and set(predicate)=={'field','operator','value'}
+ and predicate.get('field')=='epoch' and predicate.get('operator')=='eq'
+ and epoch_types in ([],['string'])):
+ with self._connect([]) as connection:
+ representatives={'type':{'epoch':''}} if epoch_types else {}
+ validated=predicates.validate(predicate,{'fields':self._definitions},representatives)
+ value=validated['value']
+ if not isinstance(value,str):
+ return validated,[]
+ found=connection.execute('SELECT epoch_uuid FROM epochs WHERE epoch_uuid=?',(value,)).fetchone()
+ if found is None:
+ return validated,[]
+ if ids is not None and found[0] not in ids:
+ return validated,[]
+ return validated,[found[0]]
with self._connect(ids) as connection:
referenced=set();pending=[predicate];visited=0
while pending and visited <= predicates.MAX_NODES:
@@ -393,7 +476,7 @@ def values(self):
class _Details(Mapping):
- def __init__(self,index): self.index=index;self.cache=OrderedDict();self.lock=threading.RLock()
+ def __init__(self,index): self.index=index;self.cache=index._detail_cache;self.lock=index._detail_lock
def __len__(self):
self.index._check();return self.index._epoch_count
def __iter__(self): return iter(self.index.values())
@@ -402,10 +485,21 @@ def __getitem__(self,key):
self.index._check()
if key not in self.cache:
with self.index._connect([]) as connection:
- row=connection.execute('SELECT detail_blob FROM epochs WHERE epoch_uuid=?',(key,)).fetchone()
+ row=connection.execute('SELECT row_json,detail_blob FROM epochs WHERE epoch_uuid=?',(key,)).fetchone()
if row is None: raise KeyError(key)
- self.cache[key]=json.loads(zlib.decompress(row[0]))
- if len(self.cache)>CACHE_SIZE: self.cache.popitem(last=False)
+ datum,cost,reusable=self.index._metadata_decoder.decode(connection,json.loads(row[0]),row[1],
+ shared_objects=True,cache_receipt=True)
+ cost+=sys.getsizeof(key)+256
+ # Oversized uncached ancestors must not be multiplied by a
+ # second, count-only epoch cache. Charge the full reachable
+ # detail graph conservatively, including shared ancestors.
+ if not reusable or cost>DETAIL_CACHE_BYTES:
+ return copy.deepcopy(datum)
+ while self.cache and (len(self.cache)>=CACHE_SIZE or self.index._detail_cache_bytes+cost>DETAIL_CACHE_BYTES):
+ removed,_=self.cache.popitem(last=False)
+ self.index._detail_cache_bytes-=self.index._detail_cache_costs.pop(removed)
+ self.cache[key]=datum;self.index._detail_cache_costs[key]=cost
+ self.index._detail_cache_bytes+=cost
self.cache.move_to_end(key);return copy.deepcopy(self.cache[key])
class _ScopedDetails(Mapping):
diff --git a/python/workspace_explorer.py b/python/workspace_explorer.py
index 8788c59..8a30ce9 100644
--- a/python/workspace_explorer.py
+++ b/python/workspace_explorer.py
@@ -57,12 +57,16 @@ def __init__(self, dj, project_uuid, *, event_table=None, revision_table=None, b
self.Binding = protocol_binding_table(dj) if binding_table is None else binding_table
self._recipe_cache = {}
- def protocol_binding(self, protocol_uuid):
+ def protocol_binding_header(self, protocol_uuid):
+ """Read the current binding version without hydrating its frozen recipe."""
protocol_uuid = str(uuid.UUID(protocol_uuid))
rows = (self.Binding & {'project_uuid': self.project_uuid, 'protocol_uuid': protocol_uuid}).to_dicts()
- if not rows:
+ return rows[0] if rows else None
+
+ def protocol_binding(self, protocol_uuid):
+ row = self.protocol_binding_header(protocol_uuid)
+ if row is None:
return None
- row = rows[0]
identity = row['revision_uuid']
if identity not in self._recipe_cache:
self._recipe_cache[identity] = self.get(identity)['recipe']
diff --git a/python/workspace_export_folder.py b/python/workspace_export_folder.py
index 777f0fb..91aaabc 100644
--- a/python/workspace_export_folder.py
+++ b/python/workspace_export_folder.py
@@ -27,6 +27,8 @@
def prepare_export_root(project_dir):
root = Path(project_dir) / 'exports'
+ from workspace_desktop_paths import require_external_data_path
+ require_external_data_path(root)
root.mkdir(parents=True, exist_ok=True)
guide = root / 'README.md'
try:
diff --git a/python/workspace_folder_browser.py b/python/workspace_folder_browser.py
new file mode 100644
index 0000000..8f41ca1
--- /dev/null
+++ b/python/workspace_folder_browser.py
@@ -0,0 +1,164 @@
+"""Read-only, paged local directory navigation for the browser app."""
+from __future__ import annotations
+
+import os
+from pathlib import Path
+import subprocess
+import sys
+from urllib.parse import urlsplit
+
+PAGE_LIMIT = 200
+MAX_ENTRIES = 20_000
+
+
+def _directory(path):
+ if path is None:
+ path = Path.home()
+ if not isinstance(path, (str, Path)) or not str(path).strip():
+ raise ValueError('Choose an absolute folder path.')
+ candidate = Path(str(path).strip()).expanduser()
+ if not candidate.is_absolute():
+ raise ValueError('Choose an absolute folder path.')
+ try:
+ # Resolving supports normal macOS aliases such as /var and /tmp.
+ candidate = candidate.resolve()
+ requested_exists = candidate.exists()
+ exists = requested_exists
+ while not exists:
+ parent = candidate.parent
+ if parent == candidate:
+ raise ValueError('The folder does not exist.')
+ candidate = parent
+ exists = candidate.exists()
+ if not candidate.is_dir():
+ raise ValueError('Choose a folder, not a file.')
+ if not os.access(candidate, os.R_OK | os.X_OK):
+ raise ValueError('Access denied for this folder.')
+ return candidate, requested_exists
+ except PermissionError as error:
+ raise ValueError('Access denied for this folder.') from error
+ except (OSError, RuntimeError) as error:
+ raise ValueError('This folder cannot be accessed. Choose another location.') from error
+
+
+def _locations():
+ home = Path.home()
+ locations, seen = [], set()
+ for name, path in (('Home', home), ('Documents', home / 'Documents'),
+ ('Desktop', home / 'Desktop'), ('Volumes', Path('/Volumes')),
+ ('Computer', Path('/'))):
+ try:
+ if not path.is_dir() or not os.access(path, os.R_OK | os.X_OK):
+ continue
+ path = str(path.resolve())
+ except (OSError, RuntimeError):
+ continue
+ if path not in seen:
+ locations.append({'name': name, 'path': path})
+ seen.add(path)
+ return locations
+
+
+def list_folder(path=None, *, offset=0, limit=PAGE_LIMIT):
+ """List immediate accessible folders, never files or a recursive tree.
+
+ A destination that has not been created opens its nearest existing parent.
+ Page size and the number of inspected directory entries are both bounded.
+ """
+ if type(offset) is not int or offset < 0 or type(limit) is not int or not 1 <= limit <= PAGE_LIMIT:
+ raise ValueError('Use a nonnegative offset and a folder page size from 1 to 200.')
+ root, requested_exists = _directory(path)
+ folders, truncated, empty = [], False, True
+ try:
+ with os.scandir(root) as entries:
+ for index, entry in enumerate(entries):
+ empty = False # Files, hidden names and aliases also occupy the folder.
+ if index >= MAX_ENTRIES:
+ truncated = True
+ break
+ if entry.name.startswith('.'):
+ continue
+ try:
+ if entry.is_symlink() or not entry.is_dir(follow_symlinks=False):
+ continue
+ child = root / entry.name
+ if not os.access(child, os.R_OK | os.X_OK):
+ continue
+ folders.append({'name': entry.name, 'path': str(child)})
+ except OSError:
+ continue # An inaccessible or disappearing child is not selectable.
+ except OSError as error:
+ raise ValueError('Access denied for this folder. Choose another location.') from error
+ folders.sort(key=lambda folder: (folder['name'].casefold(), folder['name']))
+ has_more = offset + limit < len(folders)
+ return {'directory': str(root), 'parent': None if root == root.parent else str(root.parent),
+ 'folders': folders[offset:offset + limit], 'locations': _locations(),
+ 'offset': offset, 'limit': limit, 'total': len(folders), 'has_more': has_more,
+ 'next_offset': offset + limit if has_more else None, 'truncated': truncated,
+ 'empty': empty and not truncated, 'requested_exists': requested_exists}
+
+
+def _open_exports_folder(project_dir):
+ if project_dir is None:
+ raise ValueError('Open a project before opening its exports folder.')
+ try:
+ root = Path(project_dir).expanduser().resolve(strict=True)
+ folder = root / 'exports'
+ if folder.is_symlink():
+ raise ValueError('The exports folder must not be a symbolic link.')
+ resolved = folder.resolve(strict=True)
+ if not root.is_dir() or not resolved.is_dir() or resolved != folder or not resolved.is_relative_to(root):
+ raise ValueError('This project exports folder is unavailable or redirects elsewhere.')
+ except (OSError, RuntimeError) as error:
+ raise ValueError('This project exports folder is unavailable. No folder was created.') from error
+ command = ['open', str(resolved)] if sys.platform == 'darwin' else (
+ ['explorer', str(resolved)] if sys.platform == 'win32' else ['xdg-open', str(resolved)])
+ try:
+ subprocess.run(command, check=True, timeout=10,
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ except (OSError, subprocess.SubprocessError) as error:
+ raise ValueError('The file manager could not open the exports folder. Check that a file manager is available.') from error
+ return str(resolved)
+
+
+def register_folder_browser_routes(app, *, project_dir=None):
+ """Require deliberate same-origin loopback requests for folder operations."""
+ from flask import jsonify, request
+
+ def local_request():
+ if (request.remote_addr not in {'127.0.0.1', '::1'}
+ or urlsplit(request.host_url).hostname not in {'localhost', '127.0.0.1', '::1'}
+ or request.headers.get('X-Workspace-Request') != '1'
+ or request.headers.get('Origin', request.host_url.rstrip('/')) != request.host_url.rstrip('/')):
+ return jsonify(error='Folder operations require a same-origin local app request.'), 403
+
+ @app.get('/api/folders')
+ def folder_browser():
+ boundary = local_request()
+ if boundary is not None:
+ return boundary
+ if (set(request.args) - {'directory', 'offset', 'limit'}
+ or any(len(request.args.getlist(key)) != 1 for key in request.args)):
+ return jsonify(error='Folder browsing accepts one directory, offset and limit only.'), 400
+ numbers = {}
+ for key, default in (('offset', 0), ('limit', PAGE_LIMIT)):
+ value = request.args.get(key, str(default))
+ if not value.isascii() or not value.isdecimal() or len(value) > 9:
+ return jsonify(error='Folder offset and page size must be integers.'), 400
+ numbers[key] = int(value)
+ try:
+ return jsonify(list_folder(request.args.get('directory'), **numbers))
+ except ValueError as error:
+ return jsonify(error=str(error)), 400
+
+ @app.post('/api/exports/open-folder')
+ def open_exports_folder():
+ boundary = local_request()
+ if boundary is not None:
+ return boundary
+ if request.args or request.get_json(silent=True) != {}:
+ return jsonify(error='Opening the exports folder requires an empty object and no query parameters.'), 400
+ try:
+ return jsonify(opened=True, directory=_open_exports_folder(project_dir))
+ except ValueError as error:
+ return jsonify(error=str(error)), 400
diff --git a/python/workspace_import_progress.py b/python/workspace_import_progress.py
index 70c10e9..c00c4a1 100644
--- a/python/workspace_import_progress.py
+++ b/python/workspace_import_progress.py
@@ -16,7 +16,14 @@
'verifying_catalog': 'Checking inserted catalog records', 'catalog_committed': 'Catalog transaction committed',
'finalizing_files': 'Saving protocol query files', 'refreshing_workspace': 'Refreshing the project',
'rerunning_protocols': 'Checking saved protocol queries', 'complete': 'Complete', 'failed': 'Stopped',
- 'duplicate': 'Already imported'}
+ 'duplicate': 'Already imported',
+ 'indexing_shared_annotations': 'Indexing epoch and cell tags',
+ 'preparing_native_tag_lookup': 'Preparing persistent tag lookup',
+ 'preparing_tag_filters': 'Preparing tag filters',
+ 'preparing_tag_summary': 'Preparing tag summaries',
+ 'preparing_tag_suggestions': 'Preparing tag suggestions',
+ 'indexing_dataset_tags': 'Indexing dataset tags',
+ 'preparing_protocol_reads': 'Preparing protocol views'}
def utcnow():
diff --git a/python/workspace_installation.py b/python/workspace_installation.py
index 655da69..7d3f11e 100644
--- a/python/workspace_installation.py
+++ b/python/workspace_installation.py
@@ -3,6 +3,7 @@
import json
from pathlib import Path
+from workspace_paths import workspace_root
MANIFEST = '.rieke-workspace.json'
LAUNCHER = 'rieke-workspace.py'
@@ -32,7 +33,7 @@ def initialize_workspace(folder, application):
candidate = Path(folder).expanduser()
if candidate.is_symlink():
raise ValueError('Workspace root cannot be a symbolic link')
- folder, application = candidate.resolve(), Path(application).resolve()
+ folder, application = workspace_root(candidate), Path(application).resolve()
if folder.is_relative_to(application) or application.is_relative_to(folder):
raise ValueError('Workspace storage must be separate from application code')
if (folder / 'project.json').exists():
@@ -42,20 +43,30 @@ def initialize_workspace(folder, application):
if (folder / name).exists() or (folder / name).is_symlink():
raise ValueError(f'{name} already exists; nothing was overwritten. Launch the existing workspace.')
folder.mkdir(parents=True, exist_ok=True)
+ import os
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1':
+ with (folder / MANIFEST).open('x') as handle:
+ json.dump({'format': 'rieke-workspace', 'version': 1}, handle, indent=2)
+ handle.write('\n')
+ return folder
+ from workspace_updates import managed_installation
+ installation = managed_installation(application)
+ entry_point = installation / 'manager.py' if installation else application / 'rieke.py'
+ arguments = ['--installation', str(installation), 'launch', '--'] if installation else ['launch']
launcher = '''#!/usr/bin/env python3
-"""Launch this workspace with its installed Rieke Lab OS checkout."""
+"""Launch this workspace with its installed Rieke Lab OS application."""
from pathlib import Path
import subprocess
import sys
-application = Path(APPLICATION)
-if not (application / 'rieke.py').is_file():
- sys.exit('Application checkout moved or is missing. Edit application in this launcher to its new location.')
+entry_point = Path(ENTRY_POINT)
+if not entry_point.is_file():
+ sys.exit('Application moved or is missing. Edit entry_point in this launcher to its new location.')
raise SystemExit(subprocess.call([
- sys.executable, str(application / 'rieke.py'), 'launch',
+ sys.executable, str(entry_point), *ARGUMENTS,
'--workspace', str(Path(__file__).resolve().parent), *sys.argv[1:]
]))
-'''.replace('APPLICATION', repr(str(application)))
+'''.replace('ENTRY_POINT', repr(str(entry_point))).replace('ARGUMENTS', repr(arguments))
with (folder / LAUNCHER).open('x') as handle:
handle.write(launcher)
(folder / LAUNCHER).chmod(0o755)
@@ -78,7 +89,7 @@ def select_workspace(folder, application):
raise ValueError('Choose an absolute workspace folder path')
if candidate.is_symlink():
raise ValueError('Workspace root cannot be a symbolic link')
- root, application = candidate.resolve(), Path(application).resolve()
+ root, application = workspace_root(candidate), Path(application).resolve()
if root.is_relative_to(application) or application.is_relative_to(root):
raise ValueError('Workspace storage must be separate from application code')
if root.exists() and not root.is_dir():
@@ -92,7 +103,9 @@ def select_workspace(folder, application):
initialize_workspace(root, application)
from workspace_projects import list_managed_projects
list_managed_projects(root) # Validate existing startup preferences before selecting.
- runtime = (Path(os.environ['RIEKE_INSTALLATION_ROOT']).expanduser().resolve() / 'preferences'
+ runtime = (Path(os.environ['RIEKE_DESKTOP_USER_STATE']).expanduser().resolve() / 'preferences'
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1' else
+ Path(os.environ['RIEKE_INSTALLATION_ROOT']).expanduser().resolve() / 'preferences'
if os.environ.get('RIEKE_INSTALLATION_ROOT') else application / '.rieke-runtime')
runtime.mkdir(parents=True, exist_ok=True)
preference = runtime / 'workspace-selection.json'
diff --git a/python/workspace_launcher.py b/python/workspace_launcher.py
index ebc4bff..e79c3fa 100644
--- a/python/workspace_launcher.py
+++ b/python/workspace_launcher.py
@@ -7,13 +7,15 @@
from werkzeug.exceptions import HTTPException
from workspace_projects import create_project, create_project_at, list_managed_projects, list_projects, managed_root
from workspace_project_servers import open_project
-from workspace_startup_registry import remember_project
+from workspace_startup_registry import remember_project_result
def register_project_routes(app, *, retinanalysis_dir, project_dir=None, root=None):
"""Used by both the launcher and an opened project's API."""
from workspace_app_routes import register_app_routes
register_app_routes(app)
+ from workspace_folder_browser import register_folder_browser_routes
+ register_folder_browser_routes(app, project_dir=project_dir)
current = Path(project_dir).resolve() if project_dir else None
root_provider = root if callable(root) else lambda: managed_root(root or current.parent)
@app.get('/api/projects')
@@ -33,8 +35,8 @@ def project_order():
available = {project['path'] for project in inventory['projects']}
if len(paths) != len(available) or len(set(paths)) != len(paths) or set(paths) != available:
raise ValueError('Project list changed; refresh it before rearranging projects')
- from workspace_startup_registry import remember_project_order
- remember_project_order(current.parent if current else root_provider(), paths)
+ from workspace_startup_registry import remember_project_paths_order
+ remember_project_paths_order(paths)
return project_inventory()
@app.post('/api/projects')
@@ -46,24 +48,24 @@ def project_create():
if set(body) - {'name', 'project_directory'}:
raise ValueError('Choose one exact project folder; do not combine it with a parent root')
project = create_project_at(body['project_directory'], body.get('name'))
- return jsonify(project=project, database_status='not_started',
- message='Project created in the selected folder. Opening it prepares its database.'), 201
+ return jsonify(remember_project_result(project['path'], {'project': project, 'database_status': 'not_started',
+ 'message': 'Project created in the selected folder. Opening it prepares its database.'})), 201
selected_root = body.get('root_directory')
if selected_root is not None:
if not isinstance(selected_root, str) or not selected_root.strip() or not Path(selected_root.strip()).expanduser().is_absolute():
raise ValueError('Choose an absolute root folder for the new project')
selected_root = managed_root(selected_root.strip())
project = create_project(selected_root or root_provider(), body.get('name'), directory=body.get('directory'))
- return jsonify(project=project, database_status='not_started',
- message='Empty project created. Opening it prepares its own database; no recording is required.'), 201
+ return jsonify(remember_project_result(project['path'], {'project': project, 'database_status': 'not_started',
+ 'message': 'Empty project created. Opening it prepares its own database; no recording is required.'})), 201
@app.post('/api/projects/inspect-folder')
def project_inspect_folder():
body = request.get_json(silent=True)
if request.args or not isinstance(body, dict) or set(body) != {'directory'}:
raise ValueError('Choose an existing project folder')
- from workspace_project_validation import validate_project_folder
- return jsonify(validate_project_folder(body['directory']))
+ from workspace_project_validation import inspect_project_folder
+ return jsonify(inspect_project_folder(body['directory']))
@app.post('/api/projects/open-folder')
def project_open_folder():
@@ -76,6 +78,9 @@ def project_open_folder():
if not directory.is_dir():
raise ValueError('Project folder does not exist; choose an existing project')
directory = directory.resolve()
+ from workspace_portability import MANIFEST
+ if (directory / MANIFEST).exists() or (directory / MANIFEST).is_symlink():
+ raise ValueError('This is a prepared project copy. Add it through project setup and choose a new project folder to restore it.')
if not (directory / 'project.json').is_file() or not (directory / 'catalog.json').is_file():
raise ValueError('Choose the project folder containing project.json and catalog.json, not its parent workspace')
from workspace_project_validation import validate_project_folder
@@ -85,7 +90,7 @@ def project_open_folder():
if current:
current_record = next(row for row in list_projects(current)['projects'] if row['current'])
if directory == current:
- return jsonify(url=request.host_url, project_uuid=project['uuid'])
+ return jsonify(remember_project_result(directory, {'url': request.host_url, 'project_uuid': project['uuid']}, set_last=True))
if project['uuid'] == current_record['uuid']:
import uuid
from workspace_projects import _read_manifest
@@ -105,7 +110,8 @@ def restored_instance(path):
other_instance = restored_instance(directory)
if not other_instance or other_instance == restored_instance(current):
raise ValueError('This folder duplicates the current project identity; use its original folder')
- return jsonify(open_project(directory, project['uuid'], retinanalysis_dir))
+ result = open_project(directory, project['uuid'], retinanalysis_dir)
+ return jsonify(remember_project_result(directory, result, set_last=True))
@app.post('/api/projects//open')
def project_open(project_uuid):
@@ -118,9 +124,13 @@ def project_open(project_uuid):
if current:
record = next((p for p in list_projects(current)['projects'] if p['current']), None)
if record and record['uuid'] == project_uuid:
- remember_project(root_provider(), project_uuid)
- return jsonify(url=request.host_url, project_uuid=project_uuid)
- return jsonify(open_project(root_provider(), project_uuid, retinanalysis_dir, managed=True))
+ return jsonify(remember_project_result(current, {'url': request.host_url, 'project_uuid': project_uuid}, set_last=True))
+ project = next((project for project in inventory['projects']
+ if project['uuid'] == project_uuid and project['available']), None)
+ if project is None:
+ raise ValueError('Select an available registered project')
+ result = open_project(Path(project['path']), project_uuid, retinanalysis_dir)
+ return jsonify(remember_project_result(project['path'], result, set_last=True))
def create_launcher(root, retinanalysis_dir, *, application_dir=None):
@@ -129,7 +139,8 @@ def create_launcher(root, retinanalysis_dir, *, application_dir=None):
application = Path(application_dir or Path(__file__).resolve().parents[1]).resolve()
app = Flask(__name__, static_folder=None)
app.config.update(MAX_CONTENT_LENGTH=64*1024)
- frontend = Path(__file__).resolve().parents[1] / 'workspace-app/dist'
+ frontend = (Path(os.environ['RIEKE_DESKTOP_FRONTEND']) if os.environ.get('RIEKE_DESKTOP_MODE') == '1'
+ else Path(__file__).resolve().parents[1] / 'workspace-app/dist')
@app.before_request
def local_only():
host = request.host.split(':',1)[0]
@@ -150,6 +161,11 @@ def headers(response):
def error(error):
if isinstance(error,HTTPException):
return jsonify(error=error.description),error.code
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1':
+ from workspace_desktop import DesktopProjectCompatibilityError
+ if isinstance(error, DesktopProjectCompatibilityError):
+ return jsonify(error=str(error), code=error.code, requires_migration=True,
+ migration_endpoint='/api/projects/migrate-source'), 409
if isinstance(error,(ValueError,KeyError,FileNotFoundError)):
return jsonify(error=str(error)),400
app.logger.exception('Project launcher operation failed')
diff --git a/python/workspace_metadata_objects.py b/python/workspace_metadata_objects.py
new file mode 100644
index 0000000..c617a9a
--- /dev/null
+++ b/python/workspace_metadata_objects.py
@@ -0,0 +1,216 @@
+"""Lossless ancestor references inside a sealed, disposable metadata index.
+
+Objects are scoped by source revision, object kind and acquisition UUID. Equal
+names never establish identity. Conflicting content for one identity is rejected;
+the original detail shape is reconstructed for callers and exported unchanged.
+"""
+from collections import OrderedDict
+import copy
+import hashlib
+import json
+import os
+import sys
+import threading
+import uuid
+import zlib
+
+KINDS = {'cell': 'cell_uuid', 'group': 'group_uuid', 'block': 'block_uuid'}
+CACHE_BYTES = 8 * 1024 * 1024
+CACHE_ENTRIES = 128
+_UNSET = object()
+
+
+def canonical(value):
+ return json.dumps(value, sort_keys=True, ensure_ascii=False, separators=(',', ':'), allow_nan=False).encode()
+
+
+def _uuid(value):
+ try:
+ return str(uuid.UUID(value)) if isinstance(value, str) else None
+ except ValueError:
+ return None
+
+
+def _identity(row, kind, value=_UNSET):
+ source = row.get('source_sha256')
+ identity = _uuid(row.get(KINDS[kind]))
+ if not isinstance(source, str) or not source or identity is None:
+ return None
+ if value is not _UNSET:
+ # Unidentified legacy/synthetic metadata stays inline. Do not invent an
+ # ancestor identity from labels, contents or the nearest matching row.
+ if not isinstance(value, dict) or _uuid(value.get('uuid')) is None:
+ return None
+ if _uuid(value['uuid']) != identity:
+ raise ValueError('Ancestor metadata UUID disagrees with epoch ownership')
+ return source, kind, identity
+
+
+def _retained_bytes(value):
+ size = 0
+ pending = [value]
+ seen = set()
+ while pending:
+ item = pending.pop()
+ if id(item) in seen:
+ continue
+ seen.add(id(item))
+ size += sys.getsizeof(item)
+ if isinstance(item, dict):
+ pending.extend(item.keys()); pending.extend(item.values())
+ elif isinstance(item, (list, tuple)):
+ pending.extend(item)
+ return size
+
+
+class _Cache:
+ def __init__(self, budget):
+ self.budget = budget
+ self.values = OrderedDict()
+ self.bytes = 0
+
+ def get(self, key):
+ entry = self.values.get(key)
+ if entry is None:
+ return None
+ self.values.move_to_end(key)
+ return entry[0]
+
+ def put(self, key, value):
+ size = _retained_bytes((key, value))
+ previous = self.values.pop(key, None)
+ if previous:
+ self.bytes -= previous[1]
+ if size > self.budget:
+ return
+ while self.values and (self.bytes + size > self.budget or len(self.values) >= CACHE_ENTRIES):
+ _, (_, removed) = self.values.popitem(last=False)
+ self.bytes -= removed
+ self.values[key] = value, size
+ self.bytes += size
+
+
+class Encoder:
+ def __init__(self, connection, *, cache_bytes=CACHE_BYTES):
+ self.connection = connection
+ self.cache = _Cache(cache_bytes)
+ connection.execute('CREATE TABLE metadata_objects ('
+ 'object_id INTEGER PRIMARY KEY,source_sha TEXT COLLATE BINARY NOT NULL,'
+ 'kind TEXT COLLATE BINARY NOT NULL,object_uuid TEXT COLLATE BINARY NOT NULL,'
+ 'payload BLOB NOT NULL,sha256 BLOB NOT NULL,UNIQUE(source_sha,kind,object_uuid))')
+
+ def _object(self, identity, value):
+ raw = canonical(value)
+ cached = self.cache.get(identity)
+ if cached is not None:
+ number, expected = cached
+ else:
+ saved = self.connection.execute('SELECT object_id,payload,sha256 FROM metadata_objects '
+ 'WHERE source_sha=? AND kind=? AND object_uuid=?', identity).fetchone()
+ if saved is None:
+ number = self.connection.execute('INSERT INTO metadata_objects '
+ '(source_sha,kind,object_uuid,payload,sha256) VALUES (?,?,?,?,?)',
+ (*identity, zlib.compress(raw), hashlib.sha256(raw).digest())).lastrowid
+ expected = raw
+ else:
+ number, payload, digest = saved
+ expected = zlib.decompress(payload)
+ if hashlib.sha256(expected).digest() != digest:
+ raise ValueError('Ancestor metadata checksum differs')
+ self.cache.put(identity, (number, expected))
+ if expected != raw:
+ raise ValueError('Conflicting ancestor metadata for one source UUID')
+ return number
+
+ def partition(self, row, detail):
+ if not isinstance(detail, dict):
+ raise ValueError('Epoch detail must be a metadata object')
+ inline = dict(detail)
+ objects = {}
+ if isinstance(detail.get('metadata'), dict):
+ inline['metadata'] = dict(detail['metadata'])
+ for kind in KINDS:
+ value = detail['metadata'].get(kind)
+ identity = _identity(row, kind, value) if kind in detail['metadata'] else None
+ if identity is not None:
+ objects[kind] = self._object(identity, value)
+ del inline['metadata'][kind]
+ return {'version': 1, 'inline': inline, 'objects': objects}
+
+ def encode(self, row, detail):
+ return zlib.compress(canonical(self.partition(row, detail)))
+
+
+class Decoder:
+ def __init__(self, *, cache_bytes=CACHE_BYTES):
+ self.cache = _Cache(cache_bytes)
+ self.lock = threading.RLock()
+ self.object_decodes = 0
+ self.database_identity = None
+
+ def _bind(self, connection):
+ paths = [row[2] for row in connection.execute('PRAGMA database_list') if row[1] == 'main']
+ if len(paths) != 1:
+ raise ValueError('Metadata decoder requires one main database')
+ if paths[0]:
+ status = os.stat(paths[0])
+ identity = (paths[0], status.st_dev, status.st_ino, status.st_size, status.st_mtime_ns, status.st_ctime_ns)
+ else:
+ # Retain the handle identity; an object-id integer could be reused
+ # after another in-memory database closes.
+ identity = ('memory', connection, getattr(connection, 'total_changes', None))
+ if self.database_identity is None:
+ self.database_identity = identity
+ elif self.database_identity != identity:
+ raise ValueError('Metadata decoder belongs to a different database generation')
+
+ def clear(self):
+ with self.lock:
+ self.cache.values.clear()
+ self.cache.bytes = 0
+
+ def _object(self, connection, number, identity):
+ cached = self.cache.get(number)
+ if cached is None:
+ saved = connection.execute('SELECT source_sha,kind,object_uuid,payload,sha256 '
+ 'FROM metadata_objects WHERE object_id=?', (number,)).fetchone()
+ if saved is None:
+ raise ValueError('Epoch references missing ancestor metadata')
+ raw = zlib.decompress(saved[3])
+ if hashlib.sha256(raw).digest() != saved[4]:
+ raise ValueError('Ancestor metadata checksum differs')
+ value = json.loads(raw)
+ if not isinstance(value, dict) or _uuid(value.get('uuid')) != saved[2]:
+ raise ValueError('Ancestor metadata identity differs')
+ cached = tuple(saved[:3]), value, _retained_bytes(value)
+ self.object_decodes += 1
+ self.cache.put(number, cached)
+ if cached[0] != identity:
+ raise ValueError('Epoch references a different source or ancestor UUID')
+ return cached[1], cached[2]
+
+ def decode(self, connection, row, blob, *, shared_objects=False, cache_receipt=False):
+ document = json.loads(zlib.decompress(blob))
+ if (not isinstance(document, dict) or set(document) != {'version', 'inline', 'objects'}
+ or type(document['version']) is not int or document['version'] != 1 or not isinstance(document['inline'], dict)
+ or not isinstance(document['objects'], dict) or set(document['objects']) - set(KINDS)):
+ raise ValueError('Unsupported indexed epoch metadata')
+ result = document['inline']
+ if document['objects'] and not isinstance(result.get('metadata'), dict):
+ raise ValueError('Invalid indexed ancestor references')
+ retained_bytes = _retained_bytes(result) if cache_receipt else 0
+ with self.lock:
+ self._bind(connection)
+ for kind, number in document['objects'].items():
+ identity = _identity(row, kind)
+ if identity is None or type(number) is not int or number <= 0 or kind in result['metadata']:
+ raise ValueError('Invalid indexed ancestor reference')
+ value, size = self._object(connection, number, identity)
+ retained_bytes += size
+ # Internal detail-cache entries may share immutable ancestors;
+ # public calls receive independent mutable dictionaries.
+ result['metadata'][kind] = value if shared_objects else copy.deepcopy(value)
+ reusable = all(number in self.cache.values for number in document['objects'].values())
+ if cache_receipt:
+ return result, retained_bytes, reusable
+ return result
diff --git a/python/workspace_migration.py b/python/workspace_migration.py
new file mode 100644
index 0000000..6dc66e9
--- /dev/null
+++ b/python/workspace_migration.py
@@ -0,0 +1,258 @@
+"""Explicit read-only source snapshot -> separate project-owned desktop copy.
+
+Legacy Docker is inspected through its existing local Unix socket. No Docker
+command, container start/stop/exec, source SQL write or source-file rewrite occurs.
+Only the bundled mysqldump client and a new destination MySQL runtime execute.
+"""
+from __future__ import annotations
+from contextlib import contextmanager
+import fcntl
+import http.client
+import json
+import os
+from pathlib import Path
+import re
+import shutil
+import socket
+import stat
+import subprocess
+import tempfile
+from urllib.parse import quote
+
+from workspace_portability import (DATABASES, DIRECTORIES, FORMAT, MANIFEST,
+ _artifact_relocations, _database_inventory, _destination, _files, _hash, _inside,
+ _json, _sources, _verify_recording_dependencies, _write, restore_project)
+
+MAX_INSPECT_BYTES = 1024 * 1024
+
+
+class _UnixHTTPConnection(http.client.HTTPConnection):
+ def __init__(self, path):
+ super().__init__('localhost', timeout=5)
+ self.path = str(path)
+
+ def connect(self):
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.sock.settimeout(self.timeout)
+ self.sock.connect(self.path)
+
+
+def inspect_source_container(container):
+ """GET one declared container's inspect record, never execute Docker APIs."""
+ if not isinstance(container, str) or not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]{0,127}', container):
+ raise ValueError('Legacy source container identity is invalid')
+ candidates = (Path.home() / '.docker/run/docker.sock', Path('/var/run/docker.sock'))
+ for candidate in candidates:
+ try:
+ physical = candidate.resolve(strict=True)
+ info = physical.stat()
+ if not stat.S_ISSOCK(info.st_mode) or info.st_uid not in {os.getuid(), 0}:
+ continue
+ connection = _UnixHTTPConnection(physical)
+ try:
+ connection.request('GET', '/containers/' + quote(container, safe='') + '/json',
+ headers={'Accept': 'application/json'})
+ response = connection.getresponse()
+ payload = response.read(MAX_INSPECT_BYTES + 1)
+ if response.status != 200 or len(payload) > MAX_INSPECT_BYTES:
+ raise ValueError('Source Docker inspect did not return a bounded success response')
+ value = json.loads(payload)
+ if not isinstance(value, dict) or not value.get('Id'):
+ raise ValueError('Source Docker inspect identity is invalid')
+ if value.get('State', {}).get('Running') is not True:
+ raise ValueError('The existing source database is stopped. Open it through the source installation before creating a desktop copy.')
+ return value
+ finally:
+ connection.close()
+ except (OSError, http.client.HTTPException, json.JSONDecodeError):
+ continue
+ raise ValueError('The source Docker service is unavailable. Start the existing source database or prepare a transfer in the source installation, then restore that copy here.')
+
+
+def source_connection(root):
+ """Resolve ephemeral credentials from the declared existing source only."""
+ import pymysql
+ catalog = _json(Path(root) / 'catalog.json')
+ declared = catalog.get('connection', {})
+ provider = declared.get('credential_provider', {})
+ if provider.get('kind') != 'docker-container-env' or declared.get('host') not in {'127.0.0.1', 'localhost'}:
+ raise ValueError('Source migration currently supports declared localhost Docker databases only')
+ port = declared.get('port')
+ if type(port) is not int or not 1 <= port <= 65535:
+ raise ValueError('The source project must declare its existing database port')
+ info = inspect_source_container(provider.get('container'))
+ bindings = info.get('NetworkSettings', {}).get('Ports', {}).get('3306/tcp') or []
+ selected = [item for item in bindings if item.get('HostPort') == str(port)
+ and item.get('HostIp') in {'127.0.0.1', '0.0.0.0', '::', ''}]
+ if not selected:
+ raise ValueError('The declared source port differs from the existing Docker database; refresh the source configuration before copying')
+ entries = info.get('Config', {}).get('Env')
+ if not isinstance(entries, list) or len(entries) > 1000 or any(not isinstance(item, str) or len(item) > 65536 for item in entries):
+ raise ValueError('Source credential environment is invalid')
+ environment = dict(item.split('=', 1) for item in entries if '=' in item)
+ password = environment.get('MYSQL_ROOT_PASSWORD')
+ if not isinstance(password, str) or not password:
+ raise ValueError('The declared source database does not expose its existing credential provider')
+ settings = {'host': '127.0.0.1', 'port': port, 'user': 'root', 'password': password}
+ connection = pymysql.connect(**settings, autocommit=True, connect_timeout=10, read_timeout=600, write_timeout=600)
+ try:
+ with connection.cursor() as cursor:
+ cursor.execute('SELECT @@hostname, VERSION()')
+ hostname, version = cursor.fetchone()
+ if hostname != info.get('Config', {}).get('Hostname') or not re.match(r'^8\.', version):
+ raise ValueError('Source server identity or MySQL logical format differs from its declared container')
+ return connection, settings, version
+ except BaseException:
+ connection.close()
+ raise
+
+
+@contextmanager
+def readonly_project_session(root):
+ """Use an existing session lease without creating or modifying source files."""
+ path = Path(root) / '.app-state-session.lock'
+ if path.is_symlink():
+ raise ValueError('Source session lock cannot be a symbolic link')
+ if not path.exists():
+ # Old source projects may predate leases. Consistent SQL and filesystem
+ # inventories before/after below still reject concurrent source changes.
+ yield
+ return
+ with path.open('r') as lease:
+ try:
+ fcntl.flock(lease.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except BlockingIOError:
+ raise ValueError('A source project session or import is still active. Finish its work and close that source session before creating a desktop copy.') from None
+ yield
+
+
+def _source_trigger_contract(connection):
+ from workspace_state_generation import verify_export_triggers
+ class QueryAdapter:
+ def query(self, sql, args=(), *, as_dict=False, **_):
+ with connection.cursor() as cursor:
+ cursor.execute(sql, args)
+ values = cursor.fetchall()
+ names = [column[0] for column in cursor.description] if as_dict else []
+ result = [dict(zip(names, row)) for row in values] if as_dict else values
+ class Result:
+ def fetchall(self):
+ return result
+ return Result()
+ return verify_export_triggers(QueryAdapter(), DATABASES)
+
+
+def _source_dump(settings, path, *, omit_derived_triggers=False):
+ from workspace_native_mysql import native_binary
+ arguments = [str(native_binary('mysqldump')), '--no-defaults', '--protocol=TCP', '--host=127.0.0.1',
+ '--port=' + str(settings['port']), '--user=' + settings['user'], '--single-transaction',
+ '--skip-lock-tables', '--no-tablespaces', '--set-gtid-purged=OFF', '--hex-blob', '--skip-comments',
+ *(['--skip-triggers'] if omit_derived_triggers else []),
+ '--databases', *DATABASES]
+ with path.open('wb') as output:
+ result = subprocess.run(arguments, stdout=output, stderr=subprocess.PIPE, timeout=600,
+ env={**os.environ, 'MYSQL_PWD': settings['password']})
+ if result.returncode or not path.stat().st_size:
+ raise ValueError('Read-only source backup failed; the source was not changed')
+
+
+def snapshot_source_project(project_dir, destination):
+ """Build the existing transfer format without writing the source or its SQL."""
+ from workspace_projects import _project_record
+ raw = Path(project_dir).expanduser()
+ if raw.is_symlink() or not raw.is_dir():
+ raise ValueError('Choose an existing regular source project folder')
+ root = raw.resolve()
+ record = _project_record(root)
+ catalog = _json(root / 'catalog.json')
+ if catalog.get('connection', {}).get('credential_provider', {}).get('kind') != 'docker-container-env':
+ raise ValueError('Choose a legacy source project; native projects use project transfer')
+ target = _destination(destination, root)
+ with readonly_project_session(root):
+ identity_files = {name: _hash(root / name) for name in ('project.json', 'catalog.json')}
+ connection, settings, server_version = source_connection(root)
+ staging = Path(tempfile.mkdtemp(prefix='.rieke-source-snapshot-', dir=target.parent))
+ try:
+ sources = _sources(connection, record['uuid'])
+ source_inventory = _database_inventory(connection)
+ project = _json(root / 'project.json')
+ _write(staging / 'project.json', {key: project[key] for key in
+ ('format', 'version', 'project_uuid', 'name', 'display_name', 'created_at') if key in project})
+ before = {}
+ for directory in DIRECTORIES:
+ source = root / directory
+ if source.exists():
+ before[directory] = _files(source, skip_marker=False)
+ _inside(root, directory)
+ output = staging / directory
+ output.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copytree(source, output)
+ inventory = []
+ for source in sources:
+ manifest = source['manifest']
+ metadata = Path(manifest['metadata_path']).resolve(strict=True)
+ metadata_ref = (metadata.relative_to(root).as_posix() if metadata.is_relative_to(root / 'imports')
+ else 'imports/' + source['source_sha256'] + '/metadata.normalized.json')
+ output_metadata = _inside(staging, metadata_ref)
+ output_metadata.parent.mkdir(parents=True, exist_ok=True)
+ if not output_metadata.exists():
+ shutil.copyfile(metadata, output_metadata)
+ if _hash(output_metadata) != manifest['metadata_sha256']:
+ raise ValueError('Source metadata changed; no desktop copy was published')
+ recording = Path(manifest['source_path']).resolve(strict=True)
+ recording_ref = (recording.relative_to(root).as_posix() if recording.is_relative_to(root / 'raw-uploads')
+ else 'raw-uploads/' + source['source_sha256'] + '.h5')
+ output_recording = _inside(staging, recording_ref)
+ output_recording.parent.mkdir(parents=True, exist_ok=True)
+ if not output_recording.exists():
+ shutil.copyfile(recording, output_recording)
+ if _hash(output_recording) != source['source_sha256']:
+ raise ValueError('Original recording changed; no desktop copy was published')
+ _verify_recording_dependencies(output_recording)
+ inventory.append({'source_sha256': source['source_sha256'], 'recording_ref': recording_ref,
+ 'metadata_ref': metadata_ref, 'metadata_sha256': manifest['metadata_sha256']})
+ # The verified cache-authority triggers are regenerated locally.
+ # Their source-root DEFINER is neither portable nor an authority the
+ # restricted restore account can impersonate. Unknown trigger
+ # definitions are never silently removed from a migration.
+ triggers = _source_trigger_contract(connection)
+ if triggers['managed_present'] and not triggers['safe_to_omit']:
+ raise ValueError(triggers['reason'])
+ _source_dump(settings, staging / 'database.sql',
+ omit_derived_triggers=triggers['managed_present'])
+ if _source_trigger_contract(connection)['contract_fingerprint'] != triggers['contract_fingerprint']:
+ raise ValueError('Source trigger coverage changed during backup; no desktop copy was published')
+ if _database_inventory(connection) != source_inventory:
+ raise ValueError('Source database changed during backup; finish its writes before creating a copy')
+ for directory, saved in before.items():
+ if _files(root / directory, skip_marker=False) != saved:
+ raise ValueError('Source files changed during backup; no desktop copy was published')
+ if any(_hash(root / name) != digest for name, digest in identity_files.items()):
+ raise ValueError('Source identity or configuration changed during backup; no desktop copy was published')
+ _write(staging / MANIFEST, {'format': FORMAT, 'version': 1, 'mode': 'complete',
+ 'database_format': 'mysql8-logical-v1', 'project_uuid': record['uuid'],
+ 'sources': inventory, 'database_inventory': source_inventory, 'files': _files(staging)})
+ target.mkdir(mode=0o700)
+ staging.rename(target)
+ return {'directory': str(target), 'project_uuid': record['uuid'], 'source_count': len(sources),
+ 'source_server_version': server_version, 'source_unchanged': True}
+ finally:
+ connection.close()
+ if staging.exists():
+ shutil.rmtree(staging)
+
+
+def migrate_source_project(project_dir, destination):
+ """Restore a read-only legacy snapshot to the user's new, exclusive folder."""
+ root = Path(project_dir).expanduser().resolve(strict=True)
+ target = _destination(destination, root)
+ parent = Path(tempfile.mkdtemp(prefix='.rieke-migration-', dir=target.parent))
+ try:
+ snapshot = snapshot_source_project(root, parent / 'source-snapshot')
+ result = restore_project(snapshot['directory'], target)
+ from workspace_startup_registry import remember_project_result
+ result = remember_project_result(result['directory'], result)
+ result.update(migrated=True, source_unchanged=True, source_server_version=snapshot['source_server_version'])
+ return result
+ finally:
+ shutil.rmtree(parent)
diff --git a/python/workspace_mysql_runtime.py b/python/workspace_mysql_runtime.py
index 862b109..846ad39 100644
--- a/python/workspace_mysql_runtime.py
+++ b/python/workspace_mysql_runtime.py
@@ -68,6 +68,20 @@ def mysql_runtime(root=ROOT):
"""Return pinned private executable paths without connecting or starting SQL."""
root = Path(root).resolve()
spec, _ = runtime_spec(root)
+ if os.environ.get('RIEKE_DESKTOP_RUNTIME'):
+ # Desktop resources were linked and signed by the build. Launch never
+ # invokes micromamba, changes dependency paths or repairs signatures.
+ runtime = Path(os.environ['RIEKE_DESKTOP_RUNTIME']).resolve(strict=True)
+ manifest = json.loads((runtime / 'runtime-manifest.json').read_text())
+ if (manifest.get('format') != 'rieke-desktop-runtime' or manifest.get('version') != 1
+ or manifest.get('mysql_version') != spec['mysql_version']):
+ raise ValueError('Packaged MySQL compatibility manifest is invalid')
+ prefix = runtime / 'mysql'
+ if prefix.is_symlink() or not prefix.resolve().is_relative_to(runtime):
+ raise ValueError('Packaged MySQL prefix escapes application resources')
+ if hashlib.sha256((runtime / 'mysql-lock.json').read_bytes()).hexdigest() != hashlib.sha256((root / LOCK).read_bytes()).hexdigest():
+ raise ValueError('Packaged MySQL runtime lock differs from application code')
+ return _probe(prefix, spec['mysql_version'])
prefix = root / '.rieke-runtime/mysql'
if prefix.is_symlink():
raise ValueError('Bundled MySQL prefix must not be a symbolic link')
diff --git a/python/workspace_native_tag_filter.py b/python/workspace_native_tag_filter.py
new file mode 100644
index 0000000..c8c5c9f
--- /dev/null
+++ b/python/workspace_native_tag_filter.py
@@ -0,0 +1,122 @@
+"""Compose exact hierarchical predicates from persistent native tag lookups."""
+from __future__ import annotations
+
+FIELDS={'annotations/cell/tags':('cell',), 'annotations/epoch/tags':('epoch',),
+ 'annotations/effective/tags':('cell','epoch')}
+
+
+def matching(rows,filters,predicate,lookup):
+ """Preserve eligible order and intersect cell inheritance with epoch tags."""
+ order=[];cells={}
+ for row in rows:
+ key=row['epoch_uuid'];order.append(key)
+ cells.setdefault(row['cell_uuid'],set()).add(key)
+ universe=set(order)
+ if len(universe)!=len(order):raise ValueError('Expected unique epoch identities in the tag-filter scope')
+ memo={}
+ def expand(targets):
+ result=set()
+ for kind,target in targets:
+ if kind=='epoch':
+ if target in universe:result.add(target)
+ elif kind=='cell':result.update(cells.get(target,()))
+ else:raise ValueError('Invalid native tag target kind')
+ return result
+ def contains(field,tag):
+ if not isinstance(tag,str) or not 1<=len(tag)<=255:return set()
+ key=(field,tag)
+ if key not in memo:memo[key]=expand(lookup.targets(tag,FIELDS[field]))
+ return memo[key]
+ def equal(field,values):
+ if (not isinstance(values,list) or any(not isinstance(tag,str) for tag in values)
+ or values!=sorted(set(values))):return set()
+ result=set(universe)
+ for tag in values:
+ result.intersection_update(contains(field,tag))
+ if not result:return result
+ return result-expand(lookup.targets_outside(values,FIELDS[field]))
+ def walk(node):
+ if 'all' in node:
+ result=set(universe)
+ for child in node['all']:
+ result.intersection_update(walk(child))
+ if not result:break
+ return result
+ if 'any' in node:
+ result=set()
+ for child in node['any']:result.update(walk(child))
+ return result
+ if 'not' in node:return universe-walk(node['not'])
+ field=node['field'];operator=node['operator']
+ if field not in FIELDS:raise ValueError('Unknown shared tag filter field')
+ if operator=='exists':return set(universe)
+ if operator in ('missing','is_null'):return set()
+ value=node['value']
+ if operator=='contains':return contains(field,value)
+ if operator in ('eq','ne'):
+ result=equal(field,value)
+ return universe-result if operator=='ne' else result
+ if operator in ('in','not_in'):
+ result=set()
+ for values in value:result.update(equal(field,values))
+ return universe-result if operator=='not_in' else result
+ return set()
+ result=set(universe)
+ if 'tag' in filters:result.intersection_update(contains('annotations/effective/tags',filters['tag']))
+ if 'tagged' in filters:result.intersection_update(expand(lookup.tagged_targets()))
+ if predicate is not None:result.intersection_update(walk(predicate))
+ return tuple(key for key in order if key in result)
+
+
+def suggestions(lookup,query,limit):
+ """Rank the compact persisted dictionary; memberships only serve point seeks."""
+ from workspace_native_tag_lookup import SCHEMA,TABLE,INDEX,DICTIONARY,AUTHORS
+ lookup.fill_fold_keys()
+ prefix=query.strip().casefold()
+ try:fold=prefix.encode('utf-8')
+ except UnicodeEncodeError:fold=None
+ where='project_uuid=%s';arguments=(lookup.project_uuid,)
+ if fold is None:where+=' AND FALSE'
+ elif fold:
+ where+=' AND fold_key>=%s AND fold_key<%s';arguments+=(fold,fold+b'\xff')
+ dictionary_index='by_prefix' if fold else 'by_rank'
+ total=int(lookup._rows(f'SELECT COUNT(*) AS amount FROM `{SCHEMA}`.`{DICTIONARY}` FORCE INDEX (by_prefix) WHERE '+where,arguments)[0]['amount'])
+ ranked=lookup._rows(f'SELECT tag,target_count FROM `{SCHEMA}`.`{DICTIONARY}` FORCE INDEX ({dictionary_index}) WHERE '+where+
+ ' ORDER BY target_count DESC,fold_key,tag LIMIT %s',arguments+(limit,))
+ selected=[(bytes(row['tag']).decode('utf-8'),int(row['target_count'])) for row in ranked]
+ authors={tag:[] for tag,_ in selected}
+ if selected:
+ placeholders=','.join('%s' for _ in selected)
+ profiles=lookup._rows(f'SELECT tag,profile_uuid FROM `{SCHEMA}`.`{AUTHORS}` '
+ f'WHERE project_uuid=%s AND tag IN ({placeholders})',(lookup.project_uuid,*(tag.encode('utf-8') for tag,_ in selected)))
+ winners=[]
+ for profile in profiles:
+ row=lookup._rows(f'SELECT target_kind,target_uuid FROM `{SCHEMA}`.`{TABLE}` FORCE INDEX ({INDEX}) '
+ 'WHERE project_uuid=%s AND tag=%s AND profile_uuid=%s ORDER BY target_kind DESC,target_uuid DESC LIMIT 1',
+ (lookup.project_uuid,bytes(profile['tag']),profile['profile_uuid']))
+ if len(row)!=1:raise ValueError('Shared annotation authors changed during tag suggestions; retry')
+ winners.append({'tag':profile['tag'],'profile_uuid':profile['profile_uuid'],
+ 'position':(row[0]['target_kind']+'/'+row[0]['target_uuid']).encode('ascii')})
+ by_key={}
+ for row in winners:
+ kind,target=bytes(row['position']).decode('ascii').split('/',1)
+ key=(kind,target,row['profile_uuid'])
+ by_key.setdefault(key,[]).append(bytes(row['tag']).decode('utf-8'))
+ keys=list(by_key)
+ for offset in range(0,len(keys),200):
+ batch=keys[offset:offset+200]
+ # Bind complete canonical primary keys. Joining ASCII lookup fields
+ # to UTF-8 canonical columns would coerce indexed columns and scan.
+ tuples=','.join('(%s,%s,%s)' for _ in batch)
+ source=lookup._rows(f'SELECT target_kind,target_uuid,profile_uuid,author_name '
+ f'FROM `{SCHEMA}`.`shared_annotation` WHERE project_uuid=%s '
+ f'AND (target_kind,target_uuid,profile_uuid) IN ({tuples})',
+ (lookup.project_uuid,*(value for key in batch for value in key)))
+ found=set()
+ for row in source:
+ key=(row['target_kind'],row['target_uuid'],row['profile_uuid']);found.add(key)
+ for tag in by_key[key]:authors[tag].append({'profile_uuid':row['profile_uuid'],'display_name':row['author_name']})
+ if found!=set(batch):raise ValueError('Shared annotation authors changed during tag suggestions; retry')
+ return {'tags':[{'tag':tag,'count':count,'authors':sorted(authors[tag],key=lambda a:a['profile_uuid'])} for tag,count in selected],
+ 'scope':'project_shared_annotations','query':query,'total':total,'limit':limit,
+ 'count_unit':'distinct_annotation_targets','match':'case_insensitive_prefix','has_more':total>limit}
diff --git a/python/workspace_native_tag_lookup.py b/python/workspace_native_tag_lookup.py
new file mode 100644
index 0000000..ec7bf86
--- /dev/null
+++ b/python/workspace_native_tag_lookup.py
@@ -0,0 +1,399 @@
+"""Persistent exact tag lookup maintained transactionally by native SQL triggers.
+
+Canonical JSON remains the annotation record. This table is rebuildable and has
+no application write path. The caller supplies a fresh recovery content proof at
+open/checkpoint and fences queries with the existing native generation authority.
+Ordinary writes to canonical shared_annotation are supported through the SQL
+triggers. Privileged direct rewrites of this app-owned derived table or its proof
+marker are outside the authority contract, as are rewrites of generation rows.
+"""
+from __future__ import annotations
+
+import contextlib
+import json
+import uuid
+import unicodedata
+
+SCHEMA='recording_workspace'
+TABLE='app_shared_tag_lookup'
+MARKER='app_shared_tag_lookup_checkpoint'
+DICTIONARY='app_shared_tag_dictionary'
+AUTHORS='app_shared_tag_authors'
+VERSION=2
+FOLD_VERSION='python-casefold-'+unicodedata.unidata_version
+FOLD_BYTES=1530
+PREFIX='rieke_tag_lookup_v1_'
+INDEX='by_tag'
+COLUMNS=('project_uuid','target_kind','target_uuid','profile_uuid','tag')
+
+
+def _tag(alias):
+ return f"JSON_UNQUOTE(JSON_EXTRACT({alias}.tags,CONCAT('$[',j.ordinal-1,']')))"
+
+
+def _valid(alias,*,duplicates=True):
+ # JSON_TABLE extracts each tag once. The wider staging column cannot trim a
+ # legal 255-character tag; any longer value still fails the explicit bound.
+ tag='j.tag'
+ whitespace=[chr(n).encode('utf-8').hex() for n in range(0x3001) if chr(n).isspace()]
+ ends=','.join('0x'+value for value in whitespace)
+ invalid=(f"JSON_TYPE(JSON_EXTRACT({alias}.tags,CONCAT('$[',j.ordinal-1,']')))!='STRING' "
+ f'OR CHAR_LENGTH({tag}) NOT BETWEEN 1 AND 255 OR BINARY LEFT({tag},1) IN ({ends}) '
+ f'OR BINARY RIGHT({tag},1) IN ({ends}) '
+ # UTF-8 pattern bytes are [U+0000-U+001F]. DEL remains legal.
+ f"OR REGEXP_LIKE({tag},CONVERT(0x5b002d1f5d USING utf8mb4),'c')")
+ duplicate_check=(f" OR EXISTS (SELECT 1 FROM JSON_TABLE({alias}.tags,'$[*]' COLUMNS(tag VARCHAR(1020) CHARACTER SET utf8mb4 PATH '$' ERROR ON ERROR)) d GROUP BY CAST(d.tag AS BINARY) HAVING COUNT(*)>1)" if duplicates else '')
+ return (f"IF JSON_TYPE({alias}.tags)!='ARRAY' OR JSON_LENGTH({alias}.tags)>100 OR EXISTS ("
+ f"SELECT 1 FROM JSON_TABLE({alias}.tags,'$[*]' COLUMNS(ordinal FOR ORDINALITY,"
+ f"tag VARCHAR(1020) CHARACTER SET utf8mb4 PATH '$' ERROR ON ERROR)) j WHERE {invalid}){duplicate_check} "
+ "THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Invalid canonical shared annotation tags'; END IF;")
+
+
+def _insert(alias):
+ return (_valid(alias,duplicates=False)+f' INSERT INTO `{SCHEMA}`.`{TABLE}` ({",".join(COLUMNS)}) '
+ f'SELECT {alias}.project_uuid,{alias}.target_kind,{alias}.target_uuid,{alias}.profile_uuid,'
+ f"CAST(j.tag AS BINARY) FROM JSON_TABLE({alias}.tags,'$[*]' "
+ "COLUMNS(tag VARCHAR(1020) CHARACTER SET utf8mb4 PATH '$' ERROR ON ERROR)) j;")
+
+
+def _delete(alias):
+ # Canonical DataJoint UUID columns are UTF-8. Coerce the source values,
+ # otherwise MySQL converts our ASCII indexed columns and scans the table.
+ comparisons=[f'{field}=CONVERT({alias}.{field} USING ascii) COLLATE ascii_bin'
+ for field in ('project_uuid','target_kind','target_uuid','profile_uuid')]
+ return f'DELETE FROM `{SCHEMA}`.`{TABLE}` WHERE '+ ' AND '.join(comparisons)+';'
+
+
+def _dirty(alias):
+ return (f'UPDATE `{SCHEMA}`.`{MARKER}` SET dirty=1 '
+ f'WHERE project_uuid=CONVERT({alias}.project_uuid USING ascii) COLLATE ascii_bin;')
+
+
+LEGACY_TRIGGER_MANIFEST={PREFIX+suffix:{'table':'shared_annotation','event':event,'timing':'AFTER',
+ 'body':'BEGIN '+body+' END'} for suffix,event,body in (
+ ('ai','INSERT',_insert('NEW')+' '+_dirty('NEW')),
+ ('au','UPDATE',_delete('OLD')+' '+_insert('NEW')+' '+_dirty('OLD')+' '+_dirty('NEW')),
+ ('ad','DELETE',_delete('OLD')+' '+_dirty('OLD')))}
+
+
+def _identity(alias,field):
+ return f'CONVERT({alias}.{field} USING ascii) COLLATE ascii_bin'
+
+
+def _scope_lock(alias):
+ project=f'{alias}.project_uuid' # The existing authority uses canonical UTF-8 UUID columns.
+ return (f'SET v_generation=NULL; SELECT generation INTO v_generation FROM `{SCHEMA}`.`app_state_generation` '
+ f"WHERE project_uuid={project} AND scope_kind='shared_annotations' AND scope_uuid={project} FOR UPDATE; "
+ "IF v_generation IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Annotation generation scope is unavailable'; END IF;")
+
+
+def _tag_loop(alias,adding,other=None):
+ project,kind,target,profile=(_identity(alias,field) for field in ('project_uuid','target_kind','target_uuid','profile_uuid'))
+ path=f"JSON_EXTRACT({alias}.tags,CONCAT('$[',v_i,']'))"
+ check=f'NOT v_same OR NOT JSON_CONTAINS({other}.tags,{path})' if other else 'TRUE'
+ local=f'project_uuid={project} AND target_kind={kind} AND target_uuid={target} AND tag=v_tag'
+ member=f'{local} AND profile_uuid={profile}'
+ # Current locking reads are essential: a transaction may have established a
+ # repeatable-read snapshot before waiting for the project scope lock.
+ probe=(f'SET v_other=NULL; SELECT profile_uuid INTO v_other FROM `{SCHEMA}`.`{TABLE}` FORCE INDEX (PRIMARY) '
+ f'WHERE {local} LIMIT 1 FOR UPDATE; ')
+ if adding:
+ change=(probe+f'INSERT INTO `{SCHEMA}`.`{TABLE}` ({",".join(COLUMNS)}) VALUES ({project},{kind},{target},{profile},v_tag); '
+ f'INSERT INTO `{SCHEMA}`.`{DICTIONARY}` (project_uuid,tag,target_count,fold_key) '
+ f'VALUES ({project},v_tag,IF(v_other IS NULL,1,0),NULL) ON DUPLICATE KEY UPDATE target_count=target_count+VALUES(target_count); '
+ f'INSERT INTO `{SCHEMA}`.`{AUTHORS}` (project_uuid,tag,profile_uuid,membership_count) '
+ f'VALUES ({project},v_tag,{profile},1) ON DUPLICATE KEY UPDATE membership_count=membership_count+1; ')
+ else:
+ change=(f'DELETE FROM `{SCHEMA}`.`{TABLE}` WHERE {member}; '+probe+
+ f'IF v_other IS NULL THEN UPDATE `{SCHEMA}`.`{DICTIONARY}` SET target_count=target_count-1 WHERE project_uuid={project} AND tag=v_tag; END IF; '
+ f'UPDATE `{SCHEMA}`.`{AUTHORS}` SET membership_count=membership_count-1 WHERE project_uuid={project} AND tag=v_tag AND profile_uuid={profile}; ')
+ return (f'SET v_i=0; WHILE v_iFOLD_BYTES:raise ValueError('Unicode casefold exceeds the verified dictionary key bound')
+ self._execute(f'UPDATE `{SCHEMA}`.`{DICTIONARY}` SET fold_key=%s WHERE project_uuid=%s AND tag=%s AND fold_key IS NULL',
+ (fold,self.project_uuid,tag))
+
+ def _triggers(self):
+ rows=self._rows('SELECT TRIGGER_NAME,EVENT_OBJECT_TABLE,EVENT_MANIPULATION,ACTION_TIMING,ACTION_STATEMENT '
+ 'FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s',(SCHEMA,))
+ present={row['TRIGGER_NAME']:row for row in rows if row['TRIGGER_NAME'].startswith(PREFIX)}
+ if set(present)-set(TRIGGER_MANIFEST):raise ValueError('Unknown managed tag lookup trigger')
+ for name,row in present.items():
+ spec=TRIGGER_MANIFEST[name]
+ if any(_normal(row[field])!=_normal(spec[key]) for field,key in
+ (('EVENT_OBJECT_TABLE','table'),('EVENT_MANIPULATION','event'),('ACTION_TIMING','timing'),('ACTION_STATEMENT','body'))):
+ raise ValueError('Existing tag lookup trigger is incompatible')
+ return present
+
+ def _lock_source(self):
+ self._execute(f'SELECT 1 FROM `{SCHEMA}`.`shared_annotation` LIMIT 0')
+ self._execute(f'SELECT 1 FROM `{SCHEMA}`.`{TABLE}` LIMIT 0')
+ self._execute(f'SELECT 1 FROM `{SCHEMA}`.`{MARKER}` LIMIT 0')
+ for table in (DICTIONARY,AUTHORS):self._execute(f'SELECT 1 FROM `{SCHEMA}`.`{table}` LIMIT 0')
+ rows=self._rows(f'SELECT generation FROM `{SCHEMA}`.`app_state_generation` WHERE project_uuid=%s '
+ "AND scope_kind='shared_annotations' AND scope_uuid=%s FOR UPDATE",(self.project_uuid,self.project_uuid))
+ if len(rows)!=1:raise ValueError('Native annotation generation scope must exist before lookup preparation')
+
+ def _validate_source(self):
+ from workspace_annotations import tags
+ after=None
+ while True:
+ where='project_uuid=%s';args=[self.project_uuid]
+ if after is not None:
+ kind,target,profile=after
+ where+=' AND (target_kind>%s OR (target_kind=%s AND target_uuid>%s) OR (target_kind=%s AND target_uuid=%s AND profile_uuid>%s))'
+ args.extend((kind,kind,target,kind,target,profile))
+ rows=self._rows(f'SELECT target_kind,target_uuid,profile_uuid,tags FROM `{SCHEMA}`.`shared_annotation` '
+ f'WHERE {where} ORDER BY target_kind,target_uuid,profile_uuid LIMIT 250',tuple(args))
+ if not rows:break
+ for row in rows:
+ value=row['tags']
+ tags(json.loads(value) if isinstance(value,(str,bytes)) else value)
+ last=rows[-1];after=tuple(last[key] for key in ('target_kind','target_uuid','profile_uuid'))
+
+ def _save_proof(self,proof):
+ proof={'content':proof,'table_ids':self._incarnation(),'fold_version':FOLD_VERSION}
+ self._execute(f'INSERT INTO `{SCHEMA}`.`{MARKER}` (project_uuid,version,proof,dirty) VALUES (%s,%s,%s,0) '
+ 'ON DUPLICATE KEY UPDATE version=VALUES(version),proof=VALUES(proof),dirty=0',
+ (self.project_uuid,VERSION,json.dumps(proof,sort_keys=True,separators=(',',':'))))
+
+ def _incarnation(self):
+ names=tuple(SCHEMA+'/'+table for table in (TABLE,DICTIONARY,AUTHORS))
+ rows=self._rows('SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN (%s,%s,%s)',names)
+ result={row['NAME'].split('/')[-1]:int(row['TABLE_ID']) for row in rows}
+ if set(result)!={TABLE,DICTIONARY,AUTHORS}:raise ValueError('Native tag lookup table incarnation is unavailable')
+ return result
+
+ def validate_current(self):
+ self._validate_schema()
+ if len(self._triggers())!=len(TRIGGER_MANIFEST):
+ raise ValueError('Native tag lookup trigger coverage is incomplete')
+ if self.incarnation is None or self._incarnation()!=self.incarnation:
+ raise ValueError('Native tag lookup table incarnation changed')
+ return True
+
+ def checkpoint(self,proof,*,guard=None):
+ """Caller must verify unchanged lookup authority and fresh source proof."""
+ if not self.ready:raise ValueError('Tag lookup is not prepared')
+ proof=_proof(proof,self.project_uuid)
+ if getattr(self.connection,'in_transaction',True):raise ValueError('Tag checkpoint cannot nest a transaction')
+ with self.connection.transaction:
+ self._lock_source();self.validate_current()
+ if guard is not None and not guard():raise ValueError('Native tag lookup authority changed before checkpoint publication')
+ self._save_proof(proof)
+
+ def prepare(self,proof,*,force=False,guard=None,proof_provider=None):
+ """Install once or rebuild a project after an unmatched content receipt."""
+ locked=False
+ try:
+ proof=_proof(proof,self.project_uuid)
+ if getattr(self.connection,'in_transaction',True):raise ValueError('Tag preparation cannot nest a transaction')
+ lock=self._rows("SELECT GET_LOCK('rieke_native_tag_lookup_v1',10) AS acquired")
+ if len(lock)!=1 or lock[0]['acquired']!=1:raise ValueError('Tag lookup preparation is already running')
+ locked=True
+ existing={row['TABLE_NAME'] for row in self._rows('SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN (%s,%s,%s,%s)',(SCHEMA,TABLE,MARKER,DICTIONARY,AUTHORS))}
+ if TABLE not in existing:self._execute(f'''CREATE TABLE IF NOT EXISTS `{SCHEMA}`.`{TABLE}` (
+ project_uuid varchar(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ target_kind varchar(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ target_uuid varchar(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ profile_uuid varchar(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ tag varbinary(1020) NOT NULL,PRIMARY KEY(project_uuid,target_kind,target_uuid,profile_uuid,tag),
+ KEY {INDEX}(project_uuid,tag,profile_uuid,target_kind,target_uuid)) ENGINE=InnoDB''')
+ if MARKER not in existing:self._execute(f'''CREATE TABLE IF NOT EXISTS `{SCHEMA}`.`{MARKER}` (
+ project_uuid varchar(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ version int NOT NULL,proof json NOT NULL,dirty tinyint unsigned NOT NULL,PRIMARY KEY(project_uuid)) ENGINE=InnoDB''')
+ if DICTIONARY not in existing:self._execute(f'''CREATE TABLE `{SCHEMA}`.`{DICTIONARY}` (
+ project_uuid varchar(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ tag varbinary(1020) NOT NULL,target_count bigint unsigned NOT NULL,fold_key varbinary({FOLD_BYTES}) NULL,
+ PRIMARY KEY(project_uuid,tag),KEY by_prefix(project_uuid,fold_key,tag),
+ KEY by_rank(project_uuid,target_count DESC,fold_key,tag)) ENGINE=InnoDB''')
+ if AUTHORS not in existing:self._execute(f'''CREATE TABLE `{SCHEMA}`.`{AUTHORS}` (
+ project_uuid varchar(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ tag varbinary(1020) NOT NULL,profile_uuid varchar(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ membership_count bigint unsigned NOT NULL,PRIMARY KEY(project_uuid,tag,profile_uuid)) ENGINE=InnoDB''')
+ self._migrate_legacy()
+ self._validate_schema();present=self._triggers();complete=len(present)==len(TRIGGER_MANIFEST)
+ for name,spec in TRIGGER_MANIFEST.items():
+ if name not in present:
+ self._execute(f'CREATE TRIGGER `{SCHEMA}`.`{name}` AFTER {spec["event"]} ON '
+ f'`{SCHEMA}`.`shared_annotation` FOR EACH ROW {spec["body"]}')
+ if proof_provider is not None:proof=_proof(proof_provider(),self.project_uuid)
+ with self.connection.transaction:
+ self._lock_source()
+ self._validate_schema()
+ if len(self._triggers())!=len(TRIGGER_MANIFEST):
+ raise ValueError('Native tag lookup trigger coverage changed during preparation')
+ if guard is not None and not guard():
+ raise ValueError('Native source proof changed before lookup preparation')
+ saved=self._rows(f'SELECT version,proof,dirty FROM `{SCHEMA}`.`{MARKER}` WHERE project_uuid=%s FOR UPDATE',(self.project_uuid,))
+ stored=saved[0]['proof'] if saved else None
+ if isinstance(stored,(str,bytes)):stored=json.loads(stored)
+ expected={'content':proof,'table_ids':self._incarnation(),'fold_version':FOLD_VERSION}
+ self.incarnation=expected['table_ids']
+ self.reused=bool(not force and complete and saved and saved[0]['version']==VERSION and saved[0]['dirty']==0 and stored==expected)
+ if not self.reused:
+ self._validate_source()
+ self._execute(f'DELETE FROM `{SCHEMA}`.`{TABLE}` WHERE project_uuid=%s',(self.project_uuid,))
+ self._execute(f'INSERT INTO `{SCHEMA}`.`{TABLE}` ({",".join(COLUMNS)}) '
+ f'SELECT a.project_uuid,a.target_kind,a.target_uuid,a.profile_uuid,CAST({_tag("a")} AS BINARY) '
+ f'FROM `{SCHEMA}`.`shared_annotation` a JOIN JSON_TABLE(a.tags,\'$[*]\' '
+ "COLUMNS(ordinal FOR ORDINALITY)) j "
+ 'WHERE a.project_uuid=%s',(self.project_uuid,))
+ for table in (AUTHORS,DICTIONARY):self._execute(f'DELETE FROM `{SCHEMA}`.`{table}` WHERE project_uuid=%s',(self.project_uuid,))
+ self._execute(f'INSERT INTO `{SCHEMA}`.`{DICTIONARY}` (project_uuid,tag,target_count,fold_key) '
+ f'SELECT project_uuid,tag,COUNT(DISTINCT target_kind,target_uuid),NULL FROM `{SCHEMA}`.`{TABLE}` WHERE project_uuid=%s GROUP BY project_uuid,tag',(self.project_uuid,))
+ self._execute(f'INSERT INTO `{SCHEMA}`.`{AUTHORS}` (project_uuid,tag,profile_uuid,membership_count) '
+ f'SELECT project_uuid,tag,profile_uuid,COUNT(*) FROM `{SCHEMA}`.`{TABLE}` WHERE project_uuid=%s GROUP BY project_uuid,tag,profile_uuid',(self.project_uuid,))
+ self._save_proof(proof)
+ self.ready=True;self.fill_fold_keys();self.reason=None
+ except Exception as error:
+ self.ready=False;self.reason=str(error)
+ finally:
+ if locked:
+ with contextlib.suppress(Exception):self._execute("SELECT RELEASE_LOCK('rieke_native_tag_lookup_v1')")
+ return self
+
+ def _target_query(self,tag,kinds):
+ if not self.ready:raise ValueError('Native tag lookup is unavailable: '+str(self.reason))
+ kinds=tuple(kinds)
+ if not kinds or any(kind not in ('cell','epoch') for kind in kinds):raise ValueError('Invalid annotation target kinds')
+ args=[self.project_uuid,*kinds]
+ where='project_uuid=%s AND target_kind IN ('+','.join('%s' for _ in kinds)+')'
+ hint=''
+ if tag is not None:
+ if not isinstance(tag,str) or not 1<=len(tag)<=255:raise ValueError('Invalid exact tag')
+ where+=' AND tag=%s';args.append(tag.encode('utf-8'));hint=f' FORCE INDEX ({INDEX})'
+ return f'SELECT DISTINCT target_kind,target_uuid FROM `{SCHEMA}`.`{TABLE}`{hint} WHERE {where}',tuple(args)
+
+ def targets(self,tag,kinds=('cell','epoch')):
+ if tag is None:raise ValueError('An exact tag is required')
+ sql,args=self._target_query(tag,kinds)
+ return {(row['target_kind'],row['target_uuid']) for row in self._rows(sql,args)}
+
+ def tagged_targets(self,kinds=('cell','epoch')):
+ sql,args=self._target_query(None,kinds)
+ return {(row['target_kind'],row['target_uuid']) for row in self._rows(sql,args)}
+
+ def targets_outside(self,tags,kinds=('cell','epoch')):
+ values=tuple(tags)
+ if any(not isinstance(tag,str) or not 1<=len(tag)<=255 for tag in values):
+ raise ValueError('Invalid exact tags')
+ sql,args=self._target_query(None,kinds)
+ if values:
+ sql+=' AND tag NOT IN ('+','.join('%s' for _ in values)+')'
+ args+=tuple(tag.encode('utf-8') for tag in values)
+ return {(row['target_kind'],row['target_uuid']) for row in self._rows(sql,args)}
+
+
+def bootstrap(connection,project_uuid,proof,*,force=False,guard=None,proof_provider=None):
+ return NativeTagLookup(connection,project_uuid).prepare(proof,force=force,guard=guard,proof_provider=proof_provider)
diff --git a/python/workspace_paths.py b/python/workspace_paths.py
new file mode 100644
index 0000000..1611a72
--- /dev/null
+++ b/python/workspace_paths.py
@@ -0,0 +1,21 @@
+"""Reject nested workspace/project roots before creating storage or preferences."""
+from pathlib import Path
+
+
+def workspace_root(folder, *, allow_workspace_descendant=False):
+ candidate = Path(folder).expanduser()
+ if candidate.is_symlink():
+ raise ValueError('Workspace root cannot be a symbolic link')
+ root = candidate.resolve()
+ if root.exists() and not root.is_dir():
+ raise ValueError('Workspace root must be a directory')
+ # Resolve ancestor aliases too: alias/imports/new-root must not create a
+ # second workspace inside the project reached by that alias.
+ for ancestor in (root, *root.parents):
+ marker = ancestor / 'project.json'
+ if marker.exists() or marker.is_symlink():
+ raise ValueError('Choose the parent workspace directory, not a project folder or a folder inside a project')
+ marker = ancestor / '.rieke-workspace.json'
+ if not allow_workspace_descendant and ancestor != root and (marker.exists() or marker.is_symlink()):
+ raise ValueError(f'This folder is inside an existing workspace. Select its root: {ancestor}')
+ return root
diff --git a/python/workspace_portability.py b/python/workspace_portability.py
index 7294d01..9dfeec7 100644
--- a/python/workspace_portability.py
+++ b/python/workspace_portability.py
@@ -7,7 +7,9 @@
from __future__ import annotations
import argparse
-from contextlib import contextmanager
+from contextlib import contextmanager, ExitStack
+import datetime as dt
+from decimal import Decimal
import fcntl
import hashlib
import json
@@ -23,10 +25,153 @@
FORMAT = 'rieke-project-transfer'
MANIFEST = 'transfer.json'
PENDING = '.portable-restore.pending'
-DIRECTORIES = ('imports', 'protocols', 'query-snapshots', 'exports', 'raw-uploads')
+DIRECTORIES = ('imports', 'protocols', 'query-snapshots', 'exports', 'raw-uploads',
+ 'logs/imports', 'logs/app-jobs', 'logs/errors', 'logs/storage')
DATABASES = ('schema', 'recording_workspace')
+def _database_inventory(connection):
+ """Fingerprint every logical table, including historical user-state versions.
+
+ Inspect before rebasing current locators. JSON is canonicalized because MySQL
+ may normalize its whitespace. Hashes are sorted per table so row ordering and
+ tables without primary keys cannot change the result of a logical restore.
+ Only row digests are retained, never complete recording metadata in memory.
+ """
+ from pymysql.cursors import SSCursor
+
+ def encode(value):
+ if isinstance(value, (dt.datetime, dt.date, dt.time)):
+ return {'datetime': value.isoformat()}
+ if isinstance(value, dt.timedelta):
+ return {'timedelta': str(value)}
+ if isinstance(value, Decimal):
+ return {'decimal': str(value)}
+ if isinstance(value, bytes):
+ return {'bytes': value.hex()}
+ raise TypeError(type(value).__name__)
+
+ inventory = {}
+ with connection.cursor() as cursor:
+ cursor.execute('SELECT table_schema, table_name FROM information_schema.tables '
+ 'WHERE table_schema IN (%s,%s) AND table_type=%s ORDER BY table_schema, table_name',
+ (*DATABASES, 'BASE TABLE'))
+ tables = cursor.fetchall()
+ for database, table in tables:
+ # Names come from the actual server, but still quote identifiers.
+ reference = '.'.join('`' + name.replace('`', '``') + '`' for name in (database, table))
+ cursor.execute('SHOW COLUMNS FROM ' + reference)
+ columns = cursor.fetchall()
+ json_columns = [index for index, column in enumerate(columns) if column[1] == 'json']
+ digests = []
+ with connection.cursor(SSCursor) as data_cursor:
+ data_cursor.execute('SELECT * FROM ' + reference)
+ while rows := data_cursor.fetchmany(1000):
+ for row in rows:
+ row = list(row)
+ for index in json_columns:
+ if isinstance(row[index], (str, bytes)):
+ row[index] = json.loads(row[index])
+ data = json.dumps(row, default=encode, sort_keys=True,
+ separators=(',', ':'), allow_nan=False).encode()
+ digests.append(hashlib.sha256(data).digest())
+ checksum = hashlib.sha256()
+ checksum.update(json.dumps([[column[0], column[1]] for column in columns],
+ separators=(',', ':')).encode())
+ digests.sort()
+ for digest in digests:
+ checksum.update(digest)
+ inventory[database + '.' + table] = {'rows': len(digests), 'sha256': checksum.hexdigest()}
+ return inventory
+
+
+def _validate_database_inventory(inventory):
+ if not isinstance(inventory, dict):
+ raise ValueError('Prepared project database inventory is invalid')
+ for table, expected in inventory.items():
+ if (not isinstance(table, str) or not any(table.startswith(database + '.') and
+ len(table) > len(database) + 1 for database in DATABASES)
+ or not isinstance(expected, dict) or set(expected) != {'rows', 'sha256'}
+ or type(expected['rows']) is not int or expected['rows'] < 0
+ or not isinstance(expected['sha256'], str)
+ or not re.fullmatch('[0-9a-f]{64}', expected['sha256'])):
+ raise ValueError('Prepared project database inventory is invalid')
+
+
+def register_empty_project(project_dir, connection=None):
+ """Register a genuinely new native project's SQL identity before its first import.
+
+ Missing identity in a populated project is corruption, never permission to
+ replace its database. Check scientific files and every existing logical table
+ before adding the four initial workspace tables to a still-empty database.
+ """
+ root = Path(project_dir).resolve()
+ project = _json(root / 'project.json')
+ identity = project['project_uuid']
+ provider = _json(root / 'catalog.json')['connection']['credential_provider']
+ if provider.get('kind') != 'native-project':
+ return False
+ owned = connection is None
+ connection = connection or _connection(root)
+ try:
+ with connection.cursor() as cursor:
+ cursor.execute('SELECT table_schema, table_name FROM information_schema.tables '
+ 'WHERE table_schema IN (%s,%s) AND table_type=%s', (*DATABASES, 'BASE TABLE'))
+ tables = cursor.fetchall()
+ if ('recording_workspace', 'project') in tables:
+ cursor.execute('SELECT project_uuid FROM recording_workspace.project')
+ identities = {row[0] for row in cursor.fetchall()}
+ if identities == {identity}:
+ return False
+ if identities:
+ raise ValueError('Database must contain exactly the declared project identity')
+ for directory in ('imports', 'protocols', 'query-snapshots', 'exports', 'raw-uploads'):
+ folder = _inside(root, directory)
+ files = _files(folder, skip_marker=False) if folder.exists() else {}
+ if directory == 'protocols':
+ from workspace_project_preferences import ProjectPreferences, REFERENCE
+ if REFERENCE.removeprefix('protocols/') in files:
+ ProjectPreferences(root, identity).read()
+ files.pop(REFERENCE.removeprefix('protocols/'))
+ if directory == 'exports' and 'README.md' in files:
+ from workspace_export_folder import GUIDE
+ if (folder / 'README.md').read_text() == GUIDE:
+ files.pop('README.md') # Created by API startup before the read model.
+ if files:
+ raise ValueError('Project database identity is missing from a populated project; restore its complete database before opening or sharing')
+ for database, table in tables:
+ if table == '~log':
+ continue # DataJoint infrastructure log records table declarations.
+ reference = '.'.join('`' + name.replace('`', '``') + '`' for name in (database, table))
+ if database == 'recording_workspace' and table == 'annotation_profile':
+ # Older empty projects create a default author on first open,
+ # before the first import writes the Project row.
+ cursor.execute('SELECT 1 FROM ' + reference + ' WHERE project_uuid<>%s LIMIT 1', (identity,))
+ else:
+ cursor.execute('SELECT 1 FROM ' + reference + ' LIMIT 1')
+ if cursor.fetchone() is not None:
+ raise ValueError('Project database identity is missing from a populated project; restore its complete database before opening or sharing')
+ # Same initial definitions as recording_workspace.workspace_tables.
+ cursor.execute('CREATE DATABASE IF NOT EXISTS `schema`')
+ cursor.execute('CREATE DATABASE IF NOT EXISTS recording_workspace')
+ cursor.execute('CREATE TABLE IF NOT EXISTS recording_workspace.project '
+ '(project_uuid varchar(36) PRIMARY KEY, name varchar(255) NOT NULL, directory varchar(1024) NOT NULL)')
+ cursor.execute('CREATE TABLE IF NOT EXISTS recording_workspace.source '
+ '(source_sha256 char(64) PRIMARY KEY, project_uuid varchar(36) NOT NULL, '
+ 'experiment_uuid varchar(36) NOT NULL, experiment_id int NOT NULL, manifest json NOT NULL)')
+ cursor.execute('CREATE TABLE IF NOT EXISTS recording_workspace.event '
+ '(event_uuid varchar(36) PRIMARY KEY, project_uuid varchar(36) NOT NULL, '
+ 'occurred_at datetime NOT NULL, actor varchar(255) NOT NULL, action varchar(63) NOT NULL, payload json NOT NULL)')
+ cursor.execute('CREATE TABLE IF NOT EXISTS recording_workspace.protocol_workspace '
+ '(protocol_uuid varchar(36) PRIMARY KEY, project_uuid varchar(36) NOT NULL, definition json NOT NULL)')
+ cursor.execute('INSERT INTO recording_workspace.project (project_uuid,name,directory) VALUES (%s,%s,%s)',
+ (identity, project['name'], str(root)))
+ return True
+ finally:
+ if owned:
+ connection.close()
+
+
def _json(path):
with Path(path).open(encoding='utf-8') as handle:
return json.load(handle)
@@ -48,6 +193,17 @@ def _hash(path):
return result.hexdigest()
+def _verify_recording_dependencies(path):
+ """A complete transfer must not depend on other H5 files or raw datasets."""
+ import h5py
+ from recording_workspace import validate_self_contained_h5
+ try:
+ with h5py.File(path, 'r') as handle:
+ validate_self_contained_h5(handle)
+ except OSError as error:
+ raise ValueError('Registered recording is not a readable self-contained H5 file: ' + str(path)) from error
+
+
def _inside(root, relative):
if not isinstance(relative, str) or not relative or '\\' in relative:
raise ValueError('Transfer file reference is invalid')
@@ -68,7 +224,8 @@ def _destination(value, source):
path = Path(value).expanduser()
if not path.is_absolute() or path.is_symlink():
raise ValueError('Choose an absolute, new destination folder')
- path = path.resolve()
+ from workspace_desktop_paths import require_external_data_path
+ path = require_external_data_path(path)
if path.exists():
raise ValueError('Destination already exists; nothing was overwritten')
if path.is_relative_to(source) or source.is_relative_to(path):
@@ -150,6 +307,34 @@ def _sources(connection, identity):
def _dump(project_dir, path):
+ from workspace_state_generation import verify_export_triggers
+
+ connection = _connection(project_dir)
+ class QueryAdapter:
+ def query(self, sql, args=(), *, as_dict=False, **_):
+ with connection.cursor() as cursor:
+ cursor.execute(sql, args)
+ values = cursor.fetchall()
+ names = [column[0] for column in cursor.description] if as_dict else []
+ result = [dict(zip(names, row)) for row in values] if as_dict else values
+ class Result:
+ def fetchall(self):
+ return result
+ return Result()
+ try:
+ adapter = QueryAdapter()
+ before = verify_export_triggers(adapter, DATABASES)
+ if before['managed_present'] and not before['safe_to_omit']:
+ raise ValueError(before['reason'])
+ _dump_logical(project_dir, path, omit_derived_triggers=before['managed_present'])
+ after = verify_export_triggers(adapter, DATABASES)
+ if before['contract_fingerprint'] != after['contract_fingerprint']:
+ raise ValueError('Database trigger coverage changed during backup; no transfer was published')
+ finally:
+ connection.close()
+
+
+def _dump_logical(project_dir, path, *, omit_derived_triggers=False):
provider = _json(Path(project_dir) / 'catalog.json')['connection']['credential_provider']
if provider['kind'] == 'native-project':
from workspace_native_mysql import connection_parameters, native_binary
@@ -158,6 +343,7 @@ def _dump(project_dir, path):
'--host=127.0.0.1', '--port=' + str(settings['port']), '--user=' + settings['user'],
'--single-transaction', '--skip-lock-tables', '--no-tablespaces',
'--set-gtid-purged=OFF', '--hex-blob', '--skip-comments',
+ *(['--skip-triggers'] if omit_derived_triggers else []),
'--databases', *DATABASES]
with path.open('wb') as handle:
result = subprocess.run(arguments, stdout=handle, stderr=subprocess.PIPE, timeout=600,
@@ -170,7 +356,8 @@ def _dump(project_dir, path):
container = provider['container']
# Legacy donors remain in their explicitly configured Docker runtime.
# Password remains inside Docker's environment, never an argv or artifact.
- command = 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysqldump -uroot --single-transaction --skip-lock-tables --no-tablespaces --set-gtid-purged=OFF --hex-blob --skip-comments --databases schema recording_workspace'
+ trigger_option = ' --skip-triggers' if omit_derived_triggers else ''
+ command = 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysqldump -uroot --single-transaction --skip-lock-tables --no-tablespaces --set-gtid-purged=OFF --hex-blob --skip-comments' + trigger_option + ' --databases schema recording_workspace'
with path.open('wb') as handle:
result = subprocess.run(['docker', 'exec', container, 'sh', '-c', command], stdout=handle,
stderr=subprocess.PIPE, timeout=600)
@@ -245,16 +432,21 @@ def inspect_package(package_dir):
_inside(root, relative)
if not isinstance(expected, dict) or set(expected) != {'size', 'sha256'} or type(expected['size']) is not int or expected['size'] < 0 or not isinstance(expected['sha256'], str) or not re.fullmatch('[0-9a-f]{64}', expected['sha256']):
raise ValueError('Prepared project file checksum record is invalid')
- if relative not in {'project.json', 'database.sql'} and relative.split('/')[0] not in DIRECTORIES:
+ if relative not in {'project.json', 'database.sql'} and not any(
+ relative.startswith(directory + '/') for directory in DIRECTORIES):
raise ValueError('Prepared project contains unsupported runtime files')
if _files(root) != files:
raise ValueError('Prepared project checksum or file inventory mismatch')
from workspace_projects import _identity
project = _json(root / 'project.json')
- if project.get('format') != 'recording-project' or type(project.get('version')) is not int or project['version'] != 1 or _identity(project.get('project_uuid')) != _identity(value.get('project_uuid')):
+ if not isinstance(project, dict) or project.get('format') != 'recording-project' or type(project.get('version')) is not int or project['version'] != 1 or _identity(project.get('project_uuid')) != _identity(value.get('project_uuid')):
raise ValueError('Prepared project identity or format is invalid')
if not isinstance(project.get('name'), str) or not project['name'].strip() or project.get('catalog_ref', 'catalog.json') != 'catalog.json':
raise ValueError('Prepared project manifest is invalid')
+ from workspace_project_preferences import ProjectPreferences
+ ProjectPreferences(root, project['project_uuid']).read()
+ if 'database_inventory' in value:
+ _validate_database_inventory(value['database_inventory'])
sources = value.get('sources')
if not isinstance(sources, list):
raise ValueError('Prepared project source inventory is invalid')
@@ -265,13 +457,19 @@ def inspect_package(package_dir):
sha = source['source_sha256']
if not isinstance(sha, str) or not re.fullmatch('[0-9a-f]{64}', sha) or sha in seen:
raise ValueError('Prepared project source identities are invalid or duplicated')
+ metadata_sha = source['metadata_sha256']
+ if not isinstance(metadata_sha, str) or not re.fullmatch('[0-9a-f]{64}', metadata_sha):
+ raise ValueError('Prepared project metadata checksum is invalid')
seen.add(sha)
for key in ('recording_ref', 'metadata_ref'):
_inside(root, source[key])
if not source['recording_ref'].startswith('raw-uploads/') or not source['metadata_ref'].startswith('imports/'):
raise ValueError('Prepared source references use unsupported directories')
- if files.get(source['recording_ref'], {}).get('sha256') != sha or files.get(source['metadata_ref'], {}).get('sha256') != source['metadata_sha256']:
+ if source['recording_ref'] not in files or source['metadata_ref'] not in files:
+ raise ValueError('Prepared project is missing a registered source dependency')
+ if files[source['recording_ref']]['sha256'] != sha or files[source['metadata_ref']]['sha256'] != metadata_sha:
raise ValueError('Prepared project source checksums disagree')
+ _verify_recording_dependencies(_inside(root, source['recording_ref']))
return value
@@ -290,6 +488,28 @@ def _remove_runtime(project_dir):
raise ValueError('Could not remove temporary transfer database; preserve its folder for recovery')
+@contextmanager
+def _desktop_database_session(project_dir):
+ """Own transfer SQL durably and close it before acknowledging completion."""
+ if os.environ.get('RIEKE_DESKTOP_MODE') != '1':
+ yield
+ return
+ from workspace_desktop import desktop_database_operation
+ identity = desktop_database_operation(project_dir)
+ try:
+ yield
+ finally:
+ try:
+ _remove_runtime(project_dir)
+ desktop_database_operation(project_dir, identity)
+ except BaseException:
+ try:
+ desktop_database_operation(project_dir, identity, failed=True)
+ except Exception:
+ pass # Persistent registration remains a recovery/update blocker.
+ raise
+
+
def _artifact_relocations(connection, identity, root, previous=None):
"""Verify current exported-artifact locators without rewriting frozen exports."""
root = Path(root)
@@ -348,6 +568,7 @@ def restore_project(package_dir, destination):
target.mkdir(mode=0o700)
_write(target / PENDING, {'version': 1, 'runtime': 'native-mysql', 'instance_uuid': instance, 'project_uuid': identity})
runtime_attempted = False
+ desktop_services = ExitStack()
try:
for relative in manifest['files']:
if relative == 'database.sql':
@@ -368,10 +589,14 @@ def restore_project(package_dir, destination):
raise ValueError('Prepared database changed during restore')
from workspace_project_database import ensure_project_database
runtime_attempted = True
+ desktop_services.enter_context(_desktop_database_session(target))
ensure_project_database(target, _restoring=True)
_import(target, dump)
connection = _connection(target)
try:
+ if ('database_inventory' in manifest
+ and _database_inventory(connection) != manifest['database_inventory']):
+ raise ValueError('Restored database content disagrees with the prepared project inventory')
sources = _sources(connection, identity)
artifacts = _artifact_relocations(connection, identity, target)
mappings = {source['source_sha256']: source for source in manifest['sources']}
@@ -406,10 +631,12 @@ def restore_project(package_dir, destination):
_write(target / 'database/transfer-receipt.json', {'format': FORMAT, 'version': 1,
'project_uuid': identity, 'instance_uuid': instance, 'verified': True,
'package_manifest_sha256': _hash(package / MANIFEST)})
+ desktop_services.close()
(target / PENDING).unlink()
return {'project_uuid': identity, 'instance_uuid': instance, 'directory': str(target),
'files': len(manifest['files']), 'source_count': len(manifest['sources']), 'verified': True}
except BaseException:
+ desktop_services.close()
if runtime_attempted:
_remove_runtime(target)
shutil.rmtree(target)
@@ -427,15 +654,19 @@ def prepare_project(project_dir, destination):
record = _project_record(root)
target = _destination(destination, root)
identity = record['uuid']
- with _offline(root):
+ with _offline(root), _desktop_database_session(root):
+ from workspace_project_preferences import ProjectPreferences
+ ProjectPreferences(root, identity).read()
ensure_project_database(root)
connection = _connection(root)
staging = Path(tempfile.mkdtemp(prefix='.rieke-transfer-', dir=target.parent))
try:
+ register_empty_project(root, connection)
with connection.cursor() as cursor:
cursor.execute('SET SESSION lock_wait_timeout=15')
cursor.execute('FLUSH TABLES WITH READ LOCK')
sources = _sources(connection, identity)
+ database_inventory = _database_inventory(connection)
source_project = _json(root / 'project.json')
# Only portable identity/display fields enter the transfer manifest.
project = {key: source_project[key] for key in ('format', 'version', 'project_uuid', 'name', 'display_name', 'created_at') if key in source_project}
@@ -448,8 +679,12 @@ def prepare_project(project_dir, destination):
if source_dir.exists():
if source_dir.is_symlink():
raise ValueError('Managed transfer directories cannot be symbolic links')
+ # Reject symlinked parents too, including the logs root.
+ _inside(root, directory)
before[directory] = _files(source_dir, skip_marker=False)
- shutil.copytree(source_dir, staging / directory)
+ output_dir = staging / directory
+ output_dir.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copytree(source_dir, output_dir)
inventory = []
for source in sources:
manifest = source['manifest']
@@ -471,6 +706,7 @@ def prepare_project(project_dir, destination):
raise ValueError('A registered recording is missing; locate it before preparing a complete transfer') from None
if _hash(output) != source['source_sha256']:
raise ValueError('Recording checksum changed or a recording is missing')
+ _verify_recording_dependencies(output)
inventory.append({'source_sha256': source['source_sha256'], 'recording_ref': recording_ref,
'metadata_ref': reference, 'metadata_sha256': manifest['metadata_sha256']})
_dump(root, staging / 'database.sql')
@@ -484,12 +720,20 @@ def prepare_project(project_dir, destination):
connection = None
_write(staging / MANIFEST, {'format': FORMAT, 'version': 1, 'mode': 'complete',
'database_format': 'mysql8-logical-v1', 'project_uuid': identity,
- 'sources': inventory, 'files': _files(staging)})
+ 'sources': inventory, 'database_inventory': database_inventory, 'files': _files(staging)})
# Real restore verification, including identities and current source locators.
verify_parent = Path(tempfile.mkdtemp(prefix='.rieke-verify-', dir=target.parent))
- verified = restore_project(staging, str(verify_parent / 'project'))
- _remove_runtime(Path(verified['directory']))
- shutil.rmtree(verify_parent)
+ try:
+ verified = restore_project(staging, str(verify_parent / 'project'))
+ _remove_runtime(Path(verified['directory']))
+ except BaseException:
+ # A failed runtime shutdown leaves evidence for manual recovery.
+ # Failed restores remove their own partial project already.
+ if not (verify_parent / 'project').exists():
+ shutil.rmtree(verify_parent)
+ raise
+ else:
+ shutil.rmtree(verify_parent)
# Claim the final path exclusively before replacing our own empty
# reservation with the fully verified package.
try:
@@ -569,7 +813,8 @@ def relocate_project(project_dir, destination):
source = Path(project_dir).expanduser()
if not source.is_absolute() or source.is_symlink() or not source.is_dir():
raise ValueError('Choose an absolute existing project folder, not a symbolic link')
- source = source.resolve()
+ from workspace_desktop_paths import require_external_data_path
+ source = require_external_data_path(source)
project = _project_record(source)
target = _destination(destination, source)
code_root = Path(__file__).resolve().parents[1]
diff --git a/python/workspace_project_preferences.py b/python/workspace_project_preferences.py
new file mode 100644
index 0000000..db779ef
--- /dev/null
+++ b/python/workspace_project_preferences.py
@@ -0,0 +1,185 @@
+"""Portable search shortcuts and protocol organization; device geometry stays local."""
+from __future__ import annotations
+
+import copy
+import fcntl
+import json
+import os
+from pathlib import Path
+import tempfile
+import uuid
+
+FORMAT = 'rieke-project-preferences'
+REFERENCE = 'protocols/ui-state/project-preferences.json'
+FIELDS = {'recent_searches', 'protocol_shortcuts'}
+MAX_BYTES = 1024 * 1024
+
+
+def _uuid(value):
+ if not isinstance(value, str) or str(uuid.UUID(value)) != value:
+ raise ValueError('Preference identities must be normalized UUID strings')
+ return value
+
+
+def _predicate(value):
+ from workspace_predicates import MAX_DEPTH, MAX_NODES, validate
+ fields, count = set(), 0
+ def collect(node, depth=0):
+ nonlocal count
+ count += 1
+ if count > MAX_NODES or depth > MAX_DEPTH or not isinstance(node, dict):
+ raise ValueError('Saved search predicate exceeds its shape or size limits')
+ if set(node) == {'not'}:
+ collect(node['not'], depth + 1)
+ elif set(node) in ({'all'}, {'any'}):
+ children = node[next(iter(node))]
+ if not isinstance(children, list):
+ raise ValueError('Predicate groups require arrays')
+ for child in children:
+ collect(child, depth + 1)
+ else:
+ field = node.get('field')
+ if not isinstance(field, str) or not field or len(field) > 2048:
+ raise ValueError('Saved search field is invalid')
+ fields.add(field)
+ collect(value)
+ # A previously valid field can disappear after archiving a source. Preserve
+ # the recipe's structural validity here; running it still checks the catalog.
+ return validate(value, {'fields': [{'id': field} for field in fields]}, {})
+
+
+def validate_field(field, value):
+ if field == 'protocol_shortcuts':
+ if not isinstance(value, dict) or len(value) > 2000:
+ raise ValueError('Use at most 2000 protocol shortcuts')
+ for identity, preference in value.items():
+ _uuid(identity)
+ if (not isinstance(preference, dict) or set(preference) != {'section', 'rank'}
+ or not isinstance(preference['section'], str) or preference['section'] not in {'pinned', 'main', 'support'}
+ or type(preference['rank']) is not int or not 0 <= preference['rank'] <= 2000):
+ raise ValueError('Protocol shortcuts require a known section and integer rank')
+ elif field == 'recent_searches':
+ if not isinstance(value, list) or len(value) > 100:
+ raise ValueError('Use at most 100 recent search shortcuts')
+ seen = set()
+ for entry in value:
+ if (not isinstance(entry, dict)
+ or set(entry) - {'id', 'name', 'predicate', 'splits', 'matched_count', 'cell_count', 'pinned', 'lastRunAt'}
+ or not {'id', 'predicate', 'splits', 'pinned'} <= set(entry)
+ or not isinstance(entry['id'], str) or not entry['id'] or len(entry['id']) > 65536
+ or entry['id'] in seen or type(entry['pinned']) is not bool
+ or not isinstance(entry['splits'], str) or len(entry['splits']) > 4096):
+ raise ValueError('Saved search shortcut shape or identity is invalid')
+ seen.add(entry['id'])
+ _predicate(entry['predicate'])
+ for key, maximum in (('name', 255), ('lastRunAt', 64)):
+ if key in entry and entry[key] is not None and (not isinstance(entry[key], str) or len(entry[key]) > maximum):
+ raise ValueError('Saved search label or timestamp is invalid')
+ for key in ('matched_count', 'cell_count'):
+ if key in entry and entry[key] is not None and (type(entry[key]) is not int or entry[key] < 0):
+ raise ValueError('Saved search counts must be nonnegative integers')
+ else:
+ raise ValueError('Unknown project preference field')
+ if len(json.dumps(value, allow_nan=False).encode()) > MAX_BYTES:
+ raise ValueError('Project preference field exceeds 1 MiB')
+ return copy.deepcopy(value)
+
+
+class PreferenceConflict(ValueError):
+ pass
+
+
+class ProjectPreferences:
+ def __init__(self, directory, project_uuid):
+ self.root = Path(directory).resolve()
+ self.identity = _uuid(project_uuid)
+
+ def _path(self):
+ current = self.root
+ for part in Path(REFERENCE).parts:
+ current = current / part
+ if current.is_symlink():
+ raise ValueError('Project preference files cannot be symbolic links')
+ return current
+
+ def read(self):
+ path = self._path()
+ if not path.exists():
+ return {'format': FORMAT, 'version': 1, 'project_uuid': self.identity,
+ 'revisions': {field: 0 for field in FIELDS}, 'state': {}}
+ if not path.is_file() or path.stat().st_size > MAX_BYTES:
+ raise ValueError('Project preference file is invalid or exceeds 1 MiB')
+ value = json.loads(path.read_text())
+ if (not isinstance(value, dict) or set(value) != {'format', 'version', 'project_uuid', 'revisions', 'state'}
+ or value['format'] != FORMAT or type(value['version']) is not int or value['version'] != 1
+ or value['project_uuid'] != self.identity or not isinstance(value['state'], dict)
+ or set(value['state']) - FIELDS or not isinstance(value['revisions'], dict)
+ or set(value['revisions']) != FIELDS
+ or any(type(revision) is not int or revision < 0 for revision in value['revisions'].values())):
+ raise ValueError('Unsupported project preference file or identity')
+ for field, state in value['state'].items():
+ validate_field(field, state)
+ return value
+
+ def save(self, field, value, expected_revision):
+ value = validate_field(field, value)
+ if type(expected_revision) is not int or expected_revision < 0:
+ raise ValueError('Preference expected_revision must be a nonnegative integer')
+ lock_path = self.root / '.project-preferences.lock'
+ if lock_path.is_symlink():
+ raise ValueError('Project preference lock cannot be a symbolic link')
+ with lock_path.open('a') as lock:
+ fcntl.flock(lock, fcntl.LOCK_EX)
+ current = self.read()
+ if current['revisions'][field] != expected_revision:
+ raise PreferenceConflict('Project preferences changed; reload before saving.')
+ if current['state'].get(field) == value:
+ return current
+ current['state'][field] = value
+ current['revisions'][field] += 1
+ content = json.dumps(current, allow_nan=False, indent=2, sort_keys=True).encode() + b'\n'
+ if len(content) > MAX_BYTES:
+ raise ValueError('Project preference file exceeds 1 MiB')
+ path = self._path()
+ path.parent.mkdir(parents=True, exist_ok=True)
+ fd, temporary = tempfile.mkstemp(prefix='.preferences-', dir=path.parent)
+ try:
+ with os.fdopen(fd, 'wb') as handle:
+ handle.write(content)
+ handle.flush()
+ os.fsync(handle.fileno())
+ os.replace(temporary, path)
+ finally:
+ Path(temporary).unlink(missing_ok=True)
+ return current
+
+
+def register_project_preference_routes(app, directory, project_uuid, db_lock):
+ from flask import jsonify, request
+ preferences = ProjectPreferences(directory, project_uuid)
+ app.extensions['project_preferences'] = preferences
+
+ @app.get('/api/project-preferences')
+ def read_preferences():
+ if request.args:
+ return jsonify(error='Project preferences do not accept query parameters.'), 400
+ try:
+ with db_lock:
+ return jsonify(preferences.read())
+ except (ValueError, OSError) as error:
+ return jsonify(error=str(error)), 400
+
+ @app.put('/api/project-preferences')
+ def save_preferences():
+ if request.content_length is not None and request.content_length > MAX_BYTES:
+ return jsonify(error='Project preference request exceeds 1 MiB.'), 400
+ body = request.get_json(silent=True)
+ if request.args or not isinstance(body, dict) or set(body) != {'field', 'value', 'expected_revision'}:
+ return jsonify(error='Preference saves require field, value and expected_revision.'), 400
+ try:
+ with db_lock:
+ return jsonify(preferences.save(body['field'], body['value'], body['expected_revision']))
+ except PreferenceConflict as error:
+ return jsonify(error=str(error)), 409
+ except (ValueError, OSError, TypeError) as error:
+ return jsonify(error=str(error)), 400
diff --git a/python/workspace_project_servers.py b/python/workspace_project_servers.py
index bad10f8..251e097 100644
--- a/python/workspace_project_servers.py
+++ b/python/workspace_project_servers.py
@@ -12,7 +12,7 @@
from recording_workspace import write_json
from workspace_projects import list_projects, list_managed_projects
-from workspace_startup_registry import remember_project
+from workspace_startup_registry import remember_project_result
def server_record(project_dir):
@@ -98,8 +98,13 @@ def project_server_port(project_dir, identity):
def open_project(project_dir, identity, retinanalysis_dir, *, timeout=300, managed=False):
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1':
+ from workspace_desktop import desktop_open_project
+ return desktop_open_project(project_dir, identity, retinanalysis_dir, timeout=timeout, managed=managed)
registry = list_managed_projects(project_dir) if managed else list_projects(project_dir)
- project = next((row for row in registry['projects'] if row['uuid'] == identity and row['available']), None)
+ selected_path = str(Path(project_dir).expanduser().resolve())
+ project = next((row for row in registry['projects'] if row['uuid'] == identity and row['available']
+ and (managed or row['path'] == selected_path)), None)
if project is None:
raise ValueError('Select an available registered project')
directory = Path(project['path'])
@@ -109,8 +114,7 @@ def open_project(project_dir, identity, retinanalysis_dir, *, timeout=300, manag
fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
existing = ready_url(directory, identity)
if existing:
- remember_project(directory.parent, identity)
- return {'url': existing, 'project_uuid': identity}
+ return remember_project_result(directory, {'url': existing, 'project_uuid': identity}, set_last=True)
from workspace_project_database import ensure_project_database
ensure_project_database(directory)
config = json.loads((directory / 'catalog.json').read_text())
@@ -140,8 +144,7 @@ def open_project(project_dir, identity, retinanalysis_dir, *, timeout=300, manag
raise ValueError('Project could not start. See logs/workspace-launch.log in that project.')
url = ready_url(directory, identity)
if url:
- remember_project(directory.parent, identity)
- return {'url': url, 'project_uuid': identity}
+ return remember_project_result(directory, {'url': url, 'project_uuid': identity}, set_last=True)
time.sleep(0.2)
process.terminate()
try:
diff --git a/python/workspace_project_validation.py b/python/workspace_project_validation.py
index 5fbe31d..2dff375 100644
--- a/python/workspace_project_validation.py
+++ b/python/workspace_project_validation.py
@@ -1,5 +1,6 @@
"""Read-only folder preflight. Database availability and H5 checks happen on open."""
from pathlib import Path
+from itertools import islice
from workspace_projects import _project_record, _read_manifest
from workspace_storage import DIRECTORIES, LOG_FOLDERS
@@ -30,11 +31,17 @@ def validate_project_folder(path):
except (OSError, ValueError, UnicodeError, TypeError) as error:
raise ValueError(f'Cannot open project: {error}') from error
catalog = _manifest(root, 'catalog.json')
- result = {'valid': True, 'project': record, 'checks': ['Project and catalog identities match'],
+ result = {'valid': True, 'kind': 'project', 'project': record, 'checks': ['Project and catalog identities match'],
'warnings': [], 'database_status': 'legacy_external'}
provider = catalog.get('connection', {}).get('credential_provider', {})
if record['database_kind'] != 'native-mysql' and provider.get('kind') != 'native-project':
result['warnings'].append('Legacy database configuration: database availability and linked recording files are checked when opening.')
+ import os
+ if os.environ.get('RIEKE_DESKTOP_MODE') == '1':
+ result['desktop_compatibility'] = {'can_open': False, 'requires_migration': True,
+ 'migration_available': provider.get('kind') == 'docker-container-env',
+ 'migration_endpoint': '/api/projects/migrate-source',
+ 'message': 'Create a desktop copy in a separate folder. The original project and source database stay unchanged.'}
return result
# _configuration checks descriptors and rejects symlinked native state without
# calling the runtime, opening a database connection, or changing file modes.
@@ -93,3 +100,85 @@ def validate_project_folder(path):
result['warnings'].append('Database is open or was not closed cleanly; opening will check whether local recovery is safe. A moved unclean database cannot be opened.')
result['warnings'].append('Recording availability and checksums are verified when opening.')
return result
+
+
+def _inspect_exact(candidate):
+ from workspace_portability import MANIFEST, inspect_package
+ if (candidate / MANIFEST).exists() or (candidate / MANIFEST).is_symlink():
+ transfer = inspect_package(candidate)
+ root = candidate.resolve()
+ project = _manifest(root, 'project.json')
+ return {'valid': True, 'kind': 'prepared-transfer',
+ 'project': {'uuid': transfer['project_uuid'],
+ 'name': project.get('display_name') or project['name'],
+ 'path': str(root), 'available': True, 'current': False},
+ 'database_status': 'restore_required',
+ 'checks': ['Prepared project identity and complete file inventory verified',
+ 'Recording and parsed metadata checksums verified'],
+ 'warnings': [], 'source_count': len(transfer['sources'])}
+ return validate_project_folder(candidate)
+
+
+def inspect_project_folder(path):
+ """Inspect an exact root, or propose nearby fully validated roots read-only.
+
+ Search at most eight ancestors and 128 immediate children. Never descend
+ into unrelated directory trees, follow directory aliases, open a server,
+ select a candidate automatically, or change the user's selected folder.
+ """
+ if not isinstance(path, (str, Path)) or not str(path).strip():
+ raise ValueError('Choose an absolute project folder path')
+ candidate = Path(str(path).strip()).expanduser()
+ if not candidate.is_absolute() or candidate.is_symlink():
+ raise ValueError('Choose an absolute project folder path, not a symbolic link')
+ if not candidate.is_dir():
+ raise ValueError('Choose an existing project folder')
+ try:
+ return _inspect_exact(candidate)
+ except (ValueError, OSError, UnicodeError, TypeError, KeyError) as error:
+ original_error = error
+
+ def suggestion(folder, reason):
+ if folder.is_symlink() or not folder.is_dir():
+ return None
+ if not any((folder / marker).exists() or (folder / marker).is_symlink()
+ for marker in ('project.json', 'transfer.json')):
+ return None
+ try:
+ report = _inspect_exact(folder)
+ except (ValueError, OSError, UnicodeError, TypeError, KeyError):
+ return None
+ project = report['project']
+ return {'path': project['path'], 'name': project['name'], 'kind': report['kind'],
+ 'reason': reason}
+
+ candidates = []
+ for parent in islice(candidate.parents, 8):
+ if parent.is_symlink():
+ # A nested selection beneath an alias cannot identify its actual root.
+ break
+ found = suggestion(parent, 'containing-project')
+ if found:
+ candidates.append(found)
+ break # The closest enclosing valid root owns this selection.
+ if not candidates:
+ try:
+ children = list(islice(candidate.iterdir(), 128))
+ except OSError:
+ children = []
+ for child in sorted(children, key=lambda entry: entry.name.casefold()):
+ found = suggestion(child, 'child-project')
+ if found:
+ candidates.append(found)
+ if not candidates:
+ if isinstance(original_error, ValueError):
+ raise original_error
+ raise ValueError(f'Cannot open project: {original_error}') from original_error
+ if candidates[0]['reason'] == 'containing-project':
+ message = 'This folder is inside a project. Choose its top folder below.'
+ elif len(candidates) == 1:
+ message = 'A project was found one folder below. Choose its top folder below.'
+ else:
+ message = 'Several projects were found one folder below. Choose the project you want.'
+ return {'valid': False, 'kind': 'project-root-suggestions', 'restore_required': False,
+ 'message': message, 'candidates': candidates}
diff --git a/python/workspace_projection_cache.py b/python/workspace_projection_cache.py
index 9b7a8dd..13a2931 100644
--- a/python/workspace_projection_cache.py
+++ b/python/workspace_projection_cache.py
@@ -1,16 +1,108 @@
-"""Disposable, checksum-sealed source projections; acquisition files stay untouched."""
+"""Disposable source projections with lossless, lazy ancestor reconstruction."""
from __future__ import annotations
+import base64
+from collections.abc import Mapping
+import copy
import hashlib
import json
import os
from pathlib import Path
+import sqlite3
import tempfile
import zlib
from workspace_recipes import checksum
+from workspace_cache_lifecycle import CacheNamespace
+from workspace_metadata_objects import Decoder, Encoder, KINDS, _identity, _uuid, canonical
-VERSION = 1
+VERSION = 2
+_REQUIRED = {'rows', 'details', 'cells', 'source', 'fingerprints'}
+
+
+class _Cursor:
+ def __init__(self, rows):
+ self.rows = rows
+
+ def fetchone(self):
+ return self.rows[0] if self.rows else None
+
+ def __iter__(self):
+ return iter(self.rows)
+
+
+class _ObjectRows:
+ """Immutable compressed rows implementing the decoder's two read queries."""
+ def __init__(self, objects):
+ self.rows = {}
+ identities = set()
+ if not isinstance(objects, list):
+ raise ValueError('Invalid source projection objects')
+ for item in objects:
+ if (not isinstance(item, dict) or set(item) != {'id', 'source_sha', 'kind', 'uuid', 'payload', 'sha256'}
+ or type(item['id']) is not int or item['id'] <= 0 or item['id'] in self.rows
+ or not isinstance(item['source_sha'], str) or not item['source_sha']
+ or not isinstance(item['kind'], str) or item['kind'] not in KINDS
+ or _uuid(item['uuid']) != item['uuid'] or item['uuid'] is None
+ or not isinstance(item['sha256'], str) or len(item['sha256']) != 64):
+ raise ValueError('Invalid source projection object')
+ identity = item['source_sha'], item['kind'], item['uuid']
+ if identity in identities:
+ raise ValueError('Duplicate source projection object identity')
+ identities.add(identity)
+ digest = bytes.fromhex(item['sha256'])
+ if len(digest) != 32:
+ raise ValueError('Invalid source projection object digest')
+ payload = base64.b64decode(item['payload'], validate=True)
+ self.rows[item['id']] = (*identity, payload, digest)
+
+ def execute(self, query, parameters=()):
+ if query == 'PRAGMA database_list':
+ return _Cursor([(0, 'main', '')])
+ if query == 'SELECT source_sha,kind,object_uuid,payload,sha256 FROM metadata_objects WHERE object_id=?':
+ value = self.rows.get(parameters[0])
+ return _Cursor([value] if value is not None else [])
+ raise ValueError('Unsupported source projection object query')
+
+
+class _ProjectionDetails(Mapping):
+ def __init__(self, rows, documents, objects):
+ if not isinstance(rows, dict) or not isinstance(documents, dict) or rows.keys() != documents.keys():
+ raise ValueError('Source projection detail membership differs')
+ self._objects = _ObjectRows(objects)
+ self._documents = documents
+ self._rows = {}
+ self._decoder = Decoder()
+ for identity, document in documents.items():
+ row = rows[identity]
+ if (not isinstance(row, dict) or not isinstance(document, dict)
+ or set(document) != {'version', 'inline', 'objects'}
+ or type(document['version']) is not int or document['version'] != 1
+ or not isinstance(document['inline'], dict) or not isinstance(document['objects'], dict)
+ or set(document['objects']) - set(KINDS)):
+ raise ValueError('Invalid source projection detail')
+ # Retain ownership independently of mutable rows returned to callers.
+ self._rows[identity] = {name: row.get(name) for name in ('source_sha256', *KINDS.values())}
+ metadata = document['inline'].get('metadata')
+ for kind, number in document['objects'].items():
+ if type(number) is not int or number <= 0 or not isinstance(metadata, dict) or kind in metadata:
+ raise ValueError('Invalid source projection ancestor reference')
+ saved = self._objects.rows.get(number)
+ if saved is None or tuple(saved[:3]) != _identity(row, kind):
+ raise ValueError('Source projection ancestor ownership differs')
+
+ def __len__(self):
+ return len(self._documents)
+
+ def __iter__(self):
+ return iter(self._documents)
+
+ def __getitem__(self, identity):
+ document = self._documents[identity]
+ return self._decoder.decode(self._objects, self._rows[identity], zlib.compress(canonical(document)))
+
+ def __deepcopy__(self, memo):
+ return {identity: copy.deepcopy(self[identity], memo) for identity in self}
class ProjectionCache:
@@ -19,44 +111,126 @@ def __init__(self, directory):
if self.directory.is_symlink() or self.directory.parent.is_symlink():
raise ValueError('Derived cache directories cannot be symbolic links')
self.directory.mkdir(parents=True, exist_ok=True)
+ self.namespace = CacheNamespace(self.directory, 'projections')
def _path(self, key):
return self.directory / (checksum({'version': VERSION, 'key': key}) + '.json.zlib')
def read(self, key):
+ with self.namespace.writer(self._path(key).name):
+ return self._read(key)
+
+ def _read(self, key):
path = self._path(key)
seal = path.with_suffix('.sha256')
- if not path.exists() or not seal.exists():
- return None
- try:
- blob = path.read_bytes()
- if hashlib.sha256(blob).hexdigest() != seal.read_text().strip():
- return None
- document = json.loads(zlib.decompress(blob))
- if document.get('version') != VERSION or document.get('key_hash') != checksum(key):
+ with self.namespace.lease(path.name, reclaim=False):
+ if not path.exists() or not seal.exists() or path.is_symlink() or seal.is_symlink():
return None
- projection = document['projection']
- if not isinstance(projection, dict) or not {'rows', 'details', 'cells', 'source', 'fingerprints'} <= projection.keys():
- return None
- return projection
- except (OSError, ValueError, KeyError, zlib.error):
- return None # Cache miss: rebuild from verified scientific inputs.
+ try:
+ blob = path.read_bytes()
+ if hashlib.sha256(blob).hexdigest() != seal.read_text().strip():
+ return None
+ document = json.loads(zlib.decompress(blob))
+ if (not isinstance(document, dict) or type(document.get('version')) is not int
+ or document['version'] != VERSION or document.get('key_hash') != checksum(key)):
+ return None
+ projection = document['projection']
+ if not isinstance(projection, dict) or not _REQUIRED - {'details'} <= projection.keys():
+ return None
+ projection['details'] = _ProjectionDetails(projection['rows'], document['details'], document['objects'])
+ return projection
+ except (OSError, ValueError, KeyError, TypeError, zlib.error):
+ return None # Cache miss: rebuild from verified scientific inputs.
+
+ def hold(self, keys):
+ """Pin prepared sources through whole-refresh publication or failure."""
+ return self.namespace.hold(self._path(key).name for key in keys)
+
+ def publish(self, keys):
+ return self.namespace.publish(self._path(key).name for key in keys)
def write(self, key, projection):
+ with self.namespace.writer(self._path(key).name):
+ return self._write(key, projection)
+
+ def _write(self, key, projection):
+ if not isinstance(projection, dict) or not _REQUIRED <= projection.keys():
+ raise ValueError('Incomplete source projection')
+ rows, details = projection['rows'], projection['details']
+ if rows.keys() != details.keys():
+ raise ValueError('Source projection detail membership differs')
path = self._path(key)
- blob = zlib.compress(json.dumps({'version': VERSION, 'key_hash': checksum(key),
- 'projection': projection}, allow_nan=False, separators=(',', ':')).encode(), level=1)
- seal = hashlib.sha256(blob).hexdigest().encode()
- # A crash between these replacements produces a cache miss, not bad data.
- for destination, content in ((path, blob), (path.with_suffix('.sha256'), seal)):
- fd, temporary = tempfile.mkstemp(dir=self.directory, prefix='.writing-')
+ temporary_paths = []
+
+ def temporary():
+ descriptor, name = tempfile.mkstemp(dir=self.directory, prefix=path.name+'.', suffix='.writing')
+ temporary_paths.append(name)
+ return descriptor, name
+
+ with self.namespace.lease(path.name, reclaim=False):
+ connection = None
try:
- with os.fdopen(fd, 'wb') as handle:
- handle.write(content)
+ descriptor, database = temporary()
+ os.close(descriptor)
+ connection = sqlite3.connect(database)
+ connection.execute('PRAGMA cache_size=-8192')
+ connection.execute('PRAGMA temp_store=FILE')
+ encoder = Encoder(connection)
+ descriptor, payload_path = temporary()
+ digest = hashlib.sha256()
+ compressor = zlib.compressobj(level=1)
+ json_encoder = json.JSONEncoder(ensure_ascii=False, allow_nan=False, separators=(',', ':'))
+ with os.fdopen(descriptor, 'wb') as handle:
+ def emit(text):
+ compressed = compressor.compress(text.encode())
+ handle.write(compressed)
+ digest.update(compressed)
+
+ def emit_json(value):
+ for chunk in json_encoder.iterencode(value):
+ emit(chunk)
+
+ emit('{"version":2,"key_hash":')
+ emit_json(checksum(key))
+ emit(',"projection":')
+ emit_json({name: value for name, value in projection.items() if name != 'details'})
+ emit(',"details":{')
+ for position, identity in enumerate(details):
+ if position:
+ emit(',')
+ emit_json(identity)
+ emit(':')
+ emit_json(encoder.partition(rows[identity], details[identity]))
+ emit('},"objects":[')
+ for position, item in enumerate(connection.execute('SELECT object_id,source_sha,kind,object_uuid,payload,sha256 '
+ 'FROM metadata_objects ORDER BY object_id')):
+ if position:
+ emit(',')
+ number, source, kind, identity, payload, sha = item
+ emit_json({'id': number, 'source_sha': source, 'kind': kind, 'uuid': identity,
+ 'payload': base64.b64encode(payload).decode('ascii'), 'sha256': sha.hex()})
+ emit(']}')
+ tail = compressor.flush()
+ handle.write(tail)
+ digest.update(tail)
+ handle.flush()
+ os.fsync(handle.fileno())
+ descriptor, seal_path = temporary()
+ with os.fdopen(descriptor, 'w') as handle:
+ handle.write(digest.hexdigest())
handle.flush()
os.fsync(handle.fileno())
- os.replace(temporary, destination)
+ # Both files are fully built before publication. A crash between
+ # replacements yields a checksum cache miss, never partial data.
+ os.replace(payload_path, path)
+ os.replace(seal_path, path.with_suffix('.sha256'))
finally:
- if os.path.exists(temporary):
- os.unlink(temporary)
+ if connection is not None:
+ connection.close()
+ for name in temporary_paths:
+ for suffix in ('', '-journal', '-wal', '-shm'):
+ try:
+ os.unlink(name + suffix)
+ except FileNotFoundError:
+ pass
return str(path)
diff --git a/python/workspace_projects.py b/python/workspace_projects.py
index 1f07329..59c1d06 100644
--- a/python/workspace_projects.py
+++ b/python/workspace_projects.py
@@ -1,4 +1,4 @@
-"""Read-only discovery of sibling recording projects; never scan arbitrary roots.
+"""Read-only discovery of immediate siblings and explicitly remembered projects.
Availability means the local identity manifests are valid and readable. It does
not claim that the project's SQL server is reachable or that source H5 files exist.
@@ -31,6 +31,8 @@ def _identity(value):
def _project_record(directory, *, current=False):
+ if (directory / 'transfer.json').exists() or (directory / 'transfer.json').is_symlink():
+ raise ValueError('Prepared project copies must be restored into a working project folder')
if (directory / '.portable-restore.pending').exists():
raise ValueError('Project transfer restore is incomplete; finish recovery before opening it')
project = _read_manifest(directory / 'project.json')
@@ -62,7 +64,7 @@ def _project_record(directory, *, current=False):
def list_projects(project_dir):
- """Return the current project plus valid immediate siblings, deterministically.
+ """Return current, immediate siblings and remembered external projects.
Sibling directory and manifest symlinks are excluded. Duplicate UUIDs cannot
establish a second selectable project: current wins; ambiguous non-current
@@ -86,23 +88,60 @@ def list_projects(project_dir):
candidates.append(candidate)
except (OSError, ValueError, UnicodeError):
continue
- occurrences = {}
- for candidate in candidates:
- occurrences[candidate['uuid']] = occurrences.get(candidate['uuid'], 0) + 1
- projects = [candidate for candidate in candidates if candidate.get('database_kind') == 'native-mysql' or occurrences[candidate['uuid']] == 1]
- projects.sort(key=lambda item: (item['name'].casefold(), item['name'], item['path']))
- from workspace_startup_registry import read_registry, ordered_projects
- saved = read_registry(current_dir.parent)
- return {'current_project_uuid': current['uuid'], 'projects': ordered_projects([current, *projects], saved),
- 'managed_root': str(current_dir.parent), 'last_project_uuid': saved.get('last_project_uuid'), 'launcher': False}
-
-
-def managed_root(path):
+ projects, last = _combined_projects([current, *candidates], current_dir.parent, current_dir)
+ return {'current_project_uuid': current['uuid'], 'projects': projects,
+ 'managed_root': str(current_dir.parent), **last, 'launcher': False}
+
+
+def _combined_projects(discovered, root, current=None):
+ """Read only saved exact folders; their parents are never searched."""
+ from workspace_startup_registry import read_registry, read_project_index, ordered_projects
+ local = read_registry(root)
+ index = read_project_index()
+ by_path = {project['path']: project for project in discovered}
+ for saved in index['projects']:
+ path = saved['path']
+ if path in by_path:
+ continue
+ directory = Path(path)
+ try:
+ if directory.is_symlink() or not directory.is_dir():
+ raise ValueError('Project folder is unavailable; reconnect its drive or add its new location')
+ project = _project_record(directory, current=directory == current)
+ if project['uuid'] != saved['project_uuid']:
+ raise ValueError('This folder now contains a different project; add its new location')
+ except (OSError, ValueError, UnicodeError) as error:
+ # Keep removable-drive and moved-project references visible without
+ # trusting stale manifests, following links, or exposing credentials.
+ project = {'uuid': saved['project_uuid'], 'name': saved['name'], 'path': path,
+ 'database_kind': saved['database_kind'], 'current': False,
+ 'available': False, 'unavailable_reason': str(error)}
+ by_path[path] = project
+ projects = list(by_path.values())
+ current_record = next((project for project in projects if project['current']), None)
+ if current_record:
+ projects = [project for project in projects if project['current']
+ or project['uuid'] != current_record['uuid'] or project.get('database_kind') == 'native-mysql']
+ counts = {}
+ for project in projects:
+ counts[project['uuid']] = counts.get(project['uuid'], 0) + 1
+ projects = [project for project in projects if project['current']
+ or project.get('database_kind') == 'native-mysql' or counts[project['uuid']] == 1]
+ saved_order = {**local, 'project_order': index['project_order'] or local.get('project_order', [])}
+ projects = ordered_projects(projects, saved_order)
+ last_path = index['last_project_path']
+ last = next((project for project in projects if project['path'] == last_path), None)
+ if last is None:
+ matches = [project for project in projects if project['uuid'] == local.get('last_project_uuid')]
+ last = matches[0] if len(matches) == 1 else None
+ return projects, {'last_project_path': last['path'] if last else None,
+ 'last_project_uuid': last['uuid'] if last else None}
+
+
+def managed_root(path, *, allow_workspace_descendant=False):
"""Validate a managed root without following a caller supplied symlink."""
- candidate = Path(path).expanduser()
- if candidate.is_symlink():
- raise ValueError('Managed project root cannot be a symbolic link')
- return candidate.resolve()
+ from workspace_paths import workspace_root
+ return workspace_root(path, allow_workspace_descendant=allow_workspace_descendant)
def list_managed_projects(root, current_project=None):
@@ -118,39 +157,41 @@ def list_managed_projects(root, current_project=None):
projects.append(_project_record(directory, current=directory == current_project))
except (OSError, ValueError, UnicodeError):
continue
- counts = {}
- for project in projects:
- counts[project['uuid']] = counts.get(project['uuid'], 0) + 1
- projects = [p for p in projects if p.get('database_kind') == 'native-mysql' or counts[p['uuid']] == 1]
- projects.sort(key=lambda p: (p['name'].casefold(), p['name'], p['path']))
- from workspace_startup_registry import read_registry, ordered_projects
- saved = read_registry(root)
- identities = {p['uuid'] for p in projects}
+ projects, last = _combined_projects(projects, root, current_project)
return {'current_project_uuid': next((p['uuid'] for p in projects if p['current']), None),
- 'projects': ordered_projects(projects, saved), 'managed_root': str(root),
+ 'projects': projects, 'managed_root': str(root),
'workspace_initialized': (root / '.rieke-workspace.json').is_file(),
- 'last_project_uuid': saved.get('last_project_uuid') if saved.get('last_project_uuid') in identities and counts.get(saved.get('last_project_uuid')) == 1 else None}
+ **last}
-def create_project(root, name, *, directory=None, code_root=None):
+def create_project(root, name, *, directory=None, code_root=None, _lock_path=None):
"""Create only empty manifests/directories. Database provisioning is deferred."""
import fcntl
import re
import datetime as dt
+ from contextlib import ExitStack
from recording_workspace import write_json
from workspace_storage import initialize_layout
- root = managed_root(root)
+ # An explicit exact project folder does not select/create a new workspace.
+ # It may be grouped under one, but never inside another project.
+ root = managed_root(root, allow_workspace_descendant=_lock_path is not None)
+ from workspace_desktop_paths import require_external_data_path
+ require_external_data_path(root, reject_ancestor=False)
+ if directory is not None and isinstance(directory, str) and directory.strip():
+ chosen = Path(directory).expanduser()
+ require_external_data_path(chosen if chosen.is_absolute() else root / chosen)
code_root = Path(code_root or Path(__file__).resolve().parents[1]).resolve()
if not isinstance(name, str) or not 1 <= len(name.strip()) <= 120 or any(ord(c) < 32 for c in name):
raise ValueError('Project name must contain 1–120 printable characters')
name = name.strip()
root.mkdir(parents=True, exist_ok=True)
- lock_file = root / '.project-create.lock'
+ lock_file = Path(_lock_path) if _lock_path is not None else root / '.project-create.lock'
if lock_file.is_symlink():
raise ValueError('Project registry lock cannot be a symbolic link')
- with lock_file.open('a') as lock:
+ with lock_file.open('a') as lock, ExitStack() as target_locks:
fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
- if any(p['name'].casefold() == name.casefold() for p in list_managed_projects(root)['projects']):
+ if _lock_path is None and any(p['available'] and Path(p['path']).parent == root and p['name'].casefold() == name.casefold()
+ for p in list_managed_projects(root)['projects']):
raise ValueError('A project with this name already exists; open it or choose another name')
identity = str(uuid.uuid4())
slug = re.sub(r'[^a-z0-9]+', '-', name.casefold()).strip('-')[:60] or 'project'
@@ -164,6 +205,11 @@ def create_project(root, name, *, directory=None, code_root=None):
target = target.resolve()
if target.is_relative_to(code_root) or code_root.is_relative_to(target):
raise ValueError('Project storage must be separate from application code')
+ if _lock_path is None:
+ # Managed-root and exact-folder routes must share a target lock.
+ # The root lock still serializes automatic names across siblings.
+ target_lock = target_locks.enter_context(_creation_lock_path(target).open('a'))
+ fcntl.flock(target_lock.fileno(), fcntl.LOCK_EX)
if target.exists() and (not target.is_dir() or any(target.iterdir())):
raise ValueError('Project folder already contains files; nothing was overwritten')
target.mkdir(exist_ok=True)
@@ -191,6 +237,19 @@ def create_project(root, name, *, directory=None, code_root=None):
return _project_record(target)
+def _creation_lock_path(directory):
+ from workspace_startup_registry import project_index_path
+ import hashlib
+ preference_root = project_index_path().parent
+ if preference_root.is_symlink():
+ raise ValueError('Local project preferences cannot be a symbolic link')
+ preference_root.mkdir(parents=True, exist_ok=True)
+ lock = preference_root / ('project-create-' + hashlib.sha256(str(directory).encode()).hexdigest()[:32] + '.lock')
+ if lock.is_symlink():
+ raise ValueError('Project creation lock cannot be a symbolic link')
+ return lock
+
+
def create_project_at(directory, name, *, code_root=None):
"""Use the user's exact project folder, independently of preferred storage."""
if not isinstance(directory, str) or not directory.strip():
@@ -198,10 +257,12 @@ def create_project_at(directory, name, *, code_root=None):
path = Path(directory.strip()).expanduser()
if not path.is_absolute() or path.is_symlink():
raise ValueError('Choose an absolute project folder, not a symbolic link')
+ from workspace_desktop_paths import require_external_data_path
+ require_external_data_path(path)
code = Path(code_root or Path(__file__).resolve().parents[1]).resolve()
if path.resolve().is_relative_to(code) or code.is_relative_to(path.resolve()):
raise ValueError('Project storage must be separate from application code')
# System paths such as macOS /tmp may have a symlinked parent. Resolve
# the selected location after rejecting a symlink at the project itself.
path = path.resolve()
- return create_project(path.parent, name, directory=str(path), code_root=code)
+ return create_project(path.parent, name, directory=str(path), code_root=code, _lock_path=_creation_lock_path(path))
diff --git a/python/workspace_protocol_state.py b/python/workspace_protocol_state.py
new file mode 100644
index 0000000..9c5d0fa
--- /dev/null
+++ b/python/workspace_protocol_state.py
@@ -0,0 +1,472 @@
+"""Bounded protocol reads with exact metadata and verified native revisions.
+
+The native v3 contract combines an exact metadata digest with transactional SQL
+scope generations; dense saved annotations never enter the metadata cache.
+Unsupported adapters retain the legacy full-state oracle and exact-input reuse.
+"""
+from __future__ import annotations
+
+import copy
+import contextlib
+import math
+import sys
+import uuid
+from itertools import chain
+from dataclasses import asdict
+from workspace_recipes import checksum
+from workspace_curation import RevisionConflict
+
+from workspace_disk_index import DiskMetadataIndex
+from workspace_curation import CurationStore
+from workspace_explorer import ExplorerHistory
+from workspace_service import WorkspaceService
+from workspace_annotations import SharedAnnotations
+
+STATE_CACHE_BYTES = 64 * 1024 * 1024
+STATE_CACHE_SCOPES = 8
+_STORE_METHODS={name:getattr(CurationStore,name) for name in ('read','_rows','_state','summary_decisions')}
+_SHARED_METHODS={name:getattr(SharedAnnotations,name) for name in ('snapshot','_rows','for_epochs','_validated_tags','summary','read_targets')}
+_SERVICE_METHODS={name:getattr(WorkspaceService,name)
+ for name in ('query_result','fingerprints','source_scope','binding','_ready','epoch_page','_epoch_page_identities','_decorate')}
+_HISTORY_METHODS={name:getattr(ExplorerHistory,name) for name in ('protocol_binding','protocol_binding_header','get')}
+
+
+def _exact_equal(left, right):
+ """JSON-like equality with numeric types and signed zero preserved."""
+ if left is right:
+ return True
+ if type(left) is not type(right):
+ return False
+ if isinstance(left, dict):
+ return (left.keys() == right.keys()
+ and all(_exact_equal(value,right[key]) for key,value in left.items()))
+ if isinstance(left, (list,tuple)):
+ return len(left)==len(right) and all(_exact_equal(a,b) for a,b in zip(left,right))
+ if type(left) is float and left == right == 0:
+ return math.copysign(1,left)==math.copysign(1,right)
+ return left == right
+
+
+def _plain_epoch_records(value):
+ return type(value) is list and all(type(row) is dict and len(row)==2
+ and type(row.get('uuid')) is str and type(row.get('metadata_hash')) is str for row in value)
+
+
+def _epoch_query_equal(current, proof):
+ """Exact equality against a previously checked plain-string epoch proof.
+
+ Plain string values cannot equate to bool/int/float values. Checking every
+ current record's exact types permits C-level dictionary equality for this
+ large, common schema while preserving the recursive comparator elsewhere.
+ """
+ if type(current) is not dict or current.keys()!=proof.keys():return False
+ epochs=current['epochs'];expected=proof['epochs']
+ if type(epochs) is not list or len(epochs)!=len(expected):return False
+ for row,saved in zip(epochs,expected):
+ if (type(row) is not dict or len(row)!=2 or type(row.get('uuid')) is not str
+ or type(row.get('metadata_hash')) is not str or row!=saved):return False
+ return all(_exact_equal(value,proof[key]) for key,value in current.items() if key!='epochs')
+
+
+def _retained_bytes(value, limit):
+ """Conservative identity-aware object estimate; short-circuit admission."""
+ total,seen=0,set()
+ pending=[iter((value,))]
+ while pending:
+ try:
+ current=next(pending[-1])
+ except StopIteration:
+ pending.pop()
+ continue
+ identity=id(current)
+ if identity in seen:
+ continue
+ seen.add(identity)
+ total+=sys.getsizeof(current)
+ if total>limit:
+ return total
+ if isinstance(current,dict):
+ pending.append(chain(current.keys(),current.values()))
+ elif isinstance(current,(list,tuple,set,frozenset)):
+ pending.append(iter(current))
+ return total
+
+
+class _NativeProtocolRead:
+ """Selected reads valid only inside one fully fenced response operation."""
+ def __init__(self, reader, protocol, context, proof):
+ self.reader,self.protocol,self.context,self.proof=reader,protocol,context,proof
+ self.active=True
+
+ def selected(self, epoch_ids):
+ if not self.active:raise RuntimeError('Protocol read scope is closed')
+ ids=tuple(str(uuid.UUID(key)) for key in epoch_ids)
+ if len(ids)!=len(set(ids)):raise ValueError('Duplicate epoch identity')
+ if any(key not in self.proof['allowed'] for key in ids):
+ raise ValueError('Curation includes epochs outside this protocol query')
+ saved=self.reader.store._rows(self.protocol,ids,selected_only=True)
+ fingerprints=self.proof['metadata']['fingerprints']
+ return {key:self.reader.store._state(saved.get(key),fingerprints[key]) for key in ids}
+
+ def accepts_shared(self, token):
+ expected=self.context['generation']
+ return all(getattr(token,key)==getattr(expected,key) for key in
+ ('authority','project_uuid','shared_epoch','shared_generation'))
+
+
+class ProtocolStateReader:
+ def __init__(self, service, store, shared_annotations, full_state):
+ self.service,self.store,self.shared,self.full_state=service,store,shared_annotations,full_state
+ self.cache={}
+ self.rejected={}
+ self.native_cache={}
+
+ @staticmethod
+ def _native_method(owner, name, expected):
+ method=getattr(owner,name,None)
+ return getattr(method,'__func__',method) is expected
+
+ def _native_contract(self):
+ """Private-row proofs apply only to the native reader semantics.
+
+ A custom public read/query policy may add inputs that are absent from
+ this proof. It must use the full oracle, even if initially equivalent.
+ Binding headers must belong to the same native immutable-recipe reader.
+ """
+ service,store=self.service,self.store
+ if type(store) is not CurationStore or not all(self._native_method(store,name,method)
+ for name,method in _STORE_METHODS.items()):
+ return False
+ if type(service) is not WorkspaceService or not all(self._native_method(service,name,method)
+ for name,method in _SERVICE_METHODS.items()):
+ return False
+ provider=getattr(service,'binding_provider',None)
+ header=getattr(service,'binding_header_provider',None)
+ if provider is None:
+ return header is None
+ owner=getattr(provider,'__self__',None)
+ return (type(owner) is ExplorerHistory
+ and getattr(provider,'__func__',None) is _HISTORY_METHODS['protocol_binding']
+ and getattr(header,'__self__',None) is owner
+ and getattr(header,'__func__',None) is _HISTORY_METHODS['protocol_binding_header']
+ and all(self._native_method(owner,name,method) for name,method in _HISTORY_METHODS.items()))
+
+ @staticmethod
+ def _generation_key(service,index,header):
+ return ((id(index),index.generation,id(service.rows),id(service.protocols),id(service._fingerprints)),
+ header['version'] if header else None,header.get('revision_uuid') if header else None)
+
+ def _rejection_key(self, protocol):
+ if not self._native_contract():
+ return None
+ service=self.service
+ service._ready()
+ index=getattr(service,'disk_index',None)
+ if not isinstance(index,DiskMetadataIndex):
+ return None
+ index._check()
+ provider=getattr(service,'binding_header_provider',None)
+ return self._generation_key(service,index,provider(protocol) if provider else None)
+
+ def _reject(self, protocol, metadata):
+ header=metadata['binding']
+ key=(metadata['generation'],header['version'] if header else None,header.get('revision_uuid') if header else None)
+ self.rejected.pop(protocol,None)
+ self.rejected[protocol]=key
+ while len(self.rejected)>STATE_CACHE_SCOPES:
+ self.rejected.pop(next(iter(self.rejected)))
+
+ def _oracle(self, protocol, ids):
+ result,current,revision=self.full_state(protocol)
+ if any(key not in current for key in ids):
+ raise ValueError('Curation includes epochs outside this protocol query')
+ return result,current,revision,{key:current[key] for key in ids},result.get('dataset_binding',{}).get('version',0)
+
+ def _metadata(self, protocol, *, include_shared=True):
+ service=self.service
+ if not self._native_contract():
+ return None
+ service._ready()
+ index=getattr(service,'disk_index',None)
+ if not isinstance(index,DiskMetadataIndex):
+ return None # Mutable/non-indexed adapters retain the full oracle.
+ index._check()
+ provider=getattr(service,'binding_header_provider',None)
+ if provider is None and getattr(service,'binding_provider',None) is not None:
+ return None
+ header=provider(protocol) if provider else None
+ entry=service.protocols[protocol]
+ return {'generation':(id(index),index.generation,id(service.rows),id(service.protocols),id(service._fingerprints)),
+ 'project_uuid':service.project['project_uuid'],
+ 'definition':entry['definition'],'query':entry['result'],
+ 'fingerprints':service._fingerprints,'binding':header,
+ 'cells':service.cells,
+ 'source_scope_revision':service.source_scope()['revision'],
+ 'shared_revision':self.shared.snapshot()['revision'] if self.shared and include_shared else None}
+
+ @staticmethod
+ def _metadata_equal(left,right,*,plain_epochs=False):
+ # Fingerprints are validated SHA256 strings in the full-state oracle;
+ # normal dict equality is exact for those string keys and values.
+ return (left['fingerprints']==right['fingerprints']
+ and all((_epoch_query_equal(value,right[key]) if plain_epochs and key=='query'
+ else _exact_equal(value,right[key])) for key,value in left.items() if key!='fingerprints'))
+
+ def _bound_cells(self, members, metadata):
+ if metadata['binding'] is None:
+ return None
+ return frozenset(self.service.rows[key]['cell_uuid'] for key in members)
+
+ def _native_snapshot(self, metadata):
+ """Share exact, privately owned source proofs across protocol scopes.
+
+ Membership lists and the source fingerprint dictionary commonly repeat
+ across many protocols. Only immutable-in-practice snapshots owned by
+ this reader are shared; every use still compares the current mutable
+ service values exactly. Different index generations never share proofs.
+ """
+ memo={}
+ for entry in self.native_cache.values():
+ previous=entry['metadata']
+ if previous['generation']!=metadata['generation']:continue
+ for key in ('fingerprints','cells'):
+ value=metadata[key]
+ if id(value) not in memo and (value==previous[key] if key=='fingerprints'
+ else _exact_equal(value,previous[key])):
+ memo[id(value)]=previous[key]
+ for key in ('epochs','cells','source_revisions'):
+ value=metadata['query'].get(key)
+ old=previous['query'].get(key)
+ if key in metadata['query'] and key in previous['query'] and id(value) not in memo and _exact_equal(value,old):
+ memo[id(value)]=old
+ return copy.deepcopy(metadata,memo)
+
+ def _selected(self, ids, allowed, saved, fingerprints):
+ if any(key not in allowed for key in ids):
+ raise ValueError('Curation includes epochs outside this protocol query')
+ return {key:self.store._state(saved.get(key),fingerprints[key]) for key in ids}
+
+ @contextlib.contextmanager
+ def native_read(self, protocol_uuid, *, provider):
+ """Share schema verification only within this complete fenced response."""
+ from workspace_state_generation import StateGenerationAuthority
+ tracker=getattr(self.store,'state_generation',None)
+ contract=(tracker.response_contract() if type(tracker) is StateGenerationAuthority
+ and self._native_contract() else contextlib.nullcontext())
+ previous_index=getattr(self.shared,'_shared_tag_index',None)
+ try:
+ with contract:
+ with self._native_read(protocol_uuid,provider=provider) as read:
+ yield read
+ except BaseException:
+ current_index=getattr(self.shared,'_shared_tag_index',None)
+ for index in (previous_index,current_index if current_index is not previous_index else None):
+ if index is not None:
+ with contextlib.suppress(Exception):index.close()
+ raise
+
+ @contextlib.contextmanager
+ def _native_read(self, protocol_uuid, *, provider):
+ """Fence membership, selected decisions and provenance as one response.
+
+ The API owns the ordinary curation provider. Replacing it, the service
+ reader or a native adapter retains the existing public read path.
+ No token is reused across requests or after the final attestation.
+ """
+ if self.service.curation_provider is not provider:
+ yield None
+ return
+ protocol=str(uuid.UUID(protocol_uuid))
+ context=self.native_context(protocol)
+ proof=self.native_cache.get(protocol) if context is not None else None
+ if proof is None or proof['digest']!=context['metadata_digest']:
+ yield None
+ return
+ read=_NativeProtocolRead(self,protocol,context,proof)
+ from workspace_state_generation import StateGenerationAuthority
+ tracker=self.store.state_generation
+ generation_scope=(tracker.read_scope(context['generation']) if type(tracker) is StateGenerationAuthority
+ else contextlib.nullcontext())
+ previous_index=getattr(self.shared,'_shared_tag_index',None)
+ try:
+ with generation_scope:
+ yield read
+ # This is deliberately outside read_scope: a real SQL attestation
+ # validates all membership, current rows and visible provenance.
+ after=self.native_context(protocol)
+ if after is None or after['query_revision']!=context['query_revision']:
+ raise RevisionConflict({'query_revision':'changed while reading protocol response'})
+ except BaseException:
+ # Nested index operations may have loaded rows while an external
+ # writer advanced the generation. Nothing derived in a rejected
+ # response may survive under the scope's earlier generation token.
+ current_index=getattr(self.shared,'_shared_tag_index',None)
+ for index in (previous_index,current_index if current_index is not previous_index else None):
+ if index is not None:
+ with contextlib.suppress(Exception):index.close()
+ raise
+ finally:
+ read.active=False
+
+ def native_context(self, protocol_uuid, epoch_ids=()):
+ """Selected mutation/read inputs and an explicit v3 opaque revision.
+
+ Native scope generations replace whole-dataset curation serialization.
+ Exact metadata comparison remains mandatory; dense saved tags never enter
+ this bounded cache. None means the unchanged legacy contract applies.
+ """
+ protocol=str(uuid.UUID(protocol_uuid))
+ ids=tuple(str(uuid.UUID(key)) for key in epoch_ids)
+ if len(ids)!=len(set(ids)):raise ValueError('Duplicate epoch identity')
+ tracker=getattr(self.store,'state_generation',None)
+ if tracker is None or not self._native_contract():return None
+ if self.shared is not None and (type(self.shared) is not SharedAnnotations or not all(
+ self._native_method(self.shared,name,method) for name,method in _SHARED_METHODS.items())):
+ return None
+ token=tracker.token(protocol)
+ if token is None:return None
+ metadata=self._metadata(protocol,include_shared=False)
+ if metadata is None:return None
+ cached=self.native_cache.get(protocol)
+ if cached is None or not self._metadata_equal(metadata,cached['metadata'],plain_epochs=cached['plain_epochs']) or self._bound_cells(cached['members'],metadata)!=cached['bound_cells']:
+ self.native_cache.pop(protocol,None)
+ if _retained_bytes(metadata,STATE_CACHE_BYTES)>STATE_CACHE_BYTES:return None
+ snapshot=self._native_snapshot(metadata)
+ plain_epochs=_plain_epoch_records(snapshot['query'].get('epochs'))
+ result=self.service.query_result(protocol)
+ for row in result['epochs']:row['metadata_hash']=metadata['fingerprints'][row['uuid']]
+ result['metadata_fingerprint_version']=2
+ result['source_scope']={'revision':metadata['source_scope_revision']}
+ members=tuple(row['uuid'] for row in result['epochs'])
+ digest=checksum({'query':result,'definition':metadata['definition'],'cell_metadata':metadata['cells']})
+ after=self._metadata(protocol,include_shared=False)
+ if after is None or not self._metadata_equal(after,snapshot,plain_epochs=plain_epochs) or tracker.token(protocol)!=token:return None
+ allowed=None
+ for entry in self.native_cache.values():
+ if members==entry['members']:
+ members,allowed=entry['members'],entry['allowed']
+ break
+ cached={'metadata':snapshot,'members':members,'allowed':allowed if allowed is not None else frozenset(members),'digest':digest,
+ 'plain_epochs':plain_epochs,
+ 'bound_cells':self._bound_cells(members,after),
+ 'source_revisions':tuple(result['source_revisions']),
+ 'binding_version':result.get('dataset_binding',{}).get('version',0)}
+ cached['size']=_retained_bytes(cached,STATE_CACHE_BYTES)+4096
+ if cached['size']>STATE_CACHE_BYTES:return None
+ self.native_cache[protocol]=cached
+ # Shared immutable snapshots count once in the retained graph.
+ # Summing per-protocol estimates would evict identical large proofs.
+ while (len(self.native_cache)>STATE_CACHE_SCOPES
+ or _retained_bytes(self.native_cache,STATE_CACHE_BYTES)+4096*len(self.native_cache)>STATE_CACHE_BYTES):
+ self.native_cache.pop(next(iter(self.native_cache)))
+ if any(key not in cached['allowed'] for key in ids):
+ raise ValueError('Curation includes epochs outside this protocol query')
+ revision='protocol-state-v3:'+checksum({'metadata':cached['digest'],'generation':asdict(token)})
+ return {'query_revision':revision,'query_revision_contract':'protocol-state-v3','generation':token,
+ 'binding_version':cached['binding_version'],'source_revisions':cached['source_revisions'],
+ 'metadata_digest':cached['digest'],
+ 'fingerprints':{key:metadata['fingerprints'][key] for key in ids}}
+
+ def assert_context_locked(self, protocol_uuid, context):
+ self.store.state_generation.assert_current_locked(context['generation'])
+ protocol=str(uuid.UUID(protocol_uuid))
+ cached=self.native_cache.get(protocol)
+ current=self._metadata(protocol,include_shared=False)
+ if (cached is None or current is None or cached['digest']!=context['metadata_digest']
+ or not self._metadata_equal(current,cached['metadata'],plain_epochs=cached['plain_epochs'])
+ or self._bound_cells(cached['members'],current)!=cached['bound_cells']):
+ raise RevisionConflict({'query_revision':'metadata changed during mutation'})
+ return context['query_revision']
+
+ def materialized_state(self, protocol_uuid):
+ context=self.native_context(protocol_uuid)
+ if context is None:return self.full_state(protocol_uuid)
+ result,current,_=self.full_state(protocol_uuid)
+ after=self.native_context(protocol_uuid)
+ if after is None or after['query_revision']!=context['query_revision']:
+ raise RevisionConflict({'query_revision':'changed during read'})
+ result['query_revision_contract']='protocol-state-v3'
+ return result,current,context['query_revision']
+
+ def read_selected(self, protocol_uuid, epoch_ids):
+ """Return selected current states, exact legacy revision, binding version.
+
+ Callers hold the ordinary workspace DB lock. Native authority reads only
+ selected SQL rows and fences their generation; legacy reuse verifies all
+ saved values. Every cache admission proves exact current metadata.
+ """
+ protocol=str(uuid.UUID(protocol_uuid))
+ ids=tuple(str(uuid.UUID(key)) for key in epoch_ids)
+ if len(set(ids))!=len(ids):
+ raise ValueError('Duplicate epoch identity')
+ context=self.native_context(protocol,ids)
+ if context is not None:
+ saved=self.store._rows(protocol,ids,selected_only=True)
+ selected={key:self.store._state(saved.get(key),context['fingerprints'][key]) for key in ids}
+ tracker=self.store.state_generation
+ after=self.native_context(protocol,ids)
+ if after is None or after['query_revision']!=context['query_revision']:
+ raise RevisionConflict({'query_revision':'changed during selected read'})
+ return selected,context['query_revision'],context['binding_version']
+ if protocol in self.rejected:
+ key=self._rejection_key(protocol)
+ previous=self.rejected.pop(protocol)
+ if key is not None and key==previous:
+ # This memo caches no scientific state or revision. Every read
+ # still runs the complete oracle; only futile proof allocation
+ # is skipped until generation/binding changes (no TTL).
+ self.rejected[protocol]=key
+ _,_,revision,selected,version=self._oracle(protocol,ids)
+ return selected,revision,version
+ metadata=self._metadata(protocol)
+ cached=self.cache.get(protocol)
+ if metadata is not None and cached is not None and self._metadata_equal(metadata,cached['metadata']):
+ saved=self.store._rows(protocol,cached['members'])
+ if (_exact_equal(saved,cached['saved'])
+ and self._bound_cells(cached['members'],metadata)==cached['bound_cells']):
+ self.cache.pop(protocol)
+ self.cache[protocol]=cached
+ return (self._selected(ids,cached['allowed'],saved,metadata['fingerprints']),
+ cached['revision'],cached['binding_version'])
+ self.cache.pop(protocol,None)
+ # Check admission before copying large metadata dictionaries. An oversized
+ # proof serves through the unchanged full-state path without retention.
+ snapshot=(copy.deepcopy(metadata) if metadata is not None
+ and _retained_bytes(metadata,STATE_CACHE_BYTES)<=STATE_CACHE_BYTES else None)
+ result,current,revision,selected,version=self._oracle(protocol,ids)
+ if snapshot is None:
+ if metadata is not None:
+ self._reject(protocol,metadata)
+ return selected,revision,version
+ after=self._metadata(protocol)
+ if after is None or not self._metadata_equal(snapshot,after):
+ return selected,revision,version
+ members=tuple(row['uuid'] for row in result['epochs'])
+ saved=self.store._rows(protocol,members)
+ # Verify the oracle's exact source/shared/binding state and every saved
+ # decision/default against fresh input. A racing writer prevents caching.
+ header=after['binding']
+ if (result['source_scope']['revision']!=after['source_scope_revision']
+ or result.get('shared_annotations_revision')!=after['shared_revision']
+ or not _exact_equal(version,header['version'] if header else 0)
+ or result.get('dataset_binding',{}).get('revision_uuid')!=(header.get('revision_uuid') if header else None)
+ or any(not _exact_equal(self.store._state(saved.get(key),after['fingerprints'][key]),state) for key,state in current.items())):
+ return selected,revision,version
+ bound_cells=self._bound_cells(members,after)
+ if header is not None and bound_cells!=frozenset(cell['uuid'] for cell in result['cells']):
+ return selected,revision,version
+ payload={'metadata':snapshot,'members':members,'allowed':frozenset(members),
+ 'saved':saved,'revision':revision,'binding_version':version,
+ 'bound_cells':bound_cells}
+ size=_retained_bytes(payload,STATE_CACHE_BYTES)+4096
+ if size<=STATE_CACHE_BYTES:
+ # SQL adapters return fresh row objects; still copy saved values so
+ # no mutable provider-owned object can silently change the proof.
+ payload['saved']=copy.deepcopy(saved)
+ payload['size']=size
+ self.cache[protocol]=payload
+ while len(self.cache)>STATE_CACHE_SCOPES or sum(item['size'] for item in self.cache.values())>STATE_CACHE_BYTES:
+ self.cache.pop(next(iter(self.cache)))
+ else:
+ self._reject(protocol,after)
+ return selected,revision,version
diff --git a/python/workspace_recovery_generation.py b/python/workspace_recovery_generation.py
new file mode 100644
index 0000000..c71d561
--- /dev/null
+++ b/python/workspace_recovery_generation.py
@@ -0,0 +1,421 @@
+"""Transactional, bounded recovery deltas for current project state.
+
+Only exact primary-key identities are retained in a fixed-slot SQL hint ring. A project clock
+serializes commit order; capture holds that clock while its SQL snapshot and
+outside-SQL mirror are made durable. Overwriting an old slot advances a retention
+floor, so an older mirror rebuilds instead of silently missing a deletion. The
+ring has no tag payload or secondary generation index that can grow during bulk
+transactions; captured keys are deduplicated by their complete typed identity.
+"""
+from __future__ import annotations
+
+import contextlib
+import dataclasses
+import hashlib
+import json
+import re
+import uuid
+
+from workspace_state_generation import StateGenerationAuthority, SCHEMA
+
+PREFIX='rieke_recovery_v1_'
+CLOCK='app_recovery_clock'
+FEED='app_recovery_changes'
+INSTANCE='app_recovery_instance'
+ANCHOR_LOCK='rieke_recovery_instance_v1'
+RECOVERY_WINDOW=20000
+TRIM_INTERVAL=1000
+TABLE_NAMES=('annotation_profile','shared_annotation','curation','protocol_workspace',
+ 'data_store_state','protocol_tree_layout','search_preset','search_preset_version',
+ 'protocol_binding','explorer_revision','dataset_revision','search_query_last_run','source')
+TABLE_IDS={name:index+1 for index,name in enumerate(TABLE_NAMES)}
+IDENTIFIER=re.compile(r'^[a-z][a-z0-9_]*$')
+
+
+def _name(value):
+ if not isinstance(value,str) or not IDENTIFIER.fullmatch(value):raise ValueError('Unsupported recovery SQL identifier')
+ return '`'+value+'`'
+
+
+def table_specs(connection):
+ """Canonical table/column/PK ordering, including absent optional tables."""
+ placeholders=','.join('%s' for _ in TABLE_NAMES)
+ table_rows=connection.query(f'SELECT TABLE_NAME,ENGINE FROM information_schema.TABLES '
+ f'WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN ({placeholders})',(SCHEMA,*TABLE_NAMES),as_dict=True,reconnect=False).fetchall()
+ present={row['TABLE_NAME']:row['ENGINE'] for row in table_rows}
+ if any(engine!='InnoDB' for engine in present.values()):raise ValueError('Recovery deltas require InnoDB source tables')
+ columns=connection.query('SELECT TABLE_NAME,COLUMN_NAME,COLUMN_TYPE,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,IS_NULLABLE '
+ f'FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN ({placeholders}) '
+ 'ORDER BY TABLE_NAME,ORDINAL_POSITION',(SCHEMA,*TABLE_NAMES),as_dict=True,reconnect=False).fetchall()
+ keys=connection.query('SELECT TABLE_NAME,COLUMN_NAME FROM information_schema.STATISTICS '
+ f"WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN ({placeholders}) AND INDEX_NAME='PRIMARY' "
+ 'ORDER BY TABLE_NAME,SEQ_IN_INDEX',(SCHEMA,*TABLE_NAMES),as_dict=True,reconnect=False).fetchall()
+ result={}
+ for table in TABLE_NAMES:
+ if table not in present:continue
+ fields=[row for row in columns if row['TABLE_NAME']==table]
+ primary=[row['COLUMN_NAME'] for row in keys if row['TABLE_NAME']==table]
+ by_name={row['COLUMN_NAME']:row for row in fields}
+ if not primary or 'project_uuid' not in by_name:raise ValueError('Recovery source lacks project identity or primary key')
+ maximum=2
+ for key in primary:
+ _name(key)
+ field=by_name[key]
+ if field['IS_NULLABLE']!='NO':raise ValueError('Recovery primary keys cannot be nullable')
+ if field['DATA_TYPE'] in {'char','varchar','enum'}:
+ maximum+=4+6*int(field['CHARACTER_MAXIMUM_LENGTH'])
+ elif field['DATA_TYPE'] in {'tinyint','smallint','mediumint','int','bigint'}:maximum+=33
+ else:raise ValueError('Unsupported recovery primary-key type')
+ if maximum>1024:raise ValueError('Recovery primary key exceeds the exact bounded feed key')
+ result[table]={'primary_keys':primary,'columns':[row['COLUMN_NAME'] for row in fields],
+ 'json_columns':[row['COLUMN_NAME'] for row in fields if row['DATA_TYPE']=='json'],
+ 'signature':[(row['COLUMN_NAME'],row['COLUMN_TYPE'],row['IS_NULLABLE']) for row in fields]}
+ return result
+
+
+def _change(alias, table, keys, deleted):
+ array='JSON_ARRAY('+','.join(alias+'.'+_name(key) for key in keys)+')'
+ return (f'INSERT INTO `{SCHEMA}`.`{CLOCK}` (project_uuid,scope_epoch,generation,retained_after) '
+ f'VALUES ({alias}.project_uuid,UUID(),1,0) ON DUPLICATE KEY UPDATE generation=generation+1; '
+ f'UPDATE `{SCHEMA}`.`{CLOCK}` SET retained_after=GREATEST(retained_after,GREATEST(generation,{RECOVERY_WINDOW})-{RECOVERY_WINDOW}) '
+ f'WHERE project_uuid={alias}.project_uuid; '
+ f'INSERT INTO `{SCHEMA}`.`{FEED}` (project_uuid,slot,table_id,key_bytes,generation,deleted) '
+ f'SELECT {alias}.project_uuid,MOD(generation,{RECOVERY_WINDOW}),{TABLE_IDS[table]},'
+ f'CAST({array} AS CHAR CHARACTER SET utf8mb4),generation,{int(deleted)} '
+ f'FROM `{SCHEMA}`.`{CLOCK}` WHERE project_uuid={alias}.project_uuid '
+ 'ON DUPLICATE KEY UPDATE table_id=VALUES(table_id),key_bytes=VALUES(key_bytes),'
+ 'generation=VALUES(generation),deleted=VALUES(deleted);')
+
+
+def _legacy_change(alias, table, keys, deleted, *, trim=True):
+ array='JSON_ARRAY('+','.join(alias+'.'+_name(key) for key in keys)+')'
+ body=(f'INSERT INTO `{SCHEMA}`.`{CLOCK}` (project_uuid,scope_epoch,generation,retained_after) '
+ f'VALUES ({alias}.project_uuid,UUID(),1,0) ON DUPLICATE KEY UPDATE generation=generation+1; '
+ f'INSERT INTO `{SCHEMA}`.`{FEED}` (project_uuid,table_id,key_bytes,generation,deleted) '
+ f'SELECT {alias}.project_uuid,{TABLE_IDS[table]},CAST({array} AS CHAR CHARACTER SET utf8mb4),generation,{int(deleted)} '
+ f'FROM `{SCHEMA}`.`{CLOCK}` WHERE project_uuid={alias}.project_uuid '
+ 'ON DUPLICATE KEY UPDATE generation=VALUES(generation),deleted=VALUES(deleted); '
+ f'IF (SELECT MOD(generation,{TRIM_INTERVAL})=0 FROM `{SCHEMA}`.`{CLOCK}` WHERE project_uuid={alias}.project_uuid) THEN '
+ f'UPDATE `{SCHEMA}`.`{CLOCK}` SET retained_after=GREATEST(retained_after,GREATEST(generation,{RECOVERY_WINDOW})-{RECOVERY_WINDOW}) '
+ f'WHERE project_uuid={alias}.project_uuid; '
+ f'DELETE FROM `{SCHEMA}`.`{FEED}` WHERE project_uuid={alias}.project_uuid AND generation<='
+ f'(SELECT retained_after FROM `{SCHEMA}`.`{CLOCK}` WHERE project_uuid={alias}.project_uuid); END IF;')
+
+ return body if trim else body.split(' IF (SELECT MOD(generation',1)[0]
+
+
+def manifest_from_specs(specs, *, legacy=None):
+ manifest={}
+ for table,spec in specs.items():
+ for event,suffix in (('INSERT','ai'),('UPDATE','au'),('DELETE','ad')):
+ aliases=('NEW',) if event=='INSERT' else ('OLD',) if event=='DELETE' else ('OLD','NEW')
+ body='BEGIN '+' '.join((_change(alias,table,spec['primary_keys'],alias=='OLD') if legacy is None else
+ _legacy_change(alias,table,spec['primary_keys'],alias=='OLD',trim=legacy)) for alias in aliases)+' END'
+ manifest[PREFIX+table+'_'+suffix]={'table':table,'event':event,'timing':'AFTER','body':body}
+ return manifest
+
+
+def recovery_trigger_manifest(connection):
+ return manifest_from_specs(table_specs(connection))
+
+
+def _equal_trigger(row, expected):
+ return all(' '.join(str(row[field]).split())==' '.join(expected[key].split()) for field,key in
+ (('EVENT_OBJECT_TABLE','table'),('EVENT_MANIPULATION','event'),('ACTION_TIMING','timing'),('ACTION_STATEMENT','body')))
+
+
+@dataclasses.dataclass
+class RecoveryCapture:
+ watermark: dict
+ complete: bool
+ keys: dict
+ primary_keys: dict
+ columns: dict
+ json_columns: dict
+ _verify: object=dataclasses.field(repr=False)
+
+ def verify(self):
+ self._verify()
+ return True
+
+
+class RecoveryGenerationAuthority:
+ def __init__(self, connection, project_uuid):
+ self.connection=connection;self.project_uuid=str(uuid.UUID(str(project_uuid)))
+ self.observer=StateGenerationAuthority(connection,self.project_uuid)
+ self.ready=False;self.reason='not bootstrapped';self.specs={};self.manifest={}
+ self._driver=None;self._socket=None;self._private_epoch=str(uuid.uuid4())
+ self._schema_ddl=None;self._source_specs_ddl=None;self._needs_full=False
+
+ def _rows(self, sql, args=()):return self.observer._rows(sql,args)
+
+ def _triggers(self):return self.observer._triggers()
+
+ def _instance(self, *, initialize=False):
+ owner=self._rows('SELECT IS_USED_LOCK(%s) AS owner',(ANCHOR_LOCK,))[0]['owner']
+ current=self._rows('SELECT CONNECTION_ID() AS identity')[0]['identity']
+ if owner is None and initialize:
+ if self._rows('SELECT GET_LOCK(%s,0) AS acquired',(ANCHOR_LOCK,))[0]['acquired']!=1:
+ raise ValueError('Recovery instance anchor changed during bootstrap')
+ owner=current
+ self.connection.query(f'INSERT INTO `{SCHEMA}`.`{INSTANCE}` (singleton,instance_epoch,owner_connection_id) '
+ 'VALUES (1,%s,%s) ON DUPLICATE KEY UPDATE instance_epoch=VALUES(instance_epoch),owner_connection_id=VALUES(owner_connection_id)',
+ (str(uuid.uuid4()),owner),reconnect=False)
+ rows=self._rows(f'SELECT instance_epoch,owner_connection_id FROM `{SCHEMA}`.`{INSTANCE}` WHERE singleton=1')
+ if owner is None or len(rows)!=1 or int(rows[0]['owner_connection_id'])!=int(owner):
+ raise ValueError('Recovery server-instance anchor is unavailable')
+ return {'epoch':str(uuid.UUID(rows[0]['instance_epoch'])),'owner':int(owner)}
+
+ def _clock(self, *, lock=False, create=False):
+ rows=self._rows(f'SELECT scope_epoch,generation,retained_after FROM `{SCHEMA}`.`{CLOCK}` '
+ 'WHERE project_uuid=%s'+(' FOR UPDATE' if lock else ''),(self.project_uuid,))
+ if not rows and create:
+ self.connection.query(f'INSERT IGNORE INTO `{SCHEMA}`.`{CLOCK}` '
+ '(project_uuid,scope_epoch,generation,retained_after) VALUES (%s,%s,0,0)',
+ (self.project_uuid,str(uuid.uuid4())),reconnect=False)
+ return self._clock(lock=lock)
+ if len(rows)!=1:raise ValueError('Recovery generation row is unavailable')
+ row=rows[0]
+ return {'scope_epoch':str(uuid.UUID(row['scope_epoch'])),'generation':int(row['generation']),
+ 'retained_after':int(row['retained_after'])}
+
+ def _verify_tables(self, ddl):
+ if self._schema_ddl==ddl:return
+ expected={CLOCK:[('project_uuid','varchar(36)'),('scope_epoch','char(36)'),
+ ('generation','bigint unsigned'),('retained_after','bigint unsigned')],
+ FEED:[('project_uuid','varchar(36)'),('slot','smallint unsigned'),('table_id','smallint unsigned'),('key_bytes','varbinary(1024)'),
+ ('generation','bigint unsigned'),('deleted','tinyint unsigned')],
+ INSTANCE:[('singleton','tinyint unsigned'),('instance_epoch','char(36)'),('owner_connection_id','bigint unsigned')]}
+ rows=self._rows('SELECT TABLE_NAME,COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE FROM information_schema.COLUMNS '
+ 'WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN (%s,%s,%s) ORDER BY TABLE_NAME,ORDINAL_POSITION',(SCHEMA,CLOCK,FEED,INSTANCE))
+ for table,columns in expected.items():
+ actual=[(row['COLUMN_NAME'],row['COLUMN_TYPE']) for row in rows if row['TABLE_NAME']==table]
+ if actual!=columns or any(row['IS_NULLABLE']!='NO' for row in rows if row['TABLE_NAME']==table):
+ raise ValueError('Recovery authority columns are incompatible')
+ rows=self._rows("SELECT TABLE_NAME,COLUMN_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=%s "
+ "AND TABLE_NAME IN (%s,%s,%s) AND INDEX_NAME='PRIMARY' ORDER BY TABLE_NAME,SEQ_IN_INDEX",(SCHEMA,CLOCK,FEED,INSTANCE))
+ for table,keys in ((CLOCK,['project_uuid']),(FEED,['project_uuid','slot']),(INSTANCE,['singleton'])):
+ if [row['COLUMN_NAME'] for row in rows if row['TABLE_NAME']==table]!=keys:
+ raise ValueError('Recovery authority primary key is incompatible')
+ self._schema_ddl=ddl
+
+ def _contract(self):
+ driver=getattr(self.connection,'_conn',None)
+ if driver is None:raise ValueError('Native connection required for recovery deltas')
+ socket=getattr(driver,'_sock',None)
+ if driver is not self._driver or socket is not self._socket:
+ self._needs_full=self._driver is not None
+ self._driver,self._socket=driver,socket;self._private_epoch=str(uuid.uuid4());self._schema_ddl=None;self._source_specs_ddl=None
+ before=self.observer._ddl();self.observer._active_ddl_guard()
+ server=self._rows('SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id')[0]
+ if not str(server['version']).startswith('8.4.'):raise ValueError('Recovery deltas require verified MySQL 8.4')
+ self._verify_tables(before)
+ # Column/primary-key metadata only changes through the guarded DDL
+ # vector. Keep live table-incarnation and trigger checks on every pass.
+ if self._source_specs_ddl != before:
+ current_specs=table_specs(self.connection)
+ if current_specs!=self.specs:raise ValueError('Recovery source schema changed; bootstrap required')
+ self._source_specs_ddl=before
+ watched=(*self.specs,CLOCK,FEED,INSTANCE)
+ placeholders=','.join('%s' for _ in watched)
+ rows=self._rows(f'SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN ({placeholders})',
+ tuple(SCHEMA+'/'+table for table in watched))
+ tables={row['NAME']:int(row['TABLE_ID']) for row in rows}
+ if set(tables)!={SCHEMA+'/'+table for table in watched}:raise ValueError('Recovery InnoDB table incarnation unavailable')
+ rows=self._triggers();found={row['TRIGGER_NAME']:row for row in rows if row['TRIGGER_NAME'].startswith(PREFIX)}
+ if set(found)!=set(self.manifest) or not all(_equal_trigger(row,self.manifest[name]) for name,row in found.items()):
+ raise ValueError('Recovery trigger contract changed or is incomplete')
+ self.observer._active_ddl_guard();after=self.observer._ddl()
+ if before!=after:raise ValueError('DDL changed during recovery attestation')
+ instance=self._instance()
+ payload={'contract':'rieke-recovery-v1','server':{key:server[key] for key in ('server_uuid','version')},'instance':instance,
+ 'tables':tables,'schema':self.specs,'ddl':after,'triggers':found}
+ return hashlib.sha256(json.dumps(payload,sort_keys=True,default=str).encode()).hexdigest()
+
+ def bootstrap(self):
+ if self.observer._in_transaction():
+ self.reason='Recovery bootstrap requires no active transaction';return self
+ locked=False
+ try:
+ if getattr(self.connection,'_conn',None) is None:raise ValueError('Native connection required')
+ locked=self._rows("SELECT GET_LOCK('rieke_recovery_generation_v1',10) AS acquired")[0]['acquired']==1
+ if not locked:raise ValueError('Another recovery bootstrap is running')
+ specs=table_specs(self.connection)
+ existing_tables={row['TABLE_NAME'] for row in self._rows(
+ 'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN (%s,%s,%s)',
+ (SCHEMA,CLOCK,FEED,INSTANCE))}
+ expected=manifest_from_specs(specs)
+ present={row['TRIGGER_NAME']:row for row in self._triggers() if row['TRIGGER_NAME'].startswith(PREFIX)}
+ legacy_manifests=[manifest_from_specs(specs,legacy=trim) for trim in (False,True)]
+ if set(present)-set(expected):raise ValueError('Unknown recovery trigger or removed source table')
+ old_triggers=[]
+ for name,row in present.items():
+ if _equal_trigger(row,expected[name]):continue
+ if not any(_equal_trigger(row,manifest[name]) for manifest in legacy_manifests):
+ raise ValueError('Existing recovery trigger differs from a known app-owned contract')
+ old_triggers.append(name)
+ if FEED in existing_tables:
+ columns=self._rows('SELECT COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE FROM information_schema.COLUMNS '
+ 'WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s ORDER BY ORDINAL_POSITION',(SCHEMA,FEED))
+ primary=self._rows("SELECT COLUMN_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s "
+ "AND INDEX_NAME='PRIMARY' ORDER BY SEQ_IN_INDEX",(SCHEMA,FEED))
+ actual=[(row['COLUMN_NAME'],row['COLUMN_TYPE']) for row in columns]
+ legacy=[('project_uuid','varchar(36)'),('table_id','smallint unsigned'),('key_bytes','varbinary(1024)'),
+ ('generation','bigint unsigned'),('deleted','tinyint unsigned')]
+ ring=[legacy[0],('slot','smallint unsigned'),*legacy[1:]]
+ if any(row['IS_NULLABLE']!='NO' for row in columns):raise ValueError('Recovery feed schema is incompatible')
+ if actual==legacy and [row['COLUMN_NAME'] for row in primary]==['project_uuid','table_id','key_bytes']:
+ # Only this discardable hint table and exactly known owned
+ # triggers are replaced. Canonical authored rows stay put.
+ for name in present:self.connection.query(f'DROP TRIGGER `{SCHEMA}`.`{name}`',reconnect=False)
+ self.connection.query(f'DROP TABLE `{SCHEMA}`.`{FEED}`',reconnect=False)
+ existing_tables.remove(FEED);present={};old_triggers=[];self._needs_full=True
+ elif actual!=ring or [row['COLUMN_NAME'] for row in primary]!=['project_uuid','slot']:
+ raise ValueError('Recovery feed schema is incompatible')
+ for name in old_triggers:
+ self.connection.query(f'DROP TRIGGER `{SCHEMA}`.`{name}`',reconnect=False)
+ present.pop(name);self._needs_full=True
+ if CLOCK not in existing_tables:
+ self.connection.query(f'''CREATE TABLE IF NOT EXISTS `{SCHEMA}`.`{CLOCK}` (
+ project_uuid varchar(36) NOT NULL,scope_epoch char(36) NOT NULL,
+ generation bigint unsigned NOT NULL,retained_after bigint unsigned NOT NULL,
+ PRIMARY KEY(project_uuid)) ENGINE=InnoDB''',reconnect=False)
+ if FEED not in existing_tables:
+ self.connection.query(f'''CREATE TABLE IF NOT EXISTS `{SCHEMA}`.`{FEED}` (
+ project_uuid varchar(36) NOT NULL,slot smallint unsigned NOT NULL,table_id smallint unsigned NOT NULL,key_bytes varbinary(1024) NOT NULL,
+ generation bigint unsigned NOT NULL,deleted tinyint unsigned NOT NULL,
+ PRIMARY KEY(project_uuid,slot)) ENGINE=InnoDB''',reconnect=False)
+ if INSTANCE not in existing_tables:
+ self.connection.query(f'''CREATE TABLE IF NOT EXISTS `{SCHEMA}`.`{INSTANCE}` (
+ singleton tinyint unsigned NOT NULL,instance_epoch char(36) NOT NULL,owner_connection_id bigint unsigned NOT NULL,
+ PRIMARY KEY(singleton)) ENGINE=InnoDB''',reconnect=False)
+ self._instance(initialize=True)
+ expected=manifest_from_specs(specs)
+ present={row['TRIGGER_NAME']:row for row in self._triggers() if row['TRIGGER_NAME'].startswith(PREFIX)}
+ if set(present)-set(expected):raise ValueError('Unknown recovery trigger or removed source table')
+ for name,trigger in expected.items():
+ if name in present:
+ if not _equal_trigger(present[name],trigger):raise ValueError('Existing recovery trigger differs from expected contract')
+ else:
+ self.connection.query(f"CREATE TRIGGER `{SCHEMA}`.`{name}` AFTER {trigger['event']} ON "
+ f"`{SCHEMA}`.`{trigger['table']}` FOR EACH ROW {trigger['body']}",reconnect=False)
+ self.specs,self.manifest=specs,expected
+ self._source_specs_ddl=None
+ self._contract();self._clock(create=True)
+ self.ready=True;self.reason=None
+ except Exception as error:
+ self.ready=False;self.reason=str(error)
+ finally:
+ if locked:
+ with contextlib.suppress(Exception):self.connection.query("SELECT RELEASE_LOCK('rieke_recovery_generation_v1')",reconnect=False)
+ return self
+
+ def token(self):
+ if not self.ready or self.observer._in_transaction():return None
+ try:
+ before=self._contract();clock=self._clock(create=True)
+ if self._contract()!=before:raise ValueError('Recovery authority changed while reading watermark')
+ return {'format':'rieke-recovery-watermark','version':1,'project_uuid':self.project_uuid,
+ 'authority':before,'scope_epoch':clock['scope_epoch'],'generation':clock['generation']}
+ except Exception as error:
+ self.reason=str(error);self._private_epoch=str(uuid.uuid4());self._schema_ddl=None;self._needs_full=True
+ return None
+
+ @staticmethod
+ def _continues(prior,current,floor):
+ return (isinstance(prior,dict) and set(prior)==set(current)
+ and all(prior[key]==current[key] for key in current if key!='generation')
+ and type(prior['generation']) is int and floor<=prior['generation']<=current['generation'])
+
+ @contextlib.contextmanager
+ def capture(self, prior_watermark=None, *, limit=100000):
+ if self.observer._in_transaction():raise ValueError('Recovery capture cannot nest inside a transaction')
+ # Optional tables can be created lazily by a newly used UI feature.
+ try:
+ if not self.ready:
+ self.bootstrap()
+ if self.ready:
+ try:self._contract()
+ except Exception:self.bootstrap()
+ if self.ready:self._clock(create=True)
+ except Exception as error:
+ self.reason=str(error);self._needs_full=True;self.ready=False
+ if not self.ready:
+ yield None
+ return
+ try:
+ # DataJoint normally starts WITH CONSISTENT SNAPSHOT. Read committed
+ # is scoped to this next transaction only; after the clock lock no
+ # watched project writer can commit, so all row reads are coherent.
+ self.connection.query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED',reconnect=False)
+ with self.connection.transaction:
+ # A locking read does not establish an old RR consistent-read
+ # snapshot. The first canonical read comes after this lock, so
+ # every project trigger writer is either committed or blocked.
+ clock=self._clock(lock=True)
+ for table in (*self.specs,CLOCK,FEED,INSTANCE):self._rows(f'SELECT 1 FROM `{SCHEMA}`.{_name(table)} LIMIT 0')
+ authority=self._contract()
+ watermark={'format':'rieke-recovery-watermark','version':1,'project_uuid':self.project_uuid,
+ 'authority':authority,'scope_epoch':clock['scope_epoch'],'generation':clock['generation']}
+ complete=not self._needs_full and self._continues(prior_watermark,watermark,clock['retained_after'])
+ keys={table:[] for table in self.specs}
+ if complete:
+ changes=self._rows(f'SELECT table_id,key_bytes FROM `{SCHEMA}`.`{FEED}` '
+ 'WHERE project_uuid=%s AND generation>%s ORDER BY generation LIMIT %s',
+ (self.project_uuid,prior_watermark['generation'],limit+1))
+ if len(changes)>limit:complete=False
+ else:
+ by_id={value:key for key,value in TABLE_IDS.items()};seen=set()
+ for row in changes:
+ table=by_id.get(int(row['table_id']))
+ if table not in self.specs:raise ValueError('Recovery feed references an unknown table')
+ raw=row['key_bytes'];raw=raw.decode('utf8') if isinstance(raw,(bytes,bytearray)) else raw
+ key=json.loads(raw)
+ if not isinstance(key,list) or len(key)!=len(self.specs[table]['primary_keys']):
+ raise ValueError('Recovery feed primary key is malformed')
+ identity=(table,raw)
+ if identity not in seen:
+ keys[table].append(tuple(key));seen.add(identity)
+ def verify():
+ now=self._clock(lock=True)
+ if self._contract()!=authority or now['scope_epoch']!=clock['scope_epoch'] or now['generation']!=clock['generation']:
+ raise ValueError('Recovery snapshot changed during capture')
+ plan=RecoveryCapture(watermark,complete,keys,
+ {table:spec['primary_keys'] for table,spec in self.specs.items()},
+ {table:spec['columns'] for table,spec in self.specs.items()},
+ {table:spec['json_columns'] for table,spec in self.specs.items()},verify)
+ yield plan
+ verify()
+ self._needs_full=False
+ except Exception as error:
+ self.reason=str(error);self._private_epoch=str(uuid.uuid4());self._schema_ddl=None;self._needs_full=True
+ raise
+
+ def prune(self, durable_watermark, *, keep_generations=RECOVERY_WINDOW):
+ """Keep a bounded recent window for derived consumers after durability.
+
+ Trigger-side trimming may already have advanced the floor during a large
+ transaction. A lagging mirror/index always rebuilds canonical state.
+ """
+ if self.observer._in_transaction():raise ValueError('Recovery pruning cannot nest inside a transaction')
+ if not isinstance(durable_watermark,dict):raise ValueError('Expected a durable recovery watermark')
+ if type(keep_generations) is not int or not 0<=keep_generations<=RECOVERY_WINDOW:
+ raise ValueError('Recovery retention must fit the bounded generation window')
+ with self.connection.transaction:
+ clock=self._clock(lock=True)
+ if (durable_watermark.get('project_uuid')!=self.project_uuid
+ or durable_watermark.get('scope_epoch')!=clock['scope_epoch']
+ or type(durable_watermark.get('generation')) is not int
+ or not 0<=durable_watermark['generation']<=clock['generation']):
+ raise ValueError('Durable watermark does not match this recovery clock')
+ floor=max(clock['retained_after'],max(0,durable_watermark['generation']-keep_generations))
+ self.connection.query(f'DELETE FROM `{SCHEMA}`.`{FEED}` WHERE project_uuid=%s AND (generation<=%s OR generation>%s)',
+ (self.project_uuid,floor,clock['generation']),reconnect=False)
+ self.connection.query(f'UPDATE `{SCHEMA}`.`{CLOCK}` SET retained_after=%s WHERE project_uuid=%s',
+ (floor,self.project_uuid),reconnect=False)
+
+
+def bootstrap(connection, project_uuid):return RecoveryGenerationAuthority(connection,project_uuid).bootstrap()
+
+# Public name used by the snapshot mirror integration.
+RecoveryTracker=RecoveryGenerationAuthority
diff --git a/python/workspace_recovery_store.py b/python/workspace_recovery_store.py
new file mode 100644
index 0000000..d01e730
--- /dev/null
+++ b/python/workspace_recovery_store.py
@@ -0,0 +1,543 @@
+"""Compact durable current-state recovery, independent of the live SQL store.
+
+There is one compressed after-image per canonical primary key, not one snapshot
+per edit. SQLite commits the changed rows and coverage watermark together. The
+small JSON pointer is published only after the first database is durable. Old
+v1 snapshots remain readable through workspace_state_snapshot.load.
+"""
+from __future__ import annotations
+
+import datetime as dt
+from contextlib import closing, contextmanager
+import hashlib
+import json
+import logging
+import os
+from pathlib import Path
+import re
+import sqlite3
+import struct
+import tempfile
+import uuid
+import zlib
+
+FORMAT = 'rieke-app-state'
+VERSION = 2
+CURRENT_PATTERN = re.compile(r'current-[0-9a-f]{32}\.sqlite\Z')
+CHECKPOINT_PATTERN = re.compile(r'checkpoint-v2-\d{4}-\d{2}-\d{2}\.sqlite\Z')
+# Only checkpoints generated by this version are automatically retired. Older
+# backups and explicit before-restore snapshots remain under the user's control.
+CHECKPOINT_COUNT = 2
+CHECKPOINT_BYTES = 128 * 1024 * 1024
+
+
+def _json(value):
+ from workspace_state_snapshot import serialized
+ return serialized(value)
+
+
+def _pack(value):
+ raw = _json(value).encode('utf-8')
+ compressed = zlib.compress(raw, 6)
+ return (b'z' + compressed if len(compressed) < len(raw) else b'j' + raw)
+
+
+def _unpack(blob):
+ if not isinstance(blob, bytes) or not blob or blob[:1] not in (b'z', b'j'):
+ raise ValueError('Invalid recovery row encoding')
+ try:
+ raw = zlib.decompress(blob[1:]) if blob[:1] == b'z' else blob[1:]
+ return json.loads(raw)
+ except (ValueError, zlib.error, UnicodeError) as error:
+ raise ValueError('Invalid recovery row data') from error
+
+
+def _digest(table, key, blob):
+ return hashlib.sha256(str(table).encode() + b'\0' + key + b'\0' + blob).digest()
+
+
+def _key_blob(values):
+ """Lossless typed keys, with ordinary UUID strings stored in sixteen bytes."""
+ result = bytearray()
+ for value in values:
+ try:
+ identity = uuid.UUID(value) if isinstance(value, str) else None
+ except ValueError:
+ identity = None
+ if identity is not None and str(identity) == value:
+ result.extend(b'u' + identity.bytes)
+ else:
+ data = _json(value).encode()
+ result.extend(b'j' + struct.pack('>I', len(data)) + data)
+ return bytes(result)
+
+
+def _key_values(blob):
+ result = []
+ offset = 0
+ try:
+ while offset < len(blob):
+ kind = blob[offset:offset+1]
+ offset += 1
+ if kind == b'u':
+ result.append(str(uuid.UUID(bytes=blob[offset:offset+16])))
+ offset += 16
+ elif kind == b'j':
+ size = struct.unpack('>I', blob[offset:offset+4])[0]
+ offset += 4
+ if offset + size > len(blob):
+ raise ValueError('Truncated recovery key')
+ result.append(json.loads(blob[offset:offset+size]))
+ offset += size
+ else:
+ raise ValueError('Unsupported recovery key')
+ except (ValueError, struct.error, UnicodeError) as error:
+ raise ValueError('Invalid recovery key') from error
+ return result
+
+
+def _accumulate(seal, digest, delta):
+ seal['count'] += delta
+ seal['xor'] = (int(seal['xor'], 16) ^ int.from_bytes(digest, 'big')).to_bytes(32, 'big').hex()
+
+
+def _remove_row(database, table, key, seal, table_seal):
+ old = database.execute('SELECT sha256 FROM recovery_rows WHERE table_name=? AND row_key=?', (table, key)).fetchone()
+ if old is not None:
+ _accumulate(seal, old[0], -1)
+ _accumulate(table_seal, old[0], -1)
+ database.execute('DELETE FROM recovery_rows WHERE table_name=? AND row_key=?', (table, key))
+
+
+def _directory_sync(path):
+ descriptor = os.open(path, os.O_RDONLY)
+ try:
+ os.fsync(descriptor)
+ finally:
+ os.close(descriptor)
+
+
+def _regular(path):
+ if any(parent.is_symlink() for parent in (path, *path.parents)):
+ raise ValueError('Recovery files cannot follow symbolic links')
+ if path.exists() and not path.is_file():
+ raise ValueError('Recovery state must be a regular file')
+
+
+def _folder(root):
+ folder = root / 'backups' / 'app-state'
+ for path in (root / 'backups', folder):
+ if path.is_symlink():
+ raise ValueError('Recovery directories cannot be symbolic links')
+ if not path.exists():
+ path.mkdir()
+ _directory_sync(path.parent)
+ return folder
+
+
+def pointer_path(root, pointer):
+ if (set(pointer) != {'format', 'version', 'project_uuid', 'store_uuid', 'store'}
+ or pointer.get('format') != FORMAT or pointer.get('version') != VERSION):
+ raise ValueError('Unsupported recovery pointer')
+ name = 'current-' + str(pointer['store_uuid']).replace('-', '') + '.sqlite'
+ try:
+ identity = str(uuid.UUID(pointer['store_uuid']))
+ project = str(uuid.UUID(pointer['project_uuid']))
+ except (ValueError, TypeError, AttributeError) as error:
+ raise ValueError('Invalid recovery identity') from error
+ if (identity != pointer['store_uuid'] or project != pointer['project_uuid']
+ or not CURRENT_PATTERN.fullmatch(name)
+ or pointer['store'] != 'backups/app-state/' + name):
+ raise ValueError('Invalid recovery pointer path')
+ path = Path(root) / pointer['store']
+ _regular(path)
+ return path
+
+
+@contextmanager
+def _connect(path, *, readonly=False):
+ _regular(path)
+ for suffix in ('-journal', '-wal', '-shm'):
+ _regular(Path(str(path) + suffix))
+ if readonly:
+ # A killed writer can leave a hot rollback journal after dirty pages
+ # reached the main file. SQLite must replay it before a read; mode=ro
+ # prevents that repair. Only mutable current mirrors open mode=rw (never
+ # create); SQLite's own locks arbitrate recovery with live writers. Sealed
+ # daily/legacy snapshots stay read-only. Never ignore/delete a journal.
+ mutable = CURRENT_PATTERN.fullmatch(path.name) is not None
+ database = sqlite3.connect(path.absolute().as_uri() + ('?mode=rw' if mutable else '?mode=ro'), uri=True)
+ try:
+ database.execute('PRAGMA schema_version').fetchone()
+ database.execute('PRAGMA query_only=ON')
+ except BaseException:
+ database.close()
+ raise
+ else:
+ database = sqlite3.connect(path, timeout=30)
+ database.execute('PRAGMA journal_mode=DELETE')
+ # EXTRA includes the directory sync after removing a rollback journal.
+ database.execute('PRAGMA synchronous=EXTRA')
+ database.execute('PRAGMA foreign_keys=ON')
+ try:
+ with database:
+ yield database
+ finally:
+ database.close()
+
+
+def _metadata(database, *, expected=None):
+ rows = database.execute('SELECT payload,sha256 FROM recovery_header').fetchall()
+ if len(rows) != 1:
+ raise ValueError('Expected one recovery header')
+ blob, digest = rows[0]
+ if hashlib.sha256(blob).digest() != digest:
+ raise ValueError('Recovery header checksum differs')
+ header = _unpack(blob)
+ from workspace_state_snapshot import TABLES
+ if (header.get('format') != FORMAT or header.get('version') != VERSION
+ or set(header.get('keys', {})) - set(TABLES)
+ or set(header.get('columns', {})) != set(header.get('keys', {}))
+ or set(header.get('state', {}).get('tables', {})) != set(TABLES)
+ or any(header['state']['tables'].values())):
+ raise ValueError('Unsupported recovery header')
+ table_seals = header.get('table_seals')
+ if table_seals is not None and (set(table_seals) != set(TABLES) or any(
+ not isinstance(value, dict) or set(value) != {'count', 'xor'}
+ or type(value['count']) is not int or value['count'] < 0
+ or not isinstance(value['xor'], str) or not re.fullmatch(r'[0-9a-f]{64}', value['xor'])
+ for value in table_seals.values())):
+ raise ValueError('Invalid recovery table content seals')
+ if expected is not None and (header['store_uuid'] != expected['store_uuid']
+ or header['state']['project']['project_uuid'] != expected['project_uuid']):
+ raise ValueError('Recovery pointer and database identity differ')
+ return header
+
+
+def inspect(root):
+ """Read the durable watermark without reading all canonical rows."""
+ root = Path(root).resolve()
+ target = root / 'app-state.json'
+ _regular(target)
+ if not target.exists():
+ return None
+ pointer = json.loads(target.read_text())
+ if pointer.get('version') == 1:
+ return None
+ path = pointer_path(root, pointer)
+ with _connect(path, readonly=True) as database:
+ header = _metadata(database, expected=pointer)
+ return {'pointer': pointer, 'path': path, 'header': header}
+
+
+def verified_table_content(root, tracker, tables=('shared_annotation', 'annotation_profile')):
+ """Prove content seals against the live authority, never a saved old token.
+
+ This is a checkpoint/startup operation. The caller also fences its derived
+ index and immutable metadata generation around checkpoint publication.
+ """
+ try:
+ before = tracker.token()
+ if before is None:
+ return None
+ current = inspect(root)
+ if current is None:
+ return None
+ header = current['header']
+ seals = header.get('table_seals')
+ if (not isinstance(seals, dict) or any(table not in seals for table in tables)
+ or before != header['watermark'] or tracker.token() != before
+ or before.get('project_uuid') != header['state']['project']['project_uuid']):
+ return None
+ return {'project_uuid': before['project_uuid'],
+ 'tables': {table: dict(seals[table]) for table in tables}}
+ except (OSError, ValueError, TypeError, KeyError, sqlite3.Error):
+ return None
+
+
+def _row_key(row, names):
+ if not names or any(name not in row or row[name] is None for name in names):
+ raise ValueError('Recovery row has an invalid primary key')
+ return _key_blob([row[name] for name in names if name != 'project_uuid'])
+
+
+def _write_rows(database, tables, keys, columns, project_uuid, seal, table_seals, *, replace):
+ from workspace_state_snapshot import TABLES
+ for table, rows in tables.items():
+ if rows and table not in keys:
+ raise ValueError('Recovery table has no primary-key contract')
+ seen = set() if replace else None
+ for row in rows:
+ if row.get('project_uuid') != project_uuid:
+ raise ValueError('Recovery contains a foreign project row')
+ key = _row_key(row, keys[table])
+ if seen is not None:
+ if key in seen:
+ raise ValueError('Duplicate primary key in recovery after-images')
+ seen.add(key)
+ if set(row) != set(columns[table]):
+ raise ValueError('Recovery row columns differ')
+ values = [row[name] for name in columns[table] if name not in keys[table] and name != 'project_uuid']
+ blob = _pack(values)
+ table_id = TABLES.index(table)
+ if replace:
+ _remove_row(database, table_id, key, seal, table_seals[table])
+ digest = _digest(table_id, key, blob)
+ database.execute('INSERT INTO recovery_rows VALUES (?,?,?,?)', (table_id, key, blob, digest))
+ _accumulate(seal, digest, 1)
+ _accumulate(table_seals[table], digest, 1)
+
+
+def _validate_keys(keys):
+ from workspace_state_snapshot import TABLES
+ if set(keys) - set(TABLES) or any(not isinstance(names, (list, tuple)) or not names
+ or len(set(names)) != len(names) or any(not isinstance(name, str) or not name for name in names)
+ for names in keys.values()):
+ raise ValueError('Invalid recovery primary-key contract')
+
+
+def write(root, *, state, keys, watermark, columns=None, changes=None, deleted=None, day=None):
+ """Commit a complete snapshot, or verified gap-covering after-images.
+
+ The caller holds .app-state-save.lock and proves SQL coverage. ``state`` has
+ the complete current file/source header; with changes it has empty tables.
+ Keys and watermark are published in the same SQLite transaction as rows.
+ """
+ root = Path(root).resolve()
+ _validate_keys(keys)
+ from workspace_state_snapshot import TABLES, atomic_write
+ if state.get('format') != FORMAT or state.get('version') != 1 or set(state.get('tables', {})) != set(TABLES):
+ raise ValueError('Invalid complete recovery state')
+ project_uuid = state['project']['project_uuid']
+ try:
+ if str(uuid.UUID(project_uuid)) != project_uuid:
+ raise ValueError('Recovery requires a canonical project UUID')
+ except (TypeError, AttributeError) as error:
+ raise ValueError('Recovery requires a canonical project UUID') from error
+ current = inspect(root)
+ if columns is None:
+ columns = current['header']['columns'] if current else {
+ table: list(rows[0]) if rows else list(keys[table])
+ for table, rows in state['tables'].items() if table in keys}
+ if (set(columns) != set(keys) or any(not isinstance(names, (list, tuple))
+ or len(set(names)) != len(names) or not set(keys[table]) <= set(names)
+ or 'project_uuid' not in names for table, names in columns.items())):
+ raise ValueError('Invalid recovery column contract')
+ if changes is not None and current is None:
+ raise ValueError('Incremental recovery requires a durable baseline')
+ if current is not None and current['pointer']['project_uuid'] != project_uuid:
+ raise ValueError('Recovery baseline belongs to a different project')
+ if changes is not None and not current['header'].get('table_seals'):
+ raise ValueError('Incremental recovery requires a content-sealed baseline')
+ if changes is not None and (keys != current['header']['keys'] or columns != current['header']['columns']):
+ raise ValueError('Incremental recovery key contract changed')
+ if changes is not None and any(state['tables'].values()):
+ raise ValueError('Incremental recovery header must not duplicate table rows')
+ folder = _folder(root)
+ if (changes is not None and not any(changes.values()) and not any((deleted or {}).values())
+ and state == current['header']['state'] and watermark == current['header']['watermark']):
+ daily = checkpoint(current['path'], folder, day=day)
+ return {'path': str(root / 'app-state.json'), 'bytes': current['path'].stat().st_size,
+ 'changed': False, 'daily_backup': str(daily), 'incremental': True,
+ 'recovery_storage': storage(root)}
+ store_uuid = current['pointer']['store_uuid'] if current else str(uuid.uuid4())
+ name = 'current-' + uuid.UUID(store_uuid).hex + '.sqlite'
+ final_path = folder / name
+ temporary = None
+ if current:
+ path = current['path']
+ else:
+ fd, temporary_name = tempfile.mkstemp(prefix='.recovery-', suffix='.sqlite', dir=folder)
+ os.close(fd)
+ temporary = path = Path(temporary_name)
+ header = {'format': FORMAT, 'version': VERSION, 'store_uuid': store_uuid,
+ 'keys': keys, 'columns': columns, 'watermark': watermark,
+ 'seal': dict(current['header']['seal']) if changes is not None else {'count': 0, 'xor': '0' * 64},
+ 'table_seals': {table: dict(current['header']['table_seals'][table]) if changes is not None
+ else {'count': 0, 'xor': '0' * 64} for table in TABLES},
+ 'state': {**state, 'tables': {table: [] for table in TABLES}}}
+ try:
+ with _connect(path) as database:
+ if not current:
+ database.execute('PRAGMA auto_vacuum=INCREMENTAL')
+ database.execute('CREATE TABLE recovery_header (payload BLOB NOT NULL,sha256 BLOB NOT NULL)')
+ database.execute('CREATE TABLE recovery_rows (table_name INTEGER NOT NULL,row_key BLOB NOT NULL,'
+ 'payload BLOB NOT NULL,sha256 BLOB NOT NULL,PRIMARY KEY(table_name,row_key)) WITHOUT ROWID')
+ database.commit()
+ database.execute('BEGIN IMMEDIATE')
+ if changes is None:
+ database.execute('DELETE FROM recovery_rows')
+ else:
+ if set(changes) - set(keys) or set(deleted or {}) - set(keys):
+ raise ValueError('Incremental recovery references an unsupported table')
+ for table, identities in (deleted or {}).items():
+ for identity in identities:
+ if len(identity) != len(keys[table]):
+ raise ValueError('Invalid recovery deletion key')
+ key = _key_blob([value for name, value in zip(keys[table], identity) if name != 'project_uuid'])
+ if 'project_uuid' in keys[table] and identity[keys[table].index('project_uuid')] != project_uuid:
+ raise ValueError('Recovery deletion belongs to a different project')
+ _remove_row(database, TABLES.index(table), key, header['seal'], header['table_seals'][table])
+ _write_rows(database, state['tables'] if changes is None else changes, keys, columns,
+ project_uuid, header['seal'], header['table_seals'], replace=changes is not None)
+ packed = _pack(header)
+ database.execute('DELETE FROM recovery_header')
+ database.execute('INSERT INTO recovery_header VALUES (?,?)', (packed, hashlib.sha256(packed).digest()))
+ database.commit()
+ # Reuse freed pages between edits; bound reclamation work per save.
+ database.execute('PRAGMA incremental_vacuum(64)')
+ # Explicit barrier also makes injected I/O failures observable before an
+ # acknowledgment. Initial publication additionally syncs its directory.
+ with path.open('rb') as handle:
+ os.fsync(handle.fileno())
+ if temporary is not None:
+ os.replace(temporary, final_path)
+ _directory_sync(folder)
+ pointer = dict(format=FORMAT, version=VERSION, project_uuid=project_uuid,
+ store_uuid=store_uuid, store='backups/app-state/' + name)
+ atomic_write(root / 'app-state.json', _json(pointer).encode())
+ daily = checkpoint(final_path, folder, day=day)
+ _clean_orphans(root, final_path, project_uuid)
+ return {'path': str(root / 'app-state.json'), 'bytes': final_path.stat().st_size,
+ 'changed': True, 'daily_backup': str(daily), 'incremental': changes is not None,
+ 'recovery_storage': storage(root)}
+ finally:
+ if temporary is not None:
+ temporary.unlink(missing_ok=True)
+ Path(str(temporary) + '-journal').unlink(missing_ok=True)
+ # atomic_write can publish the pointer and then raise at its final
+ # directory barrier. Preserve that referenced database. When the
+ # pointer definitely did not switch, a completed new file is only
+ # an unacknowledged staging copy and must not accumulate on retries.
+ try:
+ target = root / 'app-state.json'
+ published = json.loads(target.read_text()) if target.exists() else None
+ known_old = published is None or published.get('version') == 1
+ other_current = (published is not None and published.get('version') == 2
+ and pointer_path(root, published) != final_path)
+ if final_path.exists() and (known_old or other_current):
+ final_path.unlink()
+ _directory_sync(folder)
+ except (OSError, ValueError):
+ # An unreadable pointer gives no authority to delete evidence.
+ logging.getLogger(__name__).exception('Recovery staging cleanup could not verify the pointer')
+
+
+def _clean_orphans(root, current, project_uuid):
+ """Collect only verified unreferenced mirrors after a durable publication."""
+ removed = False
+ for path in current.parent.iterdir():
+ if path == current or not CURRENT_PATTERN.fullmatch(path.name) or path.is_symlink():
+ continue
+ try:
+ with _connect(path, readonly=True) as database:
+ header = _metadata(database)
+ expected = 'current-' + uuid.UUID(header['store_uuid']).hex + '.sqlite'
+ if path.name != expected or header['state']['project']['project_uuid'] != project_uuid:
+ continue
+ path.unlink()
+ removed = True
+ except (OSError, ValueError, sqlite3.Error):
+ logging.getLogger(__name__).warning('Preserving an unverified recovery file: %s', path)
+ if removed:
+ _directory_sync(current.parent)
+
+
+def checkpoint(current, folder, *, day=None):
+ date = dt.date.fromisoformat(day or dt.datetime.now(dt.timezone.utc).date().isoformat()).isoformat()
+ daily = folder / ('checkpoint-v2-' + date + '.sqlite')
+ _regular(daily)
+ if not daily.exists():
+ fd, name = tempfile.mkstemp(prefix='.checkpoint-', suffix='.sqlite', dir=folder)
+ os.close(fd)
+ temporary = Path(name)
+ try:
+ with _connect(current, readonly=True) as source, closing(sqlite3.connect(temporary)) as destination:
+ source.backup(destination)
+ destination.execute('VACUUM')
+ with temporary.open('rb') as handle:
+ os.fsync(handle.fileno())
+ os.chmod(temporary, 0o400)
+ try:
+ os.link(temporary, daily)
+ except FileExistsError:
+ pass
+ _directory_sync(folder)
+ finally:
+ temporary.unlink(missing_ok=True)
+ # Keep the newest checkpoint even if it alone exceeds the history budget:
+ # a storage budget cannot justify dropping the only previous recovery point.
+ retained = sorted((p for p in folder.iterdir() if CHECKPOINT_PATTERN.fullmatch(p.name)), reverse=True)
+ total = 0
+ removed = False
+ for number, path in enumerate(retained):
+ _regular(path)
+ size = path.stat().st_size
+ if number and (number >= CHECKPOINT_COUNT or total + size > CHECKPOINT_BYTES):
+ path.unlink()
+ removed = True
+ else:
+ total += size
+ if removed:
+ _directory_sync(folder)
+ return daily if daily.exists() else retained[0]
+
+
+def storage(root):
+ """Separate current state, managed history, and preserved older backups."""
+ folder = Path(root) / 'backups' / 'app-state'
+ counts = {'current_bytes': 0, 'checkpoint_bytes': 0, 'other_backup_bytes': 0,
+ 'checkpoint_count': 0, 'checkpoint_budget_bytes': CHECKPOINT_BYTES}
+ for path in folder.iterdir():
+ if path.is_symlink() or not path.is_file():
+ continue
+ size = path.stat().st_size
+ if CURRENT_PATTERN.fullmatch(path.name):
+ counts['current_bytes'] += size
+ elif CHECKPOINT_PATTERN.fullmatch(path.name):
+ counts['checkpoint_bytes'] += size
+ counts['checkpoint_count'] += 1
+ else:
+ counts['other_backup_bytes'] += size
+ counts['checkpoint_budget_exceeded'] = counts['checkpoint_bytes'] > CHECKPOINT_BYTES
+ return counts
+
+
+def load_database(path, *, expected=None):
+ """Validate the entire current-state mirror before exposing restore input."""
+ with _connect(Path(path), readonly=True) as database:
+ database.execute('BEGIN')
+ if database.execute('PRAGMA quick_check').fetchall() != [('ok',)]:
+ raise ValueError('Recovery database integrity check failed')
+ header = _metadata(database, expected=expected)
+ state = header['state']
+ from workspace_state_snapshot import TABLES
+ seal = {'count': 0, 'xor': '0' * 64}
+ table_seals = {table: {'count': 0, 'xor': '0' * 64} for table in TABLES}
+ for table_id, key, blob, digest in database.execute(
+ 'SELECT table_name,row_key,payload,sha256 FROM recovery_rows ORDER BY table_name,row_key'):
+ if type(table_id) is not int or not 0 <= table_id < len(TABLES):
+ raise ValueError('Recovery contains an unknown table')
+ table = TABLES[table_id]
+ if table not in header['keys'] or _digest(table_id, key, blob) != digest:
+ raise ValueError('Recovery row checksum or table differs')
+ _accumulate(seal, digest, 1)
+ _accumulate(table_seals[table], digest, 1)
+ key_names = [name for name in header['keys'][table] if name != 'project_uuid']
+ values = _key_values(key)
+ nonkeys = [name for name in header['columns'][table] if name not in header['keys'][table] and name != 'project_uuid']
+ data = _unpack(blob)
+ if len(values) != len(key_names) or not isinstance(data, list) or len(data) != len(nonkeys):
+ raise ValueError('Recovery row columns differ')
+ row = dict(zip(key_names, values))
+ row.update(zip(nonkeys, data))
+ row['project_uuid'] = state['project']['project_uuid']
+ if (_row_key(row, header['keys'][table]) != key
+ or row.get('project_uuid') != state['project']['project_uuid']):
+ raise ValueError('Recovery row identity differs')
+ state['tables'][table].append(row)
+ if header.get('table_seals') is not None and table_seals != header['table_seals']:
+ raise ValueError('Recovery table content seal differs')
+ if seal != header['seal']:
+ raise ValueError('Recovery row count or content seal differs')
+ return state
diff --git a/python/workspace_service.py b/python/workspace_service.py
index ba4b8ac..800768e 100644
--- a/python/workspace_service.py
+++ b/python/workspace_service.py
@@ -10,10 +10,11 @@
import datetime as dt
import hashlib
import json
+import sys
import time
from pathlib import Path
import uuid
-from collections import ChainMap
+from collections.abc import Mapping
from recording_workspace import (connect, digest, epochs, evaluate_protocol_file,
validate_protocol_definition, workspace_tables)
@@ -24,6 +25,27 @@
TREE_CACHE_VALUE_BUDGET = 2_000_000
TREE_CACHE_MAX_SCOPES = 8
+EPOCH_PAGE_CACHE_BYTES = 64 * 1024 * 1024
+
+
+class _SourceDetails(Mapping):
+ """Dispatch lazy details through the already validated owning source.
+
+ Membership iteration never opens a source or decompresses details. Looking
+ up an epoch touches exactly its own source mapping, independent of the
+ number of recordings in a project.
+ """
+ def __init__(self, rows, sources):
+ self.rows, self.sources = rows, sources
+
+ def __len__(self):
+ return len(self.rows)
+
+ def __iter__(self):
+ return iter(self.rows)
+
+ def __getitem__(self, identity):
+ return self.sources[self.rows[identity]['source_sha256']][identity]
def _date(value):
@@ -120,14 +142,17 @@ def read_response_window(path, signature, row, stream, start=0, count=20000):
"""
import h5py
import numpy as np
+ from recording_workspace import resolve_sealed_h5_path
path = Path(path)
with h5py.File(path, 'r') as h5:
- obj = h5[stream['h5_path']]
+ obj = resolve_sealed_h5_path(h5, stream['h5_path'])
if _uuid(obj.attrs['uuid']) != stream['uuid'] or _uuid(obj.parent.parent.attrs['uuid']) != row['epoch_uuid']:
raise ValueError('Trace source identity changed')
if float(obj.attrs['sampleRate']) != stream['sample_rate']:
raise ValueError('Trace source sample rate changed')
- data = obj['data']
+ data = resolve_sealed_h5_path(h5, stream['h5_path'].rstrip('/') + '/data')
+ if not isinstance(data, h5py.Dataset) or not data.dtype.names or not {'quantity', 'units'} <= set(data.dtype.names):
+ raise ValueError('Unsupported response representation')
if len(data) != stream['sample_count']:
raise ValueError('Trace sample count changed')
begin, end = bounded_window(start, count, len(data))
@@ -158,14 +183,18 @@ def __init__(self, project_dir, curation_provider=None):
raise ValueError('Project transfer restore is incomplete; finish recovery before opening it')
config = json.loads((self.project_dir / 'catalog.json').read_text())
if config.get('connection', {}).get('credential_provider', {}).get('kind') == 'native-project':
- from workspace_portability import rebase_project_paths
+ from workspace_portability import rebase_project_paths, register_empty_project
+ register_empty_project(self.project_dir)
rebase_project_paths(self.project_dir)
self.curation_provider = curation_provider
self._loaded = False
self._source_signatures = {}
from workspace_projection_cache import ProjectionCache
+ import workspace_projection_cache, workspace_metadata_objects
self._projection_store = ProjectionCache(self.project_dir / 'cache' / 'source-projections')
- self._projection_contract = digest(Path(__file__))
+ import recording_workspace
+ self._projection_contract = checksum([digest(Path(module.__file__)) for module in
+ (recording_workspace, workspace_projection_cache, workspace_metadata_objects)] + [digest(Path(__file__))])
self.refresh()
def set_curation_provider(self, provider):
@@ -263,7 +292,14 @@ def _read_source_metadata(self, source, document, path, signature):
rows, details, cells = {}, {}, {}
source_epoch_count = 0
source_cells = set()
+ from recording_workspace import validate_source_identity, validate_manifest_cell_count
with h5py.File(path, 'r') as h5:
+ validate_source_identity(h5, document)
+ for animal in document['animals']:
+ for preparation in animal['preparations']:
+ for cell in preparation['cells']:
+ cells[cell['uuid']] = {'cell_uuid': cell['uuid'], 'label': cell['label'],
+ 'cell_type': cell.get('type'), 'date': _date(cell['start_time']), 'start_time': cell['start_time']}
for cell, group, block, epoch in epochs(document):
identity = _uuid(epoch['uuid'])
if identity in rows:
@@ -309,7 +345,8 @@ def _read_source_metadata(self, source, document, path, signature):
'block': _metadata(block, {'epochs'}), 'epoch': epoch}}
cells[cell['uuid']] = {'cell_uuid': cell['uuid'], 'label': cell['label'],
'cell_type': cell.get('type'), 'date': _date(cell['start_time']), 'start_time': cell['start_time']}
- if source_epoch_count != manifest['counts']['epochs'] or len(source_cells) != manifest['counts']['cells']:
+ validate_manifest_cell_count(manifest, cells, source_cells)
+ if source_epoch_count != manifest['counts']['epochs']:
raise ValueError('Source count differs from validated manifest')
after = path.stat()
if signature != (str(path), after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns, after.st_ctime_ns):
@@ -373,7 +410,7 @@ def _refresh_verified_sources(self, metrics):
_, Source, Event, _ = workspace_tables(dj)
source_records = (Source & {'project_uuid': project['project_uuid']}).to_dicts()
rows, cells, sources, manifests, fingerprints = {}, {}, [], {}, {}
- detail_maps = []
+ detail_maps = {}
old_cache = getattr(self, '_source_metadata_cache', {})
projection_store = getattr(self, '_projection_store', None)
pending_projections = []
@@ -406,15 +443,18 @@ def _refresh_verified_sources(self, metrics):
prepared.append((source, metadata_path, key))
final_checks.extend([(metadata_path, metadata_signature), (source_path, source_signature)])
metrics['timing_ms']['metadata_check'] += (time.perf_counter() - check_started) * 1000
+ # Prepared source projections remain leased until this refresh either
+ # publishes its complete generation or releases them after failure.
+ projection_leases=projection_store.hold(entry['key'] for entry in next_cache.values()) if projection_store else None
disk_index, previous_index, location, generation = None, getattr(self, 'disk_index', None), None, None
if projection_store:
from workspace_disk_index import DiskMetadataIndex
- import workspace_disk_index, workspace_tree, workspace_predicates
+ import workspace_disk_index, workspace_tree, workspace_predicates, workspace_metadata_objects
import sqlite3
generation = checksum({'project_uuid':project['project_uuid'],
'sources':{identity:entry['key'] for identity,entry in next_cache.items()},
'index_contract':[digest(Path(module.__file__)) for module in
- (workspace_disk_index,workspace_tree,workspace_predicates)]})
+ (workspace_disk_index,workspace_tree,workspace_predicates,workspace_metadata_objects)]})
index_started = time.perf_counter()
location = self.project_dir / 'cache' / 'metadata' / (generation + '.sqlite')
if previous_index is not None and previous_index.generation == generation:
@@ -463,15 +503,15 @@ def _refresh_verified_sources(self, metrics):
raise ValueError('Duplicate epoch UUID across source recordings')
snapshot = projection if projection_store else copy.deepcopy(projection)
rows.update(snapshot['rows'])
- # ChainMap iteration reads identities only. Do not details.update()
- # a lazy mapping: that would decompress every epoch eagerly.
- detail_maps.append(snapshot['details'])
+ # Retain lazy details and dispatch by the independently validated
+ # source owner, without probing every preceding source mapping.
+ detail_maps[identity] = snapshot['details']
cells.update(snapshot['cells'])
fingerprints.update(snapshot['fingerprints'])
sources.append(snapshot['source'])
manifests[identity] = copy.deepcopy(manifest)
next_cache[identity] = {'key':key,'disk':True} if projection_store else {'key':key,'projection':projection}
- details = ChainMap(*detail_maps)
+ details = _SourceDetails(rows, detail_maps)
number_block_epochs(rows)
query_started = time.perf_counter()
protocols = {}
@@ -541,6 +581,13 @@ def _refresh_verified_sources(self, metrics):
elif registered_cache is not None:
self._registered_tree_cache = ((id(rows), len(rows), id(self.details), len(self.details)), registered_cache[1])
self._loaded = True
+ if projection_store and disk_index is not None:
+ try:
+ metrics['cache_cleanup']={'metadata':disk_index.publish(),
+ 'projections':projection_store.publish(entry['key'] for entry in next_cache.values())}
+ except (OSError,ValueError) as error:
+ metrics.setdefault('cache_warnings',[]).append('Derived cache cleanup was deferred: '+str(error))
+ if projection_leases is not None:projection_leases.close()
return {'sources': len(sources), 'epochs': len(rows), 'protocols': len(protocols)}
def _ready(self):
@@ -567,8 +614,10 @@ def protocol_file(self, protocol_uuid):
self._ready()
return self.protocols[_uuid(protocol_uuid)]['file']
- def set_binding_provider(self, provider):
+ def set_binding_provider(self, provider, *, header_provider=None):
self.binding_provider = provider
+ self.binding_header_provider = header_provider
+ self._epoch_page_cache = None
def binding(self, protocol_uuid):
provider = getattr(self, 'binding_provider', None)
@@ -619,6 +668,11 @@ def _filter_rows(self, rows, filters):
rows = [row for row in rows if all(row.get(key) == value for key, value in ordinary.items())]
if any(key in filters for key in ('tag', 'tagged', 'tag_predicate')):
shared = getattr(self, 'shared_annotations', None)
+ indexed=shared.filter_epoch_ids(rows,filters) if hasattr(shared,'filter_epoch_ids') else None
+ if indexed is not None:
+ selected=set(indexed[0])
+ return sorted((row for row in rows if row['epoch_uuid'] in selected),
+ key=lambda row:(row['date'],row['start_time'],row['epoch_uuid']))
annotations = shared.for_epochs(rows) if shared else {}
from workspace_predicates import matches
predicate = validate_tag_filter_predicate(filters['tag_predicate']) if 'tag_predicate' in filters else None
@@ -691,19 +745,111 @@ def protocol(self, protocol_uuid, filters=None):
'groups': sorted({r['group_label'] for r in all_rows}, key=str),
'filters': validate_filters(filters)}
- def epoch_page(self, protocol_uuid, filters=None, offset=0, limit=100, anchor_uuid=None):
+ def epoch_page(self, protocol_uuid, filters=None, offset=0, limit=100, anchor_uuid=None, *, include_curation=True, include_cells=False):
if isinstance(offset, bool) or isinstance(limit, bool) or not isinstance(offset, int) or not isinstance(limit, int) or offset < 0 or not 1 <= limit <= 250:
raise ValueError('Epoch page requires nonnegative offset and limit 1–250')
- rows = self.filtered_rows(protocol_uuid, filters)
+ if type(include_curation) is not bool or type(include_cells) is not bool:
+ raise ValueError('Epoch page projection options must be boolean')
+ identities,shared_generation = self._epoch_page_identities(protocol_uuid, filters)
anchor_index = None
if anchor_uuid is not None:
identity = _uuid(anchor_uuid)
- anchor_index = next((index for index, row in enumerate(rows) if row['epoch_uuid'] == identity), None)
- if anchor_index is None:
+ try:
+ anchor_index = identities.index(identity)
+ except ValueError:
raise ValueError('Focused epoch is outside this protocol and filter scope')
offset = anchor_index // limit * limit
- return {'total': len(rows), 'offset': offset, 'limit': limit, 'epochs': rows[offset:offset + limit],
+ shown = identities[offset:offset + limit]
+ provider = self.curation_provider
+ curation = provider(_uuid(protocol_uuid), {key:self._fingerprints[key] for key in shown}) if include_curation and provider and shown else {}
+ rows = [self._decorate(self.rows[key], curation) for key in shown]
+ result={'total': len(identities), 'offset': offset, 'limit': limit, 'epochs': rows,
+ **({'_shared_annotation_generation':shared_generation} if shared_generation is not None else {}),
**({'anchor_index': anchor_index, 'anchor_uuid': identity} if anchor_index is not None else {})}
+ if include_cells:
+ counts={}
+ for identity in identities:
+ cell=self.rows[identity]['cell_uuid']
+ counts[cell]=counts.get(cell,0)+1
+ result['cells']=sorted(({**self.cells[cell],'epochs':count} for cell,count in counts.items()),
+ key=lambda row:(row['cell_type'] or '',row['date'],row['label']))
+ return result
+
+ def _epoch_page_identities(self, protocol_uuid, filters=None):
+ """Order raw membership once; read current curation only after slicing.
+
+ Frozen binding headers are safe cache keys because saved recipes are
+ immutable. Unbound/provider-only memberships are compared explicitly to
+ detect in-place changes. Shared-tag membership is always read live.
+ """
+ self._ready()
+ protocol_uuid = _uuid(protocol_uuid)
+ protocol = self.protocols[protocol_uuid]
+ filters = validate_filters(filters)
+ tag_filters = {key:value for key,value in filters.items() if key in {'tag','tagged','tag_predicate'}}
+ ordinary = {key:value for key,value in filters.items() if key not in tag_filters}
+ header_provider = getattr(self, 'binding_header_provider', None)
+ binding = None if header_provider else self.binding(protocol_uuid)
+ header = header_provider(protocol_uuid) if header_provider else binding
+ if header is None and protocol['definition'].get('initial_revision_uuid'):
+ raise ValueError('Pinned protocol creation was interrupted. Retry creating it from the same saved selection and name.')
+ members = (binding['recipe']['epochs'] if binding else protocol['result']['epochs'])
+ signature = None if header_provider and header is not None else tuple(member['uuid'] for member in members)
+ key = (protocol_uuid, header and header['version'], header and header.get('revision_uuid'), tuple(sorted(ordinary.items())))
+ from workspace_disk_index import DiskMetadataIndex
+ index = getattr(self, 'disk_index', None)
+ cache = None
+ overhead = sys.getsizeof(self._fingerprints) + 16 * 1024
+ if isinstance(index, DiskMetadataIndex):
+ index._check()
+ generation = (id(index),index.generation,id(self.rows),id(self.protocols),id(self._fingerprints))
+ previous = getattr(self, '_epoch_page_cache', None)
+ if overhead < EPOCH_PAGE_CACHE_BYTES:
+ if previous is None or previous[0] != generation or previous[2] != self._fingerprints:
+ previous = self._epoch_page_cache = (generation,{},dict(self._fingerprints))
+ cache = previous[1]
+ else:
+ self._epoch_page_cache = None
+ entry = cache.get(key) if cache is not None else None
+ if entry is not None and (signature is None or signature == entry[0]):
+ cache.pop(key)
+ cache[key] = entry
+ identities = entry[1]
+ else:
+ if cache is not None:
+ cache.pop(key,None)
+ if header_provider and header is not None:
+ # Re-read the binding together with its recipe on a cold scope.
+ # A concurrent bind may have changed since the header lookup.
+ binding = self.binding(protocol_uuid)
+ if binding is None and protocol['definition'].get('initial_revision_uuid'):
+ raise ValueError('Pinned protocol creation was interrupted. Retry creating it from the same saved selection and name.')
+ members = binding['recipe']['epochs'] if binding else protocol['result']['epochs']
+ key = (protocol_uuid, binding and binding['version'], binding and binding.get('revision_uuid'), tuple(sorted(ordinary.items())))
+ signature = tuple(member['uuid'] for member in members)
+ if binding is not None and set(signature) - self.rows.keys():
+ raise ValueError('Bound dataset contains unavailable epochs; explicit reconciliation is required')
+ rows = self._filter_rows([self.rows[identity] for identity in signature], ordinary)
+ identities = tuple(row['epoch_uuid'] for row in rows)
+ if cache is not None:
+ # Only UUID tuples are retained. Conservatively count their
+ # strings twice even if borrowed/shared, keeping admission cheap.
+ size = sum(sys.getsizeof(values) + sum(sys.getsizeof(value) for value in values)
+ for values in (signature,identities))
+ if size + overhead <= EPOCH_PAGE_CACHE_BYTES:
+ cache.pop(key,None)
+ cache[key] = (signature,identities,size)
+ while len(cache) > 8 or overhead + sum(item[2] for item in cache.values()) > EPOCH_PAGE_CACHE_BYTES:
+ cache.pop(next(iter(cache)))
+ if tag_filters:
+ rows = [self.rows[identity] for identity in identities]
+ shared = getattr(self, 'shared_annotations', None)
+ indexed = shared.filter_epoch_ids(rows,tag_filters) if hasattr(shared,'filter_epoch_ids') else None
+ if indexed is not None:
+ return indexed
+ rows = self._filter_rows(rows, tag_filters)
+ identities = tuple(row['epoch_uuid'] for row in rows)
+ return identities,None
def _remember_tree_catalog(self, key, result):
cache = self._tree_catalog_cache
@@ -732,7 +878,9 @@ def _tree_fields(self, protocol_uuid, filters=None):
scope = self.source_scope() if protocol_uuid is None else None
scope_revision = scope['revision'] if scope else None
shared = getattr(self, 'shared_annotations', None)
- tag_revision = shared.snapshot()['revision'] if shared and any(key in filters for key in ('tag', 'tagged', 'tag_predicate')) else None
+ uses_tags=shared and any(key in filters for key in ('tag', 'tagged', 'tag_predicate'))
+ tag_generation=shared.generation_token() if uses_tags and hasattr(shared,'generation_token') else None
+ tag_revision=tag_generation if tag_generation is not None else shared.snapshot()['revision'] if uses_tags else None
key = (protocol_uuid, binding['version'] if binding else 0, scope_revision, tuple(sorted(filters.items())), tag_revision)
if key in cache:
result = cache.pop(key)
@@ -750,6 +898,9 @@ def _tree_fields(self, protocol_uuid, filters=None):
result = index.catalog(ids=ids, known_fields=known), index.values(ids=ids)
else:
result = tree_catalog(rows, self.details, known, sources=self.sources)
+ if tag_generation is not None and shared.generation_token()!=tag_generation:
+ from workspace_shared_tag_index import SharedTagsChanged
+ raise SharedTagsChanged('Shared tags changed while loading this tree. Refresh and try again.')
return self._remember_tree_catalog(key, result)
def tree_fields(self, protocol_uuid, filters=None, *, splits=None):
@@ -759,6 +910,23 @@ def tree_fields(self, protocol_uuid, filters=None, *, splits=None):
catalog, _ = materialize_combinations(catalog, values, order)
return catalog
+ def metadata_fields(self):
+ """Registered field definitions for inspecting one epoch's metadata.
+
+ The sealed index stores its complete catalog at publication, so this
+ projection never materializes filtered epoch values or saved decisions.
+ """
+ self._ready()
+ index=getattr(self,'disk_index',None)
+ if index is not None:
+ index._check()
+ catalog=index.catalog()
+ else:
+ catalog=self._registered_tree_fields()[0]
+ names=('id','label','category','path','components','grouping_role','grouping_priority','grouping_hint')
+ return {'fields':[{key:copy.deepcopy(field[key]) for key in names if key in field}
+ for field in catalog['fields']]}
+
def _tree_rows(self, protocol_uuid, filters=None):
if protocol_uuid is not None:
return self.filtered_rows(protocol_uuid, filters)
@@ -811,12 +979,29 @@ def predicate_fields(self):
cached = self._predicate_catalog_cache = (scope['revision'], data)
return self._with_annotation_fields({**cached[1], 'source_scope': scope, 'total_catalog': len(self.rows)})
- def explore_preview(self, predicate, splits='date,protocol,cell', *, include_tree=True):
+ def explore_preview(self, predicate, splits='date,protocol,cell', *, include_tree=True, include_catalog_summary=True):
+ if type(include_catalog_summary) is not bool or (include_tree and not include_catalog_summary):
+ raise ValueError('A rendered tree requires catalog summaries; include_catalog_summary must be boolean')
scope = self.source_scope()
eligible = [row['epoch_uuid'] for row in self._tree_rows(None)]
validated, identities, annotation_scope = self.match_predicate(predicate, eligible)
index = getattr(self, 'disk_index', None)
- if index:
+ if not include_catalog_summary:
+ # Search-result navigation needs field identities, exact frozen
+ # membership and a revision, not every field's scoped statistics.
+ # Never label global counts or suggestions as matched-scope data.
+ registered = index.catalog() if index else self._registered_tree_fields()[0]
+ fields = [{key: field[key] for key in ('id', 'label', 'category', 'path', 'components') if key in field}
+ for field in registered['fields']]
+ definitions = {field['id']: field for field in fields}
+ order = parse_splits(splits, definitions)
+ from workspace_tree import joint_definition
+ fields.extend(joint_definition(field, definitions) for field in order if field not in definitions)
+ rows = [self.rows[key] for key in identities]
+ scoped_catalog = {'fields': fields, 'total': len(rows), 'summary_available': False}
+ scoped_values = None
+ total_source = len(eligible)
+ elif index:
rows = [self.rows[key] for key in identities]
scoped_catalog = index.catalog(ids=identities, known_fields=index.catalog()['fields'])
scoped_values = index.values(ids=identities)
@@ -827,11 +1012,13 @@ def explore_preview(self, predicate, splits='date,protocol,cell', *, include_tre
scoped_catalog, scoped_values = tree_catalog(rows, self.details, catalog['fields'], sources=self.sources)
total_source = len(values)
order = parse_splits(splits, {field['id'] for field in scoped_catalog['fields']})
- scoped_catalog, scoped_values = materialize_combinations(scoped_catalog, scoped_values, order)
+ if include_catalog_summary:
+ scoped_catalog, scoped_values = materialize_combinations(scoped_catalog, scoped_values, order)
tree = self._render_tree(rows, scoped_catalog, scoped_values, splits) if include_tree else {
'count': len(rows), 'split_order': order, 'matlab_command': matlab_tree_command(order)}
from workspace_tree_pages import selection_revision
return {'predicate': validated, 'splits': splits, 'tree': tree, 'catalog': scoped_catalog,
+ **({'catalog_summary': False} if not include_catalog_summary else {}),
'tree_revision': selection_revision(self, None, validated, {}, order, rows, annotation_scope=annotation_scope),
'total_source': total_source, 'total_catalog': len(self.rows), 'matched_count': len(rows),
'source_scope': scope, 'source_revisions': scope['active_source_revisions'],
@@ -945,8 +1132,9 @@ def events(self, limit=100):
self._ready()
if not isinstance(limit, int) or not 1 <= limit <= 1000:
raise ValueError('Event limit must be 1–1000')
- rows = (self.Event & {'project_uuid': self.project['project_uuid']}).fetch(
- as_dict=True, order_by='occurred_at DESC', limit=limit)
+ rows = (self.Event & {'project_uuid': self.project['project_uuid']}).proj(
+ 'project_uuid', 'occurred_at', 'actor', 'action').fetch(
+ as_dict=True, order_by='occurred_at DESC, event_uuid DESC', limit=limit)
return [{**row, 'occurred_at': row['occurred_at'].replace(tzinfo=dt.timezone.utc).isoformat()} for row in rows]
def event_page(self, limit=50, offset=0, action=None):
@@ -958,11 +1146,17 @@ def event_page(self, limit=50, offset=0, action=None):
if not isinstance(action, str) or not action or len(action) > 63:
raise ValueError('Invalid action filter')
restriction['action'] = action
- rows = (self.Event & restriction).fetch(as_dict=True,
+ relation = self.Event & restriction
+ summaries = relation.proj('project_uuid', 'occurred_at', 'actor', 'action').fetch(as_dict=True,
order_by='occurred_at DESC, event_uuid DESC', limit=limit + 1, offset=offset)
+ page = summaries[:limit]
+ details = ((relation & [{'event_uuid': row['event_uuid']} for row in page]).fetch(as_dict=True)
+ if page else [])
+ by_identity = {row['event_uuid']: row for row in details}
+ rows = [by_identity[row['event_uuid']] for row in page]
return {'events': [{**row, 'occurred_at': row['occurred_at'].replace(
- tzinfo=dt.timezone.utc).isoformat()} for row in rows[:limit]],
- 'offset': offset, 'limit': limit, 'has_more': len(rows) > limit}
+ tzinfo=dt.timezone.utc).isoformat()} for row in rows],
+ 'offset': offset, 'limit': limit, 'has_more': len(summaries) > limit}
def event_detail(self, event_uuid):
self._ready()
diff --git a/python/workspace_shared_tag_index.py b/python/workspace_shared_tag_index.py
new file mode 100644
index 0000000..b4f4e0c
--- /dev/null
+++ b/python/workspace_shared_tag_index.py
@@ -0,0 +1,577 @@
+"""Disposable, incremental shared-tag membership index with exact text semantics.
+
+Canonical annotations remain in SQL. This index stores no waveform or acquisition
+metadata and never substitutes its records for the visible provenance response.
+A transactional source token fences every rebuild/delta; a caller must also fence
+that token across membership and its later visible annotation/revision reads.
+"""
+from __future__ import annotations
+
+import copy
+import json
+import sqlite3
+import tempfile
+import sys
+import zlib
+import hashlib
+import threading
+from functools import wraps
+from collections import OrderedDict
+from pathlib import Path
+
+
+FIELDS={'annotations/cell/tags':('cell',),
+ 'annotations/epoch/tags':('epoch',),
+ 'annotations/effective/tags':('cell','epoch')}
+SCOPE_CACHE_ROWS=200000
+MATCH_CACHE_BYTES=64*1024*1024
+MATCH_DELTA_MAX_RECORDS=1000
+CATALOG_FIELDS=(*FIELDS,'annotations/authors','annotations/author_uuids')
+
+
+class SharedTagsChanged(ValueError):
+ pass
+
+
+def _serialized(operation):
+ """One owner at a time for SQLite, its temp scopes and derived caches."""
+ @wraps(operation)
+ def guarded(self, *args, **kwargs):
+ with self._lock:
+ return operation(self, *args, **kwargs)
+ return guarded
+
+
+class SharedTagIndex:
+ """One derived on-disk index with bounded browse and aggregate scopes.
+
+ generation() returns a verified immutable authority token, or None when its
+ contract is unavailable. changes(old) returns (target keys, current token),
+ or None when a full rebuild is required. records(keys) yields canonical rows;
+ keys=None requests a bounded streaming full scan. validate(row) returns its
+ exact validated tag strings and must check target/profile/author/revision.
+ """
+ def __init__(self,*,generation,changes,records,validate):
+ self._lock=threading.RLock()
+ self.generation,self.changes,self.records,self.validate=generation,changes,records,validate
+ self.token=None;self.folder=None;self.connection=None
+ self.scopes={kind:None for kind in ('filter','summary','catalog')};self.scope_revisions=dict.fromkeys(self.scopes,0)
+ self.summary_cache=None;self.catalog_cache={}
+ self.shared_vocabulary=None
+ self.tag_ids=OrderedDict()
+ self.match_cache=OrderedDict();self.match_cache_bytes=0
+ self.stats={'full_builds':0,'delta_updates':0,'rows_loaded':0,'rows_indexed':0}
+
+ def _open(self):
+ if self.connection is not None:return self.connection
+ self.folder=tempfile.TemporaryDirectory(prefix='rieke-shared-tag-index-')
+ # Waitress hands successive requests to different threads. Every public
+ # operation below holds _lock for its full cursor/transaction lifetime;
+ # this flag permits ownership transfer, never concurrent SQLite access.
+ self.connection=sqlite3.connect(Path(self.folder.name)/'membership.sqlite',check_same_thread=False)
+ self.connection.create_collation('TAG_ORDER',lambda left,right:
+ ((left.casefold(),left)>(right.casefold(),right))-((left.casefold(),left)<(right.casefold(),right)))
+ self.connection.create_function('pack_array',1,lambda value:zlib.compress(value.encode('utf-8'),1),deterministic=True)
+ self.connection.create_function('array_prefix',1,lambda value:zlib.decompress(value).decode('utf-8')[:160],deterministic=True)
+ self.connection.create_function('array_hash',1,lambda value:hashlib.sha256(value).digest(),deterministic=True)
+ # This private working database is disposable after a crash. Keep
+ # transactional rollback, but reserve disk durability barriers for the
+ # canonical SQL commit and explicitly sealed reusable checkpoints.
+ self.connection.executescript('''
+ PRAGMA cache_size=-8192;
+ PRAGMA temp_store=FILE;
+ PRAGMA auto_vacuum=INCREMENTAL;
+ PRAGMA journal_mode=DELETE;
+ PRAGMA synchronous=OFF;
+ PRAGMA foreign_keys=ON;
+ CREATE TABLE records(record_id INTEGER PRIMARY KEY,kind TEXT COLLATE BINARY,target TEXT COLLATE BINARY,
+ profile TEXT COLLATE BINARY,author TEXT,revision INTEGER,
+ UNIQUE(kind,target,profile));
+ CREATE TABLE tag_values(tag_id INTEGER PRIMARY KEY,tag TEXT COLLATE BINARY UNIQUE);
+ CREATE TABLE tag_memberships(record_id INTEGER REFERENCES records(record_id) ON DELETE CASCADE,
+ tag_id INTEGER REFERENCES tag_values(tag_id),PRIMARY KEY(record_id,tag_id)) WITHOUT ROWID;
+ CREATE INDEX memberships_by_tag ON tag_memberships(tag_id,record_id);
+ CREATE VIEW tags AS SELECT r.kind,r.target,r.profile,v.tag FROM tag_memberships m
+ JOIN records r ON r.record_id=m.record_id JOIN tag_values v ON v.tag_id=m.tag_id;
+ CREATE TEMP TABLE changed_tag_values(tag_id INTEGER PRIMARY KEY);
+ CREATE TABLE filter_scope(position INTEGER PRIMARY KEY,epoch_uuid TEXT COLLATE BINARY UNIQUE,cell_uuid TEXT COLLATE BINARY);
+ CREATE INDEX filter_scope_cell ON filter_scope(cell_uuid);
+ CREATE TABLE summary_scope(position INTEGER PRIMARY KEY,epoch_uuid TEXT COLLATE BINARY UNIQUE,cell_uuid TEXT COLLATE BINARY);
+ CREATE INDEX summary_scope_cell ON summary_scope(cell_uuid);
+ CREATE TABLE catalog_scope(position INTEGER PRIMARY KEY,epoch_uuid TEXT COLLATE BINARY UNIQUE,cell_uuid TEXT COLLATE BINARY);
+ CREATE INDEX catalog_scope_cell ON catalog_scope(cell_uuid);
+ CREATE TEMP TABLE predicate_values(literal_id INTEGER,tag TEXT COLLATE BINARY,
+ PRIMARY KEY(literal_id,tag)) WITHOUT ROWID;
+ CREATE TEMP TABLE affected_positions(position INTEGER PRIMARY KEY);
+ CREATE TEMP TABLE catalog_values(field INTEGER,position INTEGER,bucket_id INTEGER,
+ PRIMARY KEY(field,position)) WITHOUT ROWID;
+ CREATE INDEX catalog_values_by_bucket ON catalog_values(bucket_id,position);
+ CREATE TEMP TABLE catalog_examples(example_id INTEGER PRIMARY KEY,prefix TEXT COLLATE BINARY UNIQUE);
+ CREATE TEMP TABLE changed_examples(example_id INTEGER PRIMARY KEY);
+ CREATE TEMP TABLE catalog_buckets(bucket_id INTEGER PRIMARY KEY,field INTEGER,value BLOB,value_hash BLOB,
+ amount INTEGER,first_position INTEGER,example_id INTEGER);
+ CREATE INDEX catalog_bucket_lookup ON catalog_buckets(field,value_hash);
+ CREATE INDEX catalog_choices ON catalog_buckets(field,first_position);
+ CREATE INDEX catalog_examples_by_bucket ON catalog_buckets(example_id,field);
+ CREATE TEMP TABLE catalog_fields(field INTEGER PRIMARY KEY,amount INTEGER,distinct_count INTEGER);
+ CREATE TEMP TABLE catalog_changes(position INTEGER PRIMARY KEY,old_bucket INTEGER,new_value BLOB,old_value BLOB,old_example INTEGER);
+ CREATE TEMP TABLE affected_cells(cell_uuid TEXT PRIMARY KEY) WITHOUT ROWID;
+ CREATE TEMP TABLE summary_cells(cell_uuid TEXT PRIMARY KEY,tagged_cell INTEGER,tagged_epochs INTEGER) WITHOUT ROWID;
+ CREATE TEMP TABLE summary_cell_tags(cell_uuid TEXT,tag TEXT COLLATE BINARY,epoch_count INTEGER,
+ PRIMARY KEY(cell_uuid,tag)) WITHOUT ROWID;
+ CREATE TEMP TABLE summary_tags(tag TEXT COLLATE BINARY PRIMARY KEY,epoch_count INTEGER,cell_count INTEGER) WITHOUT ROWID;
+ CREATE INDEX summary_rank ON summary_tags(epoch_count DESC,tag COLLATE TAG_ORDER);
+ CREATE TEMP TABLE summary_totals(id INTEGER PRIMARY KEY,tagged_cells INTEGER,tagged_epochs INTEGER,total_tags INTEGER);
+ CREATE TEMP TABLE summary_changes(tag TEXT COLLATE BINARY PRIMARY KEY,amount INTEGER) WITHOUT ROWID;
+ ''')
+ return self.connection
+
+ @_serialized
+ def refresh(self):
+ current=self.generation()
+ if current is None:return False
+ if current==self.token:return True
+ change=self.changes(self.token) if self.token is not None else None
+ keys=None
+ if change is not None:
+ keys,current=change
+ if current is None:return False
+ # Profile display metadata is separate from the author_name saved
+ # with each annotation. It changes freshness, not historical text.
+ keys=[key for key in keys if key['target_kind'] in ('cell','epoch')]
+ if keys is None and self.connection is not None:
+ # A lost authority/changefeed already makes the old index unusable.
+ # Drop that disposable projection before rebuilding so a complete
+ # old database and a full rollback journal never coexist on disk.
+ self.close()
+ db=self._open();loaded=0;removed=0;self.tag_ids.clear()
+ previous_tags={}
+ # Large external batches conservatively drop matches rather than keep
+ # another unbounded copy of every changed record's tag strings.
+ changed_tags=set() if keys is not None and len(keys)<=MATCH_DELTA_MAX_RECORDS and self.match_cache else None
+ reset_vocabulary=keys is not None and len(keys)>MATCH_DELTA_MAX_RECORDS and self.shared_vocabulary is not None
+ try:
+ with db:
+ if reset_vocabulary:
+ # sqlite3's context manager does not begin a transaction
+ # for DDL alone. Include the lazy reset in rollback too.
+ if not db.in_transaction:db.execute('BEGIN')
+ self.shared_vocabulary.discard()
+ vocabulary_before=self.shared_vocabulary.before(keys) if self.shared_vocabulary is not None and keys is not None and not reset_vocabulary else None
+ if changed_tags is not None:
+ for key in keys:
+ identity=(key['target_kind'],key['target_uuid'],key['profile_uuid'])
+ previous_tags[identity]={row[0] for row in db.execute('SELECT v.tag FROM records r '
+ 'JOIN tag_memberships m ON m.record_id=r.record_id JOIN tag_values v ON v.tag_id=m.tag_id '
+ 'WHERE r.kind=? AND r.target=? AND r.profile=?',identity)}
+ if keys is None:
+ db.execute('DELETE FROM tag_memberships');db.execute('DELETE FROM records');db.execute('DELETE FROM tag_values')
+ else:
+ params=[(key['target_kind'],key['target_uuid'],key['profile_uuid']) for key in keys]
+ db.execute('DELETE FROM changed_tag_values')
+ db.executemany('INSERT OR IGNORE INTO changed_tag_values SELECT m.tag_id FROM records r JOIN tag_memberships m ON m.record_id=r.record_id WHERE r.kind=? AND r.target=? AND r.profile=?',params)
+ removed=db.executemany('DELETE FROM records WHERE kind=? AND target=? AND profile=?',params).rowcount
+ for row in self.records(keys):
+ values=self.validate(row)
+ key=(row['target_kind'],row['target_uuid'],row['profile_uuid'])
+ if changed_tags is not None:
+ changed_tags.update(previous_tags.pop(key,set()) ^ set(values))
+ record_id=db.execute('INSERT INTO records(kind,target,profile,author,revision) VALUES (?,?,?,?,?)',(*key,row['author_name'],row['revision'])).lastrowid
+ ids=[]
+ for tag in values:
+ tag_id=self.tag_ids.get(tag)
+ if tag_id is None:
+ found=db.execute('SELECT tag_id FROM tag_values WHERE tag=?',(tag,)).fetchone()
+ tag_id=found[0] if found else db.execute('INSERT INTO tag_values(tag) VALUES (?)',(tag,)).lastrowid
+ self.tag_ids[tag]=tag_id
+ if len(self.tag_ids)>8192:self.tag_ids.popitem(last=False)
+ else:self.tag_ids.move_to_end(tag)
+ ids.append(tag_id)
+ db.executemany('INSERT INTO tag_memberships VALUES (?,?)',((record_id,tag_id) for tag_id in ids))
+ loaded+=1
+ if keys is not None:
+ db.execute('DELETE FROM tag_values WHERE tag_id IN (SELECT c.tag_id FROM changed_tag_values c WHERE NOT EXISTS (SELECT 1 FROM tag_memberships m WHERE m.tag_id=c.tag_id))')
+ if changed_tags is not None:
+ for values in previous_tags.values():changed_tags.update(values)
+ self._update_catalog(keys)
+ self._update_summary(keys)
+ if vocabulary_before is not None:self.shared_vocabulary.after(keys,vocabulary_before)
+ if self.generation()!=current:
+ raise SharedTagsChanged('Shared tags changed while loading this selection. Refresh and try again.')
+ self.token=copy.deepcopy(current)
+ if reset_vocabulary:self.shared_vocabulary=None
+ self._invalidate_matches(changed_tags)
+ self.stats['full_builds' if keys is None else 'delta_updates']+=1
+ self.stats['rows_loaded']+=loaded
+ self.stats['rows_indexed']=loaded if keys is None else self.stats['rows_indexed']-removed+loaded
+ # Reclaim bounded batches of free pages after large removals. Only
+ # this derived database is touched; its page cache is capped at8MiB.
+ db.execute('PRAGMA incremental_vacuum(256)')
+ return True
+ except Exception:
+ # No invalid or mixed-generation membership is published. SQLite's
+ # transaction retains the previous complete index after a failure.
+ self.tag_ids.clear();raise
+
+ def _scope(self,rows,kind='filter'):
+ identities=tuple((row['epoch_uuid'],row['cell_uuid']) for row in rows)
+ if identities==self.scopes[kind]:return
+ if len({key for key,_ in identities})!=len(identities):
+ raise ValueError('Expected unique epoch identities in the tag-filter scope')
+ db=self._open();table=kind+'_scope'
+ with db:
+ db.execute('DELETE FROM '+table)
+ db.executemany('INSERT INTO '+table+' VALUES (?,?,?)',((position,*pair) for position,pair in enumerate(identities)))
+ if kind=='catalog':self._clear_catalog()
+ if kind=='summary':self._clear_summary()
+ # Scopes retain only already-owned UUID strings; they never retain tag
+ # provenance. Separate scopes keep pagination from discarding aggregate
+ # work for the overview or project field catalog.
+ self.scopes[kind]=identities if len(identities)<=SCOPE_CACHE_ROWS else None
+ self.scope_revisions[kind]+=1
+ if kind=='summary':self.summary_cache=None
+ if kind=='catalog':self.catalog_cache={}
+ if kind=='filter':self.match_cache.clear();self.match_cache_bytes=0
+
+ def _check_generation(self):
+ if self.generation()!=self.token:
+ raise SharedTagsChanged('Shared tags changed while loading this selection. Refresh and try again.')
+
+ @staticmethod
+ def _tag_dependencies(filters,predicate):
+ """Exact literals sufficient for contains/boolean queries; None is broad.
+
+ Array equality and tagged/empty-state tests depend on the whole tag set,
+ so any changed tag conservatively invalidates those cached results.
+ """
+ if 'tagged' in filters:return None
+ dependencies={filters['tag']} if 'tag' in filters else set()
+ def walk(node):
+ if 'not' in node:return walk(node['not'])
+ for logic in ('all','any'):
+ if logic in node:
+ result=set()
+ for child in node[logic]:
+ current=walk(child)
+ if current is None:return None
+ result.update(current)
+ return result
+ if node['operator']=='contains' and node['field'] in FIELDS:
+ return {node['value']} if isinstance(node['value'],str) else set()
+ if node['operator'] in ('exists','missing','is_null'):return set()
+ return None
+ if predicate is not None:
+ current=walk(predicate)
+ if current is None:return None
+ dependencies.update(current)
+ return dependencies
+
+ def _invalidate_matches(self,changed_tags):
+ if changed_tags is None:
+ self.match_cache.clear();self.match_cache_bytes=0
+ return
+ if not changed_tags:return
+ for key,(_,size) in list(self.match_cache.items()):
+ dependencies=self._tag_dependencies(*json.loads(key))
+ if dependencies is None or not dependencies.isdisjoint(changed_tags):
+ del self.match_cache[key];self.match_cache_bytes-=size
+
+ @_serialized
+ def summary(self,rows):
+ """Exact overview counts/top vocabulary, with no per-epoch chip copies."""
+ self._scope(rows,'summary');key=(self.token,self.scope_revisions['summary'])
+ if self.summary_cache is not None and self.summary_cache[0]==key:
+ self._check_generation();return copy.deepcopy(self.summary_cache[1])
+ db=self.connection
+ stored=db.execute('SELECT tagged_cells,tagged_epochs,total_tags FROM summary_totals WHERE id=1').fetchone()
+ if stored is None:
+ with db:
+ db.execute('INSERT INTO summary_totals VALUES (1,0,0,0)')
+ for (cell,) in db.execute('SELECT DISTINCT cell_uuid FROM summary_scope'):
+ self._replace_summary_cell(cell)
+ stored=db.execute('SELECT tagged_cells,tagged_epochs,total_tags FROM summary_totals WHERE id=1').fetchone()
+ tagged_cells,tagged_epochs,total=stored
+ counts=db.execute('SELECT tag,epoch_count,cell_count FROM summary_tags ORDER BY epoch_count DESC,tag COLLATE TAG_ORDER LIMIT 12').fetchall()
+ result={'shared_tagged_cells':tagged_cells,'shared_tagged_epochs':tagged_epochs,
+ 'tags':[{'tag':tag,'epoch_count':epochs,'cell_count':cells} for tag,epochs,cells in counts],
+ 'total_tags':total,'truncated':total>12}
+ self._check_generation();self.summary_cache=(key,copy.deepcopy(result))
+ return result
+
+ def _clear_summary(self):
+ for table in ('summary_cells','summary_cell_tags','summary_tags','summary_totals','summary_changes'):
+ self.connection.execute('DELETE FROM '+table)
+
+ def _replace_summary_cell(self,cell):
+ """Reconcile one exact eligible cell, retaining profile/inheritance unions."""
+ db=self.connection;difference=0
+ previous=db.execute('SELECT tagged_cell,tagged_epochs FROM summary_cells WHERE cell_uuid=?',(cell,)).fetchone() or (0,0)
+ for tag,amount in db.execute('SELECT tag,epoch_count FROM summary_cell_tags WHERE cell_uuid=?',(cell,)):
+ db.execute('UPDATE summary_tags SET epoch_count=epoch_count-?,cell_count=cell_count-1 WHERE tag=?',(amount,tag))
+ difference-=db.execute('DELETE FROM summary_tags WHERE tag=? AND cell_count=0',(tag,)).rowcount
+ db.execute('DELETE FROM summary_cell_tags WHERE cell_uuid=?',(cell,))
+ db.execute('DELETE FROM summary_changes')
+ db.execute("INSERT INTO summary_changes SELECT v.tag,COUNT(DISTINCT s.epoch_uuid) FROM summary_scope s CROSS JOIN records r ON r.kind='epoch' AND r.target=s.epoch_uuid JOIN tag_memberships m ON m.record_id=r.record_id JOIN tag_values v ON v.tag_id=m.tag_id WHERE s.cell_uuid=? GROUP BY v.tag",(cell,))
+ size=db.execute('SELECT COUNT(*) FROM summary_scope WHERE cell_uuid=?',(cell,)).fetchone()[0]
+ db.execute("INSERT OR REPLACE INTO summary_changes SELECT tag,? FROM tags WHERE kind='cell' AND target=? GROUP BY tag",(size,cell))
+ tagged_cell=db.execute("SELECT EXISTS (SELECT 1 FROM tags WHERE kind='cell' AND target=?)",(cell,)).fetchone()[0]
+ tagged_epochs=size if tagged_cell else db.execute("SELECT COUNT(*) FROM summary_scope s WHERE s.cell_uuid=? AND EXISTS (SELECT 1 FROM tags WHERE kind='epoch' AND target=s.epoch_uuid)",(cell,)).fetchone()[0]
+ for tag,amount in db.execute('SELECT tag,amount FROM summary_changes'):
+ db.execute('INSERT INTO summary_cell_tags VALUES (?,?,?)',(cell,tag,amount))
+ difference+=db.execute('INSERT OR IGNORE INTO summary_tags VALUES (?,0,0)',(tag,)).rowcount
+ db.execute('UPDATE summary_tags SET epoch_count=epoch_count+?,cell_count=cell_count+1 WHERE tag=?',(amount,tag))
+ db.execute('INSERT OR REPLACE INTO summary_cells VALUES (?,?,?)',(cell,tagged_cell,tagged_epochs))
+ db.execute('UPDATE summary_totals SET tagged_cells=tagged_cells+?,tagged_epochs=tagged_epochs+?,total_tags=total_tags+? WHERE id=1',
+ (tagged_cell-previous[0],tagged_epochs-previous[1],difference))
+
+ def _update_summary(self,keys):
+ if keys is None:self._clear_summary();return
+ db=self.connection
+ if not keys or db.execute('SELECT 1 FROM summary_totals WHERE id=1').fetchone() is None:return
+ db.execute('DELETE FROM affected_cells')
+ for key in keys:
+ column='cell_uuid' if key['target_kind']=='cell' else 'epoch_uuid'
+ db.execute('INSERT OR IGNORE INTO affected_cells SELECT DISTINCT cell_uuid FROM summary_scope WHERE '+column+'=?',(key['target_uuid'],))
+ for (cell,) in db.execute('SELECT cell_uuid FROM affected_cells'):
+ self._replace_summary_cell(cell)
+
+ @staticmethod
+ def _array_sql(field):
+ """Sorted exact union; the scalar JSON result exists only for one row."""
+ if field in FIELDS:
+ scopes=[f"(kind='{kind}' AND target=s.{'cell_uuid' if kind=='cell' else 'epoch_uuid'})"
+ for kind in FIELDS[field]]
+ source='SELECT DISTINCT tag AS value FROM tags WHERE '+' OR '.join(scopes)+' ORDER BY value'
+ elif field in ('annotations/authors','annotations/author_uuids'):
+ column='author' if field=='annotations/authors' else 'profile'
+ source=f'''SELECT DISTINCT r.{column} AS value FROM records r WHERE
+ ((r.kind='cell' AND r.target=s.cell_uuid) OR (r.kind='epoch' AND r.target=s.epoch_uuid))
+ AND EXISTS (SELECT 1 FROM tags t WHERE t.kind=r.kind AND t.target=r.target AND t.profile=r.profile)
+ ORDER BY value'''
+ else:raise ValueError('Unsupported shared annotation catalog field')
+ return '(SELECT json_group_array(value) FROM ('+source+'))'
+
+ def _clear_catalog(self):
+ for table in ('catalog_values','catalog_buckets','catalog_fields','catalog_changes','catalog_examples','changed_examples'):
+ self.connection.execute('DELETE FROM '+table)
+
+ def _update_catalog(self,keys):
+ """Update only changed members of any materialized active-scope catalog."""
+ if keys is None:self._clear_catalog();return
+ db=self.connection
+ fields=[row[0] for row in db.execute('SELECT field FROM catalog_fields')]
+ if not fields or not keys:return
+ db.execute('DELETE FROM affected_positions')
+ for key in keys:
+ column='cell_uuid' if key['target_kind']=='cell' else 'epoch_uuid'
+ db.execute('INSERT OR IGNORE INTO affected_positions SELECT position FROM catalog_scope WHERE '+column+'=?',(key['target_uuid'],))
+ db.execute('DELETE FROM changed_examples')
+ for field in fields:
+ db.execute('DELETE FROM catalog_changes')
+ db.execute('INSERT INTO catalog_changes SELECT s.position,c.bucket_id,pack_array('+self._array_sql(CATALOG_FIELDS[field])+'),b.value,b.example_id'+
+ ' FROM affected_positions a JOIN catalog_scope s ON s.position=a.position JOIN catalog_values c ON c.field=? AND c.position=s.position JOIN catalog_buckets b ON b.bucket_id=c.bucket_id',(field,))
+ difference=0
+ for position,old,new,old_example in db.execute('SELECT position,old_bucket,new_value,old_example FROM catalog_changes WHERE old_value!=new_value'):
+ db.execute('DELETE FROM catalog_values WHERE field=? AND position=?',(field,position))
+ db.execute('UPDATE catalog_buckets SET amount=amount-1 WHERE bucket_id=?',(old,))
+ removed=db.execute('DELETE FROM catalog_buckets WHERE bucket_id=? AND amount=0',(old,)).rowcount
+ difference-=removed
+ if removed:db.execute('INSERT OR IGNORE INTO changed_examples VALUES (?)',(old_example,))
+ db.execute('UPDATE catalog_buckets SET first_position=(SELECT MIN(position) FROM catalog_values WHERE bucket_id=?) WHERE bucket_id=? AND first_position=?',
+ (old,old,position))
+ found=db.execute('SELECT bucket_id FROM catalog_buckets WHERE field=? AND value_hash=array_hash(?) AND value=?',(field,new,new)).fetchone()
+ if found:bucket=found[0]
+ else:
+ prefix=zlib.decompress(new).decode('utf-8')[:160]
+ db.execute('INSERT OR IGNORE INTO catalog_examples(prefix) VALUES (?)',(prefix,))
+ example=db.execute('SELECT example_id FROM catalog_examples WHERE prefix=?',(prefix,)).fetchone()[0]
+ bucket=db.execute('INSERT INTO catalog_buckets(field,value,value_hash,amount,first_position,example_id) VALUES (?,?,array_hash(?),0,?,?)',(field,new,new,position,example)).lastrowid
+ difference+=1
+ db.execute('INSERT INTO catalog_values VALUES (?,?,?)',(field,position,bucket))
+ db.execute('UPDATE catalog_buckets SET amount=amount+1,first_position=MIN(first_position,?) WHERE bucket_id=?',(position,bucket))
+ db.execute('UPDATE catalog_fields SET distinct_count=distinct_count+? WHERE field=?',(difference,field))
+ db.execute('DELETE FROM catalog_changes')
+ db.execute('DELETE FROM catalog_examples WHERE example_id IN (SELECT c.example_id FROM changed_examples c WHERE NOT EXISTS (SELECT 1 FROM catalog_buckets b WHERE b.example_id=c.example_id))')
+
+ @_serialized
+ def catalog(self,rows,fields):
+ """Exact metrics over lossless compressed arrays and integer buckets.
+
+ The digest is only a small lookup index. Every lookup also compares the
+ complete compressed bytes, so collisions cannot merge distinct arrays.
+ The displayed examples are truncated to160 characters. Sorting those
+ prefixes gives exactly the same displayed examples as sorting complete
+ JSON arrays first; equal prefixes remain equal after truncation.
+ """
+ self._scope(rows,'catalog');key=(self.token,self.scope_revisions['catalog']);result={};db=self.connection
+ for field in fields:
+ field_id=CATALOG_FIELDS.index(field)
+ cached=self.catalog_cache.get(field)
+ if cached is not None and cached[0]==key:
+ result[field]=copy.deepcopy(cached[1]);continue
+ stored=db.execute('SELECT amount,distinct_count FROM catalog_fields WHERE field=?',(field_id,)).fetchone()
+ if stored is None:
+ with db:
+ db.execute('DELETE FROM catalog_changes')
+ db.execute('INSERT INTO catalog_changes SELECT s.position,NULL,pack_array('+self._array_sql(field)+'),NULL,NULL FROM catalog_scope s')
+ db.execute('INSERT OR IGNORE INTO catalog_examples(prefix) SELECT DISTINCT array_prefix(new_value) FROM catalog_changes')
+ db.execute('INSERT INTO catalog_buckets(field,value,value_hash,amount,first_position,example_id) SELECT ?,c.new_value,array_hash(c.new_value),COUNT(*),MIN(c.position),e.example_id FROM catalog_changes c JOIN catalog_examples e ON e.prefix=array_prefix(c.new_value) GROUP BY c.new_value',(field_id,))
+ db.execute('INSERT INTO catalog_values SELECT ?,c.position,b.bucket_id FROM catalog_changes c CROSS JOIN catalog_buckets b ON b.field=? AND b.value_hash=array_hash(c.new_value) AND b.value=c.new_value',(field_id,field_id))
+ count=db.execute('SELECT COUNT(*) FROM catalog_scope').fetchone()[0]
+ distinct=db.execute('SELECT COUNT(*) FROM catalog_buckets WHERE field=?',(field_id,)).fetchone()[0]
+ db.execute('INSERT INTO catalog_fields VALUES (?,?,?)',(field_id,count,distinct))
+ db.execute('DELETE FROM catalog_changes')
+ else:count,distinct=stored
+ examples=[row[0] for row in db.execute('SELECT e.prefix FROM catalog_examples e CROSS JOIN catalog_buckets b ON b.example_id=e.example_id WHERE b.field=? ORDER BY e.prefix LIMIT 5',(field_id,))]
+ choices=[{'value':json.loads(zlib.decompress(value)),'type':'array','count':amount} for value,amount in db.execute(
+ 'SELECT value,amount FROM catalog_buckets WHERE field=? ORDER BY first_position LIMIT 50',(field_id,))]
+ current={'count':count,'distinct_count':distinct,'recorded_distinct_count':distinct,
+ 'missing_count':0,'null_count':0,'varying':distinct>1,'examples':examples,
+ 'grouping_role':'annotation','grouping_priority':100,'varies_within_cell':False,
+ 'suggested_rank':None,'suggestion_reason':'','high_cardinality':distinct>24,
+ 'active_types':['array'] if count else [],'types':['array'],'types_scope':'project_annotations',
+ 'element_types':['string'],'choices':choices,'choices_truncated':distinct>50}
+ self.catalog_cache[field]=(key,copy.deepcopy(current));result[field]=current
+ self._check_generation()
+ return result
+
+ @staticmethod
+ def _contains(field,value,params):
+ if not isinstance(value,str):return '0'
+ parts=[]
+ for kind in FIELDS[field]:
+ column='cell_uuid' if kind=='cell' else 'epoch_uuid'
+ source=("SELECT r.target FROM records r CROSS JOIN tag_memberships m ON m.record_id=r.record_id WHERE r.kind='cell' AND m.tag_id=(SELECT tag_id FROM tag_values WHERE tag=?)" if kind=='cell' else
+ "SELECT r.target FROM tag_values v JOIN tag_memberships m ON m.tag_id=v.tag_id JOIN records r ON r.record_id=m.record_id WHERE v.tag=? AND r.kind='epoch'")
+ parts.append(f"s.{column} IN ({source})")
+ params.append(value)
+ return '('+' OR '.join(parts)+')'
+
+ def _equal(self,field,value,params):
+ # Display/filter arrays are exact sorted unions across profiles. Typed
+ # scalars, repeated values and differently ordered arrays cannot equal.
+ if (not isinstance(value,list) or any(not isinstance(tag,str) for tag in value)
+ or value!=sorted(set(value))):return '0'
+ # Store array literals once instead of expanding one SQL parameter and
+ # subquery per tag per scope. The public grammar permits enough literals
+ # to exceed SQLite's platform-dependent variable limit if expanded.
+ literal_id=self._literal_id;self._literal_id+=1
+ if value:
+ self.connection.executemany('INSERT INTO predicate_values VALUES (?,?)',
+ ((literal_id,tag) for tag in value))
+ parts=[];scopes=[]
+ if value:
+ def rarity(tag):
+ key=(field,tag)
+ if key not in self._rarity:
+ amount=0
+ for kind in FIELDS[field]:
+ # Bounded probes select an effective seed without a
+ # vocabulary-wide count. A cell match can inherit to
+ # many epochs, so conservatively saturate its estimate.
+ limit=1 if kind=='cell' else 32
+ if kind=='cell':
+ count=self.connection.execute("SELECT COUNT(*) FROM (SELECT 1 FROM records r CROSS JOIN tag_memberships m ON m.record_id=r.record_id WHERE r.kind='cell' AND m.tag_id=(SELECT tag_id FROM tag_values WHERE tag=?) LIMIT 1)",(tag,)).fetchone()[0]
+ else:
+ count=self.connection.execute("SELECT COUNT(*) FROM (SELECT 1 FROM tag_values v JOIN tag_memberships m ON m.tag_id=v.tag_id JOIN records r ON r.record_id=m.record_id WHERE v.tag=? AND r.kind='epoch' LIMIT 32)",(tag,)).fetchone()[0]
+ amount+=32 if kind=='cell' and count else count
+ self._rarity[key]=amount
+ return self._rarity[key]
+ seed=min(value,key=rarity)
+ parts.append(self._contains(field,seed,params))
+ for kind in FIELDS[field]:
+ column='cell_uuid' if kind=='cell' else 'epoch_uuid'
+ outside=f' AND tag NOT IN (SELECT tag FROM predicate_values WHERE literal_id={literal_id})' if value else ''
+ parts.append(f"NOT EXISTS (SELECT 1 FROM tags WHERE kind='{kind}' AND target=s.{column}{outside})")
+ scopes.append(f"(kind='{kind}' AND target=s.{column})")
+ if value:
+ parts.append('(SELECT COUNT(DISTINCT tag) FROM tags WHERE '+' OR '.join(scopes)+f')={len(value)}')
+ return '('+' AND '.join(parts)+')'
+
+ def _predicate(self,node,params):
+ if 'all' in node:return '('+(' AND '.join(self._predicate(child,params) for child in node['all']) or '1')+')'
+ if 'any' in node:return '('+(' OR '.join(self._predicate(child,params) for child in node['any']) or '0')+')'
+ if 'not' in node:return '(NOT '+self._predicate(node['not'],params)+')'
+ field,operator=node['field'],node['operator']
+ if field not in FIELDS:raise ValueError('Only shared tag arrays are available in this index')
+ if operator=='exists':return '1'
+ if operator in ('missing','is_null'):return '0'
+ value=node['value']
+ if operator=='contains':return self._contains(field,value,params)
+ if operator in ('eq','ne'):
+ equal=self._equal(field,value,params)
+ return '(NOT '+equal+')' if operator=='ne' else equal
+ if operator in ('in','not_in'):
+ branches=['SELECT s.epoch_uuid FROM filter_scope s WHERE '+self._equal(field,item,params) for item in value]
+ any_equal='s.epoch_uuid IN ('+' UNION '.join(branches)+')' if branches else '0'
+ return '(NOT '+any_equal+')' if operator=='not_in' else any_equal
+ # Validated tag arrays never satisfy numeric ordering predicates.
+ return '0'
+
+ @_serialized
+ def matching(self,rows,filters,predicate=None):
+ """Return ordered UUID membership and its verified source token.
+
+ The caller validates predicate shape/typed limits before passing it here.
+ The SQLite scope makes NOT/empty arrays relative to exact eligible rows,
+ including rows with no direct or inherited annotations.
+ """
+ if self.token is None:raise RuntimeError('Refresh the shared-tag index before querying it')
+ self._scope(rows)
+ cache_key=json.dumps([filters,predicate],sort_keys=True,ensure_ascii=False,separators=(',',':'))
+ cached=self.match_cache.get(cache_key)
+ if cached is not None:
+ self._check_generation();self.match_cache.move_to_end(cache_key)
+ return cached[0],copy.deepcopy(self.token)
+ params=[];parts=[];self._literal_id=0;self._rarity={}
+ self.connection.execute('DELETE FROM predicate_values')
+ if 'tag' in filters:parts.append(self._contains('annotations/effective/tags',filters['tag'],params))
+ if 'tagged' in filters:parts.append('(NOT '+self._equal('annotations/effective/tags',[],params)+')')
+ if predicate is not None:parts.append(self._predicate(predicate,params))
+ sql='SELECT s.epoch_uuid FROM filter_scope AS s WHERE '+(' AND '.join(parts) or '1')+' ORDER BY s.position'
+ result=tuple(row[0] for row in self.connection.execute(sql,params))
+ self.connection.commit()
+ if self.generation()!=self.token:
+ raise SharedTagsChanged('Shared tags changed while loading this selection. Refresh and try again.')
+ size=sys.getsizeof(cache_key)+sys.getsizeof(result)+sum(sys.getsizeof(value) for value in result)
+ if size<=MATCH_CACHE_BYTES:
+ self.match_cache[cache_key]=(result,size);self.match_cache_bytes+=size
+ while len(self.match_cache)>8 or self.match_cache_bytes>MATCH_CACHE_BYTES:
+ _,(_,removed)=self.match_cache.popitem(last=False);self.match_cache_bytes-=removed
+ return result,copy.deepcopy(self.token)
+
+ @_serialized
+ def suggestions(self,query,limit):
+ if not self.refresh():return None
+ if self.shared_vocabulary is None:
+ from workspace_shared_vocabulary import SharedVocabulary
+ try:self.shared_vocabulary=SharedVocabulary(self)
+ except Exception:
+ # A partially-created disposable schema must never be reused.
+ self.close()
+ raise
+ return self.shared_vocabulary.suggestions(query,limit)
+
+ @_serialized
+ def storage_stats(self):
+ """Account for SQLite's live temp database as well as the named file."""
+ if self.connection is None:return {'sqlite_allocated_bytes':0,'sqlite_used_bytes':0,'match_cache_bytes':0}
+ result={}
+ for schema in ('main','temp'):
+ size=self.connection.execute(f'PRAGMA {schema}.page_size').fetchone()[0]
+ pages=self.connection.execute(f'PRAGMA {schema}.page_count').fetchone()[0]
+ free=self.connection.execute(f'PRAGMA {schema}.freelist_count').fetchone()[0]
+ result[schema+'_allocated_bytes']=size*pages
+ result[schema+'_used_bytes']=size*(pages-free)
+ result['sqlite_allocated_bytes']=result['main_allocated_bytes']+result['temp_allocated_bytes']
+ result['sqlite_used_bytes']=result['main_used_bytes']+result['temp_used_bytes']
+ result['match_cache_bytes']=self.match_cache_bytes
+ return result
+
+ @_serialized
+ def close(self):
+ if self.connection is not None:self.connection.close();self.connection=None
+ if self.folder is not None:self.folder.cleanup();self.folder=None
+ self.token=None;self.scopes=dict.fromkeys(self.scopes);self.tag_ids.clear();self.match_cache.clear();self.match_cache_bytes=0
+ self.summary_cache=None;self.catalog_cache={}
+ self.shared_vocabulary=None
diff --git a/python/workspace_shared_vocabulary.py b/python/workspace_shared_vocabulary.py
new file mode 100644
index 0000000..147acd3
--- /dev/null
+++ b/python/workspace_shared_vocabulary.py
@@ -0,0 +1,122 @@
+"""Lazy shared-tag autocomplete over the existing disposable membership index.
+
+Counts refer to distinct (kind, target UUID), never profile rows or inherited
+copies. Author attribution remains keyed by profile UUID; a profile's stored
+name is taken from its lexically last (kind, target) tagged record, making the
+formerly unspecified SQL-row order deterministic. No acquisition data is copied.
+"""
+from __future__ import annotations
+
+
+class SharedVocabulary:
+ def __init__(self, index):
+ self.index = index
+ self.sequence = 0
+ db = index.connection
+ db.create_function('shared_casefold', 1, str.casefold, deterministic=True)
+ db.executescript('''
+ CREATE TABLE shared_vocabulary_counts(tag_id INTEGER PRIMARY KEY REFERENCES tag_values(tag_id) ON DELETE CASCADE, amount INTEGER NOT NULL);
+ CREATE INDEX shared_vocabulary_prefix ON tag_values(shared_casefold(tag));
+ CREATE TABLE shared_vocabulary_ready(tag_id INTEGER PRIMARY KEY REFERENCES tag_values(tag_id) ON DELETE CASCADE, used INTEGER NOT NULL);
+ CREATE TABLE shared_vocabulary_authors(tag_id INTEGER REFERENCES tag_values(tag_id) ON DELETE CASCADE,
+ profile TEXT COLLATE BINARY, author TEXT, kind TEXT COLLATE BINARY, target TEXT COLLATE BINARY,
+ PRIMARY KEY(tag_id,profile)) WITHOUT ROWID;
+ ''')
+ with db:
+ db.execute('INSERT INTO shared_vocabulary_counts SELECT tag_id,COUNT(*) FROM '
+ '(SELECT m.tag_id,r.kind,r.target FROM tag_memberships m JOIN records r ON r.record_id=m.record_id '
+ 'GROUP BY m.tag_id,r.kind,r.target) GROUP BY tag_id')
+
+ def discard(self):
+ """Large external batches rebuild SQL aggregates lazily, without a
+ Python copy of unbounded old/new target unions. Caller owns transaction.
+ """
+ for table in ('shared_vocabulary_counts','shared_vocabulary_authors','shared_vocabulary_ready'):
+ self.index.connection.execute('DROP TABLE '+table)
+ self.index.connection.execute('DROP INDEX shared_vocabulary_prefix')
+
+ def before(self, keys):
+ db = self.index.connection
+ targets = {(key['target_kind'], key['target_uuid']) for key in keys}
+ unions = {key: {row[0] for row in db.execute('SELECT DISTINCT m.tag_id FROM records r '
+ 'JOIN tag_memberships m ON m.record_id=r.record_id WHERE r.kind=? AND r.target=?', key)} for key in targets}
+ records = {}
+ for key in keys:
+ identity = (key['target_kind'], key['target_uuid'], key['profile_uuid'])
+ records[identity] = {row[0] for row in db.execute('SELECT m.tag_id FROM records r '
+ 'JOIN tag_memberships m ON m.record_id=r.record_id WHERE r.kind=? AND r.target=? AND r.profile=?', identity)}
+ return unions, records
+
+ def after(self, keys, previous):
+ db = self.index.connection
+ current, records = self.before(keys)
+ old_unions, old_records = previous
+ for identity, before in old_unions.items():
+ after = current[identity]
+ for tag_id in before - after:
+ # Removing the last membership cascades the vocabulary row.
+ db.execute('UPDATE shared_vocabulary_counts SET amount=amount-1 WHERE tag_id=?', (tag_id,))
+ for tag_id in after - before:
+ db.execute('INSERT INTO shared_vocabulary_counts VALUES (?,1) '
+ 'ON CONFLICT(tag_id) DO UPDATE SET amount=amount+1', (tag_id,))
+ # Only already-requested author lists need maintenance. The canonical
+ # winner usually stays unchanged when the scientist edits another row.
+ for identity in old_records:
+ kind, target, profile = identity
+ for tag_id in old_records[identity] | records[identity]:
+ if not db.execute('SELECT 1 FROM shared_vocabulary_ready WHERE tag_id=?', (tag_id,)).fetchone():
+ continue
+ winner = db.execute('SELECT kind,target FROM shared_vocabulary_authors WHERE tag_id=? AND profile=?', (tag_id, profile)).fetchone()
+ if winner is not None and winner != (kind, target) and (tag_id not in records[identity] or winner > (kind, target)):
+ continue
+ # A changed winning record may have lost the tag; resolve the
+ # exact surviving winner, including other records by that UUID.
+ found = db.execute('SELECT r.author,r.kind,r.target FROM tag_memberships m JOIN records r '
+ 'ON r.record_id=m.record_id WHERE m.tag_id=? AND r.profile=? '
+ 'ORDER BY r.kind DESC,r.target DESC LIMIT 1', (tag_id, profile)).fetchone()
+ db.execute('DELETE FROM shared_vocabulary_authors WHERE tag_id=? AND profile=?', (tag_id, profile))
+ if found:
+ db.execute('INSERT INTO shared_vocabulary_authors VALUES (?,?,?,?,?)', (tag_id, profile, *found))
+ if db.execute('SELECT 1 FROM shared_vocabulary_counts WHERE amount<=0 LIMIT 1').fetchone():
+ raise ValueError('Shared vocabulary counts no longer match annotation targets')
+
+ def authors(self, tag_id):
+ db = self.index.connection
+ if not db.execute('SELECT 1 FROM shared_vocabulary_ready WHERE tag_id=?', (tag_id,)).fetchone():
+ db.execute('INSERT INTO shared_vocabulary_authors SELECT ?,profile,author,kind,target FROM '
+ '(SELECT r.profile,r.author,r.kind,r.target,ROW_NUMBER() OVER '
+ '(PARTITION BY r.profile ORDER BY r.kind DESC,r.target DESC) AS position '
+ 'FROM tag_memberships m JOIN records r ON r.record_id=m.record_id WHERE m.tag_id=?) WHERE position=1', (tag_id, tag_id))
+ db.execute('INSERT INTO shared_vocabulary_ready VALUES (?,0)', (tag_id,))
+ self.sequence += 1
+ db.execute('UPDATE shared_vocabulary_ready SET used=? WHERE tag_id=?', (self.sequence, tag_id))
+ return [{'profile_uuid': profile, 'display_name': name} for profile, name in db.execute(
+ 'SELECT profile,author FROM shared_vocabulary_authors WHERE tag_id=? ORDER BY profile', (tag_id,))]
+
+ def suggestions(self, query, limit):
+ from workspace_curation_vocabulary import CurationVocabulary
+ db = self.index.connection
+ prefix = query.strip().casefold()
+ where = ''; params = []
+ if any(0xd800 <= ord(char) <= 0xdfff for char in prefix):
+ where = ' WHERE 0'
+ elif prefix:
+ where = ' WHERE shared_casefold(v.tag)>=?'; params.append(prefix)
+ upper = CurationVocabulary._upper_prefix(prefix)
+ if upper is not None:
+ where += ' AND shared_casefold(v.tag)'; params.append(upper)
+ source = ' FROM tag_values v JOIN shared_vocabulary_counts c ON c.tag_id=v.tag_id' + where
+ total = db.execute('SELECT COUNT(*)' + source, params).fetchone()[0]
+ rows = db.execute('SELECT v.tag_id,v.tag,c.amount' + source +
+ ' ORDER BY c.amount DESC,v.tag COLLATE TAG_ORDER LIMIT ?', (*params, limit)).fetchall()
+ with db:
+ tags = [{'tag': tag, 'count': count, 'authors': self.authors(tag_id)} for tag_id, tag, count in rows]
+ # Retain author evidence for at most 128 requested tag names. Counts
+ # remain compact per-tag integers; UUID/author lists are not copied
+ # for the entire vocabulary just because many prefixes were typed.
+ retired = [row[0] for row in db.execute('SELECT tag_id FROM shared_vocabulary_ready ORDER BY used DESC LIMIT -1 OFFSET 128')]
+ db.executemany('DELETE FROM shared_vocabulary_authors WHERE tag_id=?', ((key,) for key in retired))
+ db.executemany('DELETE FROM shared_vocabulary_ready WHERE tag_id=?', ((key,) for key in retired))
+ self.index._check_generation()
+ return {'tags': tags, 'scope': 'project_shared_annotations', 'query': query, 'total': total,
+ 'limit': limit, 'count_unit': 'distinct_annotation_targets', 'match': 'case_insensitive_prefix', 'has_more': total > limit}
diff --git a/python/workspace_startup_registry.py b/python/workspace_startup_registry.py
index 70f15a2..914be9e 100644
--- a/python/workspace_startup_registry.py
+++ b/python/workspace_startup_registry.py
@@ -3,6 +3,8 @@
import json
import uuid
import fcntl
+import os
+import tempfile
from recording_workspace import write_json
@@ -49,3 +51,153 @@ def ordered_projects(projects, saved):
ranks = {path: index for index, path in enumerate(paths)} if isinstance(paths, list) and all(isinstance(path, str) for path in paths) else {}
return sorted(projects, key=lambda item: (ranks.get(item['path'], len(ranks)),
item['name'].casefold(), item['name'], item['path']))
+
+
+PROJECT_INDEX_FORMAT = 'rieke-local-projects'
+PROJECT_INDEX_LIMIT = 1024 * 1024
+
+
+def project_index_path():
+ """Local OS-user discovery state, independent of project and application roots."""
+ override = os.environ.get('RIEKE_PROJECT_INDEX')
+ if override:
+ path = Path(override).expanduser()
+ elif os.environ.get('RIEKE_DESKTOP_USER_STATE'):
+ path = Path(os.environ['RIEKE_DESKTOP_USER_STATE']).expanduser() / 'preferences/project-index.json'
+ else:
+ path = Path(os.environ.get('RIEKE_PREFERENCES_DIR', Path.home() / '.rieke-os')).expanduser() / 'project-index.json'
+ if not path.is_absolute():
+ raise ValueError('Local project index must use an absolute path')
+ if path.is_symlink():
+ raise ValueError('Local project index cannot be a symbolic link')
+ # macOS /tmp and /var are system aliases. Preserve the selected filename
+ # while canonicalizing its parent, rather than rejecting normal OS paths.
+ return path.parent.resolve() / path.name
+
+
+def _path_string(value):
+ if (not isinstance(value, str) or not value or len(value) > 4096 or '\0' in value
+ or not Path(value).is_absolute() or '..' in Path(value).parts
+ or str(Path(value)) != value):
+ raise ValueError('Local project references must be normalized absolute folders')
+ return value
+
+
+def _validate_project_index(value):
+ if (not isinstance(value, dict) or set(value) != {'format', 'version', 'projects', 'last_project_path', 'project_order'}
+ or value['format'] != PROJECT_INDEX_FORMAT or type(value['version']) is not int or value['version'] != 1
+ or not isinstance(value['projects'], list) or len(value['projects']) > 1000
+ or not isinstance(value['project_order'], list) or len(value['project_order']) > 1000):
+ raise ValueError('Local project index is invalid; preserve it and repair its configuration')
+ seen = set()
+ for project in value['projects']:
+ if not isinstance(project, dict) or set(project) != {'path', 'project_uuid', 'name', 'database_kind'}:
+ raise ValueError('Local project index contains an invalid project record')
+ path = _path_string(project['path'])
+ if path in seen:
+ raise ValueError('Local project index contains duplicate folder references')
+ seen.add(path)
+ if (not isinstance(project['project_uuid'], str) or str(uuid.UUID(project['project_uuid'])) != project['project_uuid']
+ or not isinstance(project['name'], str) or not 1 <= len(project['name'].strip()) <= 200
+ or not isinstance(project['database_kind'], str) or project['database_kind'] not in {'native-mysql', 'legacy-mysql'}):
+ raise ValueError('Local project index identity or display fields are invalid')
+ order = [_path_string(path) for path in value['project_order']]
+ if len(set(order)) != len(order):
+ raise ValueError('Local project order contains duplicate folders')
+ if value['last_project_path'] is not None:
+ _path_string(value['last_project_path'])
+ if value['last_project_path'] not in seen:
+ raise ValueError('Last-open project is missing from the local project index')
+ return value
+
+
+def read_project_index():
+ path = project_index_path()
+ if path.is_symlink():
+ raise ValueError('Local project index cannot be a symbolic link')
+ if not path.exists():
+ return {'format': PROJECT_INDEX_FORMAT, 'version': 1, 'projects': [],
+ 'last_project_path': None, 'project_order': []}
+ if not path.is_file() or path.stat().st_size > PROJECT_INDEX_LIMIT:
+ raise ValueError('Local project index is invalid or unexpectedly large')
+ return _validate_project_index(json.loads(path.read_text()))
+
+
+def _update_project_index(change):
+ path = project_index_path()
+ if path.is_symlink() or path.parent.is_symlink():
+ raise ValueError('Local project index cannot be a symbolic link')
+ path.parent.mkdir(parents=True, exist_ok=True)
+ lock_path = path.with_suffix('.lock')
+ if lock_path.is_symlink():
+ raise ValueError('Local project index lock cannot be a symbolic link')
+ with lock_path.open('a') as lock:
+ fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
+ value = _validate_project_index(change(read_project_index()))
+ content = json.dumps(value, sort_keys=True, indent=2, allow_nan=False).encode() + b'\n'
+ if len(content) > PROJECT_INDEX_LIMIT:
+ raise ValueError('Local project index is unexpectedly large')
+ fd, temporary = tempfile.mkstemp(prefix='.project-index-', dir=path.parent)
+ try:
+ with os.fdopen(fd, 'wb') as handle:
+ handle.write(content)
+ handle.flush()
+ os.fsync(handle.fileno())
+ os.replace(temporary, path)
+ finally:
+ Path(temporary).unlink(missing_ok=True)
+ return value
+
+
+def remember_project_path(directory, identity=None, *, set_last=False, previous_directory=None):
+ """Remember one explicitly created/opened folder; never inspect its neighbors."""
+ from workspace_projects import _project_record
+ directory = Path(directory).expanduser()
+ if not directory.is_absolute() or directory.is_symlink() or not directory.is_dir():
+ raise ValueError('Remember an existing regular project folder by its absolute path')
+ record = _project_record(directory.resolve())
+ if identity is not None and record['uuid'] != str(uuid.UUID(identity)):
+ raise ValueError('Remembered project identity disagrees with its folder')
+ entry = {'path': record['path'], 'project_uuid': record['uuid'], 'name': record['name'],
+ 'database_kind': 'native-mysql' if record.get('database_kind') == 'native-mysql' else 'legacy-mysql'}
+ previous = None
+ if previous_directory is not None:
+ previous = Path(previous_directory).expanduser()
+ if not previous.is_absolute():
+ raise ValueError('Previous project folder must be absolute')
+ previous = str(previous.resolve())
+
+ def update(value):
+ value['projects'] = [project for project in value['projects'] if project['path'] not in {entry['path'], previous}]
+ value['projects'].append(entry)
+ if previous and previous != entry['path']:
+ value['project_order'] = [entry['path'] if path == previous else path for path in value['project_order'] if path != entry['path']]
+ if set_last or (previous is not None and value['last_project_path'] == previous):
+ value['last_project_path'] = entry['path']
+ return value
+ return _update_project_index(update)
+
+
+def remember_project_paths_order(paths):
+ """Preserve global path order across preferred locations and copied UUIDs."""
+ if not isinstance(paths, list) or len(paths) > 1000:
+ raise ValueError('Project order must be a bounded folder list')
+ paths = [_path_string(path) for path in paths]
+ if len(set(paths)) != len(paths):
+ raise ValueError('Project order cannot contain duplicate folders')
+ return _update_project_index(lambda value: {**value, 'project_order': paths})
+
+
+def remember_project_result(directory, result, *, set_last=False, previous_directory=None):
+ """A failed discovery preference must not undo a completed project operation."""
+ options = {}
+ if set_last:
+ options['set_last'] = True
+ if previous_directory is not None:
+ options['previous_directory'] = previous_directory
+ try:
+ remember_project_path(directory, **options)
+ except (OSError, ValueError) as error:
+ return {**result, 'registry_warning':
+ f'Project is ready at {directory}, but its project-list entry could not be saved: {error}'}
+ return result
diff --git a/python/workspace_state_generation.py b/python/workspace_state_generation.py
new file mode 100644
index 0000000..99898a0
--- /dev/null
+++ b/python/workspace_state_generation.py
@@ -0,0 +1,479 @@
+"""Verified MySQL change authority for derived, discardable read caches.
+
+Native triggers advance scope counters in the same InnoDB transaction as source
+DML. A server-global DDL command vector detects interruptions in trigger coverage,
+including drop/recreate within the precision of information_schema.CREATED.
+Active DDL/prepared/procedure/event statements must be observable in Performance
+Schema; disabled witnesses fail closed. Coordinated privileged disabling and
+restoring those witnesses or rewriting authority rows is outside this contract.
+Unsupported adapters or unverifiable contracts always return None: callers use
+the canonical reader. This is not an authorization/security boundary against a
+privileged administrator deliberately rewriting both data and its authority.
+"""
+from __future__ import annotations
+
+from dataclasses import dataclass, replace
+import contextlib
+import contextvars
+import hashlib
+import json
+import threading
+import uuid
+
+SCHEMA='recording_workspace'
+GENERATION='app_state_generation'
+CHANGES='app_annotation_changes'
+PREFIX='rieke_state_v1_'
+OBSERVER_LOSSES=tuple('Performance_schema_'+name+'_lost' for name in (
+ 'thread_classes','thread_instances','statement_classes','nested_statement',
+ 'prepared_statements','program','locker'))
+DDL_COUNTERS=('Com_create_trigger','Com_drop_trigger','Com_create_table','Com_drop_table',
+ 'Com_alter_table','Com_rename_table','Com_truncate','Com_create_db','Com_drop_db',
+ 'Com_alter_db','Com_alter_tablespace','Com_create_event','Com_alter_event','Com_drop_event')
+DDL_EVENTS=tuple('statement/sql/'+key[4:] for key in DDL_COUNTERS)+(
+ 'statement/sql/execute_sql','statement/com/Execute','statement/sql/call_procedure',
+ 'statement/scheduler/event')
+WATCHED=('curation','shared_annotation','annotation_profile',GENERATION,CHANGES)
+
+
+def _scope_sql(alias, kind):
+ scope=f'{alias}.protocol_uuid' if kind=='protocol_curation' else f'{alias}.project_uuid'
+ return (f"INSERT INTO `{SCHEMA}`.`{GENERATION}` (project_uuid,scope_kind,scope_uuid,scope_epoch,generation) "
+ f"VALUES ({alias}.project_uuid,'{kind}',{scope},UUID(),1) "
+ "ON DUPLICATE KEY UPDATE generation=generation+1;")
+
+
+def _change_sql(alias, table, deleted):
+ kind=f'{alias}.target_kind' if table=='shared_annotation' else "'profile'"
+ target=f'{alias}.target_uuid' if table=='shared_annotation' else f'{alias}.profile_uuid'
+ return (f"INSERT INTO `{SCHEMA}`.`{CHANGES}` (project_uuid,target_kind,target_uuid,profile_uuid,last_generation,deleted) "
+ f"SELECT {alias}.project_uuid,{kind},{target},{alias}.profile_uuid,generation,{int(deleted)} "
+ f"FROM `{SCHEMA}`.`{GENERATION}` WHERE project_uuid={alias}.project_uuid "
+ f"AND scope_kind='shared_annotations' AND scope_uuid={alias}.project_uuid "
+ "ON DUPLICATE KEY UPDATE last_generation=VALUES(last_generation),deleted=VALUES(deleted);")
+
+
+def trigger_manifest():
+ result={}
+ for table,short in (('curation','curation'),('shared_annotation','annotation'),('annotation_profile','profile')):
+ kind='protocol_curation' if table=='curation' else 'shared_annotations'
+ for event,suffix in (('INSERT','ai'),('UPDATE','au'),('DELETE','ad')):
+ statements=[]
+ for alias in (('NEW',) if event=='INSERT' else ('OLD',) if event=='DELETE' else ('OLD','NEW')):
+ statements.append(_scope_sql(alias,kind))
+ if table!='curation':statements.append(_change_sql(alias,table,alias=='OLD'))
+ result[PREFIX+short+'_'+suffix]={'table':table,'event':event,'timing':'AFTER',
+ 'body':'BEGIN '+' '.join(statements)+' END'}
+ return result
+
+
+MANIFEST=trigger_manifest()
+
+
+def _normalized(value):
+ return ' '.join(str(value).split())
+
+
+@dataclass(frozen=True)
+class GenerationToken:
+ authority: str
+ project_uuid: str
+ shared_epoch: str
+ shared_generation: int
+ protocol_uuid: str | None = None
+ protocol_epoch: str | None = None
+ protocol_generation: int | None = None
+
+
+class StateGenerationAuthority:
+ """One connection's fail-closed view of a server-native authority contract."""
+ def __init__(self, connection, project_uuid):
+ self.connection=connection
+ self.project_uuid=str(uuid.UUID(str(project_uuid)))
+ self.reason='not bootstrapped'
+ self.ready=False
+ self._connection_ref=None
+ self._connection_epoch=None
+ self._read_generation=contextvars.ContextVar('rieke_native_read_generation',default=None)
+ self._response_contract=contextvars.ContextVar('rieke_native_response_contract',default=None)
+ self._verified_read_token=None
+
+ def _rows(self, sql, args=()):
+ return list(self.connection.query(sql,args,as_dict=True,reconnect=False).fetchall())
+
+ def _in_transaction(self):
+ return bool(getattr(self.connection,'in_transaction',True))
+
+ def _ddl(self):
+ names=','.join("'"+key+"'" for key in DDL_COUNTERS)
+ rows=self._rows(f'SHOW GLOBAL STATUS WHERE Variable_name IN ({names})')
+ values={row['Variable_name'].casefold():int(row['Value']) for row in rows}
+ if set(values)!={key.casefold() for key in DDL_COUNTERS}:
+ raise ValueError('server does not expose the required global DDL counters')
+ return tuple((key,values[key.casefold()]) for key in DDL_COUNTERS)
+
+ def _triggers(self):
+ return self._rows('SELECT TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,ACTION_TIMING,ACTION_STATEMENT,CREATED '
+ 'FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=%s',(SCHEMA,))
+
+ @staticmethod
+ def _verified_trigger(row):
+ expected=MANIFEST.get(row['TRIGGER_NAME'])
+ return expected is not None and all(_normalized(row[field])==_normalized(expected[key]) for field,key in
+ (('EVENT_OBJECT_TABLE','table'),('EVENT_MANIPULATION','event'),('ACTION_TIMING','timing'),('ACTION_STATEMENT','body')))
+
+ def _observer_status(self):
+ names=OBSERVER_LOSSES+('Connections','Threads_connected')
+ quoted=','.join("'"+name+"'" for name in names)
+ rows=self._rows(f'SHOW GLOBAL STATUS WHERE Variable_name IN ({quoted})')
+ values={row['Variable_name']:int(row['Value']) for row in rows}
+ if set(values)!=set(names) or any(values[name] for name in OBSERVER_LOSSES):
+ raise ValueError('Performance Schema lost a required statement or thread observer')
+ return values['Connections'],values['Threads_connected']
+
+ def _active_ddl_guard(self):
+ # Command counters advance when a DDL statement begins, even while its
+ # metadata lock is pending. An active-statement witness closes that gap.
+ # Disabled/unavailable instrumentation is never treated as an empty set.
+ population=self._observer_status()
+ consumers=self._rows("SELECT NAME,ENABLED FROM performance_schema.setup_consumers WHERE NAME IN "
+ "('global_instrumentation','thread_instrumentation','events_statements_current')")
+ if len(consumers)!=3 or any(row['ENABLED']!='YES' for row in consumers):
+ raise ValueError('DDL statement consumers are unavailable or disabled')
+ placeholders=','.join('%s' for _ in DDL_EVENTS)
+ instruments=self._rows(f'SELECT NAME,ENABLED FROM performance_schema.setup_instruments WHERE NAME IN ({placeholders})',DDL_EVENTS)
+ if len(instruments)!=len(DDL_EVENTS) or any(row['ENABLED']!='YES' for row in instruments):
+ raise ValueError('DDL statement instruments are unavailable or disabled')
+ threads=self._rows("SELECT PROCESSLIST_ID,NAME,INSTRUMENTED FROM performance_schema.threads "
+ "WHERE TYPE='FOREGROUND' AND PROCESSLIST_ID IS NOT NULL")
+ if any(row['INSTRUMENTED']!='YES' for row in threads):
+ raise ValueError('an uninstrumented SQL connection prevents DDL attestation')
+ # Loss counters can be reset by administrative FLUSH STATUS. Match the
+ # independently maintained connection population as well, fencing new
+ # connections so an omitted live thread cannot look like an empty set.
+ known={'thread/sql/one_connection','thread/sql/event_scheduler','thread/sql/compress_gtid_table'}
+ clients=sum(row['NAME']=='thread/sql/one_connection' for row in threads)
+ if any(row['NAME'] not in known for row in threads) or clients!=population[1] or self._observer_status()!=population:
+ raise ValueError('SQL connection observers changed or are incomplete')
+ if self._rows('SELECT EVENT_SCHEMA,EVENT_NAME FROM information_schema.EVENTS LIMIT 1'):
+ raise ValueError('scheduled database events are outside the native authority contract')
+ if self._rows('SELECT CHANNEL_NAME FROM performance_schema.replication_applier_configuration LIMIT 1'):
+ raise ValueError('replication appliers are outside the native authority contract')
+ active=self._rows(f'SELECT THREAD_ID,EVENT_NAME FROM performance_schema.events_statements_current '
+ f'WHERE EVENT_NAME IN ({placeholders}) AND END_EVENT_ID IS NULL LIMIT 1',DDL_EVENTS)
+ if active:raise ValueError('DDL is in progress; native cache authority is deferred')
+
+ def _verify_authority_tables(self, ddl):
+ if getattr(self,'_schema_counter',None)==ddl:return
+ expected={GENERATION:[('project_uuid','varchar(36)'),('scope_kind','varchar(32)'),
+ ('scope_uuid','varchar(36)'),('scope_epoch','char(36)'),('generation','bigint unsigned')],
+ CHANGES:[('project_uuid','varchar(36)'),('target_kind','varchar(16)'),('target_uuid','varchar(36)'),
+ ('profile_uuid','varchar(36)'),('last_generation','bigint unsigned'),('deleted','tinyint unsigned')]}
+ rows=self._rows('SELECT TABLE_NAME,COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE FROM information_schema.COLUMNS '
+ 'WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN (%s,%s) ORDER BY TABLE_NAME,ORDINAL_POSITION',
+ (SCHEMA,GENERATION,CHANGES))
+ for table,columns in expected.items():
+ actual=[(row['COLUMN_NAME'],row['COLUMN_TYPE']) for row in rows if row['TABLE_NAME']==table]
+ if actual!=columns or any(row['IS_NULLABLE']!='NO' for row in rows if row['TABLE_NAME']==table):
+ raise ValueError('authority table columns are incompatible')
+ rows=self._rows('SELECT TABLE_NAME,COLUMN_NAME FROM information_schema.STATISTICS '
+ "WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN (%s,%s) AND INDEX_NAME='PRIMARY' ORDER BY TABLE_NAME,SEQ_IN_INDEX",
+ (SCHEMA,GENERATION,CHANGES))
+ for table,keys in ((GENERATION,['project_uuid','scope_kind','scope_uuid']),
+ (CHANGES,['project_uuid','target_kind','target_uuid','profile_uuid'])):
+ if [row['COLUMN_NAME'] for row in rows if row['TABLE_NAME']==table]!=keys:
+ raise ValueError('authority primary key is incompatible')
+ self._schema_counter=ddl
+
+ def _contract(self):
+ lease=self._response_contract.get()
+ driver=getattr(self.connection,'_conn',None)
+ if (lease is not None and lease['active'] and not self._in_transaction()
+ and lease['thread']==threading.get_ident() and driver is lease['driver']
+ and getattr(driver,'_sock',None) is lease['socket']):
+ return lease['authority']
+ return self._attest_contract()
+
+ @contextlib.contextmanager
+ def response_contract(self):
+ """Attest schema once before and after a complete read response.
+
+ Scope generations still read live before/after the response. Contract
+ reuse is confined to this thread, socket and autocommit read interval;
+ the owner must discard derived work if the closing attestation fails.
+ """
+ if not self.ready or self._in_transaction() or self._response_contract.get() is not None:
+ yield
+ return
+ try:
+ authority=self._attest_contract()
+ except Exception:
+ # Preserve the ordinary fresh-token/fallback behavior when a native
+ # contract cannot be established at the opening boundary.
+ yield
+ return
+ driver=getattr(self.connection,'_conn',None)
+ lease={'active':True,'authority':authority,'driver':driver,
+ 'socket':getattr(driver,'_sock',None),'thread':threading.get_ident()}
+ reset=self._response_contract.set(lease)
+ try:
+ yield
+ finally:
+ lease['active']=False
+ self._response_contract.reset(reset)
+ try:
+ if self._attest_contract()!=authority:
+ raise ValueError('Native database contract changed while reading the response')
+ except Exception:
+ self._connection_epoch=str(uuid.uuid4())
+ self._schema_counter=None
+ self._verified_read_token=None
+ raise
+
+ def _attest_contract(self):
+ underlying=getattr(self.connection,'_conn',None)
+ if underlying is None:raise ValueError('unrecognized connection incarnation')
+ if underlying is not self._connection_ref:
+ self._connection_ref=underlying
+ self._connection_epoch=str(uuid.uuid4())
+ self._schema_counter=None
+ before=self._ddl()
+ self._active_ddl_guard()
+ self._verify_authority_tables(before)
+ server=self._rows('SELECT @@server_uuid AS server_uuid,@@version AS version,CONNECTION_ID() AS connection_id')[0]
+ # This contract is validated against the bundled MySQL 8.4 family.
+ if not str(server['version']).startswith('8.4.'):
+ raise ValueError('native generation authority requires verified MySQL 8.4')
+ optional_lookup=('app_shared_tag_lookup','app_shared_tag_lookup_checkpoint',
+ 'app_shared_tag_dictionary','app_shared_tag_authors')
+ names=','.join('%s' for _ in (*WATCHED,*optional_lookup))
+ rows=self._rows(f'SELECT NAME,TABLE_ID FROM information_schema.INNODB_TABLES WHERE NAME IN ({names})',
+ tuple(SCHEMA+'/'+table for table in (*WATCHED,*optional_lookup)))
+ incarnations={row['NAME']:int(row['TABLE_ID']) for row in rows}
+ if not {SCHEMA+'/'+table for table in WATCHED} <= set(incarnations):
+ raise ValueError('watched InnoDB table incarnation is unavailable')
+ triggers=self._triggers()
+ known={row['TRIGGER_NAME']:row for row in triggers if row['TRIGGER_NAME'] in MANIFEST}
+ if set(known)!=set(MANIFEST) or not all(self._verified_trigger(row) for row in known.values()):
+ raise ValueError('native generation trigger contract is incomplete or changed')
+ # Other application triggers may add effects but cannot silently suppress
+ # the AFTER triggers. Unexpected app-owned names are incompatible.
+ if any(row['TRIGGER_NAME'].startswith(PREFIX) and row['TRIGGER_NAME'] not in MANIFEST for row in triggers):
+ raise ValueError('unknown application generation trigger')
+ self._active_ddl_guard()
+ after=self._ddl()
+ if before!=after:raise ValueError('DDL changed while verifying native authority')
+ payload={'server':server,'connection_epoch':self._connection_epoch,'tables':incarnations,
+ 'ddl':after,'triggers':known,
+ 'lookup_triggers':{row['TRIGGER_NAME']:row for row in triggers
+ if row['TRIGGER_NAME'].startswith('rieke_tag_lookup_v1_')}}
+ return hashlib.sha256(json.dumps(payload,sort_keys=True,default=str).encode()).hexdigest()
+
+ def bootstrap(self):
+ if self._in_transaction():
+ self.reason='bootstrap deferred during an active transaction';return self
+ locked=False
+ try:
+ # Avoid attempting DDL against test doubles or other SQL adapters.
+ if getattr(self.connection,'_conn',None) is None:
+ raise ValueError('native connection required')
+ locked=self._rows("SELECT GET_LOCK('rieke_state_generation_v1',10) AS acquired")[0]['acquired']==1
+ if not locked:raise ValueError('another authority bootstrap is running')
+ existing_tables={row['TABLE_NAME'] for row in self._rows(
+ 'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=%s AND TABLE_NAME IN (%s,%s)',
+ (SCHEMA,GENERATION,CHANGES))}
+ if GENERATION not in existing_tables:
+ self.connection.query(f'''CREATE TABLE IF NOT EXISTS `{SCHEMA}`.`{GENERATION}` (
+ project_uuid varchar(36) NOT NULL,scope_kind varchar(32) NOT NULL,scope_uuid varchar(36) NOT NULL,
+ scope_epoch char(36) NOT NULL,generation bigint unsigned NOT NULL,
+ PRIMARY KEY(project_uuid,scope_kind,scope_uuid)) ENGINE=InnoDB''',reconnect=False)
+ if CHANGES not in existing_tables:
+ self.connection.query(f'''CREATE TABLE IF NOT EXISTS `{SCHEMA}`.`{CHANGES}` (
+ project_uuid varchar(36) NOT NULL,target_kind varchar(16) NOT NULL,target_uuid varchar(36) NOT NULL,
+ profile_uuid varchar(36) NOT NULL,last_generation bigint unsigned NOT NULL,deleted tinyint unsigned NOT NULL,
+ PRIMARY KEY(project_uuid,target_kind,target_uuid,profile_uuid),KEY changed(project_uuid,last_generation)) ENGINE=InnoDB''',reconnect=False)
+ present={row['TRIGGER_NAME']:row for row in self._triggers()}
+ for name,expected in MANIFEST.items():
+ if name in present:
+ if not self._verified_trigger(present[name]):raise ValueError('existing authority trigger differs from expected contract')
+ else:
+ self.connection.query(f"CREATE TRIGGER `{SCHEMA}`.`{name}` AFTER {expected['event']} ON "
+ f"`{SCHEMA}`.`{expected['table']}` FOR EACH ROW {expected['body']}",reconnect=False)
+ self._contract()
+ self.ready=True;self.reason=None
+ except Exception as error:
+ self.ready=False;self.reason=str(error)
+ finally:
+ if locked:
+ try:self.connection.query("SELECT RELEASE_LOCK('rieke_state_generation_v1')",reconnect=False)
+ except Exception:pass
+ return self
+
+ def _scope(self, kind, identity):
+ rows=self._rows(f'SELECT scope_epoch,generation FROM `{SCHEMA}`.`{GENERATION}` '
+ 'WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s',(self.project_uuid,kind,identity))
+ if not rows:
+ # First access or deleted counter: fresh UUID forbids collision with
+ # previous cache tokens. Only the derived authority row is created.
+ self.connection.query(f'INSERT IGNORE INTO `{SCHEMA}`.`{GENERATION}` '
+ '(project_uuid,scope_kind,scope_uuid,scope_epoch,generation) VALUES (%s,%s,%s,%s,0)',
+ (self.project_uuid,kind,identity,str(uuid.uuid4())),reconnect=False)
+ rows=self._rows(f'SELECT scope_epoch,generation FROM `{SCHEMA}`.`{GENERATION}` '
+ 'WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s',(self.project_uuid,kind,identity))
+ if len(rows)!=1:raise ValueError('scope generation unavailable')
+ return str(uuid.UUID(rows[0]['scope_epoch'])),int(rows[0]['generation'])
+
+ @contextlib.contextmanager
+ def read_scope(self, expected):
+ """Reuse one verified generation only inside an externally fenced read.
+
+ The owner must freshly attest after leaving this scope and discard all
+ derived work on failure. This is never a transaction/mutation authority.
+ Context-local leases cannot escape their lifetime or cross threads.
+ """
+ if (type(expected) is not GenerationToken or expected.project_uuid!=self.project_uuid
+ or expected!=self._verified_read_token):
+ raise ValueError('Read scope belongs to a different native authority')
+ previous=self._read_generation.get()
+ if previous is not None and previous['active']:
+ raise ValueError('Native read scopes cannot be nested')
+ lease={'generation':expected,'connection':getattr(self.connection,'_conn',None),
+ 'thread':threading.get_ident(),'active':True}
+ reset=self._read_generation.set(lease)
+ try:yield
+ finally:
+ lease['active']=False
+ self._read_generation.reset(reset)
+
+ def token(self, protocol_uuid=None):
+ lease=self._read_generation.get()
+ if (lease is not None and lease['active'] and self.ready and not self._in_transaction()
+ and lease['thread']==threading.get_ident()
+ and getattr(self.connection,'_conn',None) is lease['connection']):
+ expected=lease['generation']
+ protocol=str(uuid.UUID(str(protocol_uuid))) if protocol_uuid is not None else None
+ if protocol is None:
+ return replace(expected,protocol_uuid=None,protocol_epoch=None,protocol_generation=None)
+ if protocol==expected.protocol_uuid:return expected
+ return self._fresh_token(protocol_uuid)
+
+ def _fresh_token(self, protocol_uuid=None):
+ self._verified_read_token=None
+ if not self.ready:return None
+ if self._in_transaction():
+ self.reason='generation reads require an autocommit snapshot';return None
+ try:
+ authority=self._contract()
+ shared=self._scope('shared_annotations',self.project_uuid)
+ protocol=str(uuid.UUID(str(protocol_uuid))) if protocol_uuid is not None else None
+ current=self._scope('protocol_curation',protocol) if protocol else (None,None)
+ # Fence all metadata and scope queries against concurrent DDL.
+ if authority!=self._contract():raise ValueError('authority changed while reading scope generations')
+ self.reason=None
+ self._verified_read_token=GenerationToken(authority,self.project_uuid,*shared,protocol,*current)
+ return self._verified_read_token
+ except Exception as error:
+ # A failed witness breaks continuity even if a later read sees
+ # coincident counters. Never reuse the previous private authority.
+ self._connection_epoch=str(uuid.uuid4())
+ self._schema_counter=None
+ self.reason=str(error);return None
+
+ def assert_current_locked(self, expected):
+ """Lock scope rows and reject a stale mutation inside its transaction.
+
+ Trigger writers acquire these same rows, closing the ordinary external
+ DML race between preflight and the saved decisions.
+ """
+ from workspace_curation import RevisionConflict
+ if not self.ready or not self._in_transaction():raise RevisionConflict({'generation':'unavailable'})
+ try:
+ # Short transaction-only metadata locks prevent trigger DDL during
+ # the mutation; ordinary source DML remains governed by scope locks.
+ for table in WATCHED:self._rows(f'SELECT 1 FROM `{SCHEMA}`.`{table}` LIMIT 0')
+ authority=self._contract()
+ scopes=[('shared_annotations',self.project_uuid)]
+ if expected.protocol_uuid:scopes.append(('protocol_curation',expected.protocol_uuid))
+ found=[]
+ for kind,identity in scopes:
+ rows=self._rows(f'SELECT scope_epoch,generation FROM `{SCHEMA}`.`{GENERATION}` '
+ 'WHERE project_uuid=%s AND scope_kind=%s AND scope_uuid=%s FOR UPDATE',
+ (self.project_uuid,kind,identity))
+ if len(rows)!=1:raise ValueError('scope generation missing')
+ found.append((rows[0]['scope_epoch'],int(rows[0]['generation'])))
+ current=GenerationToken(authority,self.project_uuid,*found[0],expected.protocol_uuid,
+ *(found[1] if len(found)>1 else (None,None)))
+ if current!=expected or self._contract()!=authority:raise ValueError('generation changed')
+ except Exception as error:
+ raise RevisionConflict({'generation':str(error)}) from error
+
+ def shared_changes(self, after_token, *, limit=10000):
+ current=self.token()
+ if current is None or after_token is None or (current.authority,current.shared_epoch)!=(after_token.authority,after_token.shared_epoch):
+ return None
+ rows=self._rows(f'SELECT target_kind,target_uuid,profile_uuid,last_generation,deleted FROM `{SCHEMA}`.`{CHANGES}` '
+ 'WHERE project_uuid=%s AND last_generation>%s ORDER BY last_generation LIMIT %s',
+ (self.project_uuid,after_token.shared_generation,limit+1))
+ if len(rows)>limit or self.token()!=current:return None
+ return rows,current
+
+
+def bootstrap(connection,project_uuid):
+ return StateGenerationAuthority(connection,project_uuid).bootstrap()
+
+
+def verify_export_triggers(connection, schemas):
+ """Classify dump triggers without ever omitting an unknown SQL behavior.
+
+ ``connection.query`` must return dictionary rows. The transfer layer adapts
+ its PyMySQL connection to this read-only interface. A DDL witness fences the
+ inventory and lets a caller reject changes across the logical dump.
+ """
+ reader = StateGenerationAuthority(connection, '00000000-0000-0000-0000-000000000000')
+ try:
+ before = reader._ddl()
+ except Exception:
+ before = None
+ rows = []
+ for schema in schemas:
+ rows.extend(connection.query(
+ 'SELECT TRIGGER_SCHEMA,TRIGGER_NAME,EVENT_MANIPULATION,EVENT_OBJECT_TABLE,'
+ 'ACTION_TIMING,ACTION_STATEMENT,CREATED FROM information_schema.TRIGGERS '
+ 'WHERE TRIGGER_SCHEMA=%s', (schema,), as_dict=True).fetchall())
+ rows.sort(key=lambda row: (row['TRIGGER_SCHEMA'], row['TRIGGER_NAME']))
+ recovery_prefix = 'rieke_recovery_v1_'
+ from workspace_native_tag_lookup import PREFIX as lookup_prefix, TRIGGER_MANIFEST
+ expected = {**MANIFEST, **TRIGGER_MANIFEST}
+ if any(row['TRIGGER_SCHEMA'] == SCHEMA and row['TRIGGER_NAME'].startswith(recovery_prefix) for row in rows):
+ try:
+ from workspace_recovery_generation import recovery_trigger_manifest
+ expected.update(recovery_trigger_manifest(connection))
+ except Exception:
+ # A schema/observer mismatch cannot authorize stripping a trigger.
+ pass
+ try:
+ after = reader._ddl()
+ except Exception:
+ after = None
+ if before != after:
+ raise ValueError('Database trigger coverage changed while preparing its backup')
+ managed = any(row['TRIGGER_SCHEMA'] == SCHEMA and row['TRIGGER_NAME'].startswith((PREFIX, recovery_prefix, lookup_prefix))
+ for row in rows)
+ known = [row for row in rows if row['TRIGGER_SCHEMA'] == SCHEMA
+ and row['TRIGGER_NAME'] in expected
+ and all(_normalized(row[field]) == _normalized(expected[row['TRIGGER_NAME']][key])
+ for field, key in (('EVENT_OBJECT_TABLE', 'table'), ('EVENT_MANIPULATION', 'event'),
+ ('ACTION_TIMING', 'timing'), ('ACTION_STATEMENT', 'body')))]
+ # A partial known installation is disposable too, but every present trigger
+ # must be verified before --skip-triggers is safe. Foreign triggers remain
+ # part of the original dump path or cause a mixed-installation refusal.
+ safe = not rows or (len(known) == len(rows) and after is not None)
+ reason = None
+ if managed and not safe:
+ reason = 'Project has unverified or custom database triggers; they cannot be omitted from a portable backup'
+ fingerprint = hashlib.sha256(json.dumps({'triggers': rows, 'ddl': after},
+ sort_keys=True, default=str).encode()).hexdigest()
+ return {'safe_to_omit': safe, 'managed_present': managed, 'reason': reason,
+ 'trigger_count': len(rows), 'known_trigger_count': len(known),
+ 'trigger_names': [row['TRIGGER_NAME'] for row in rows],
+ 'contract_fingerprint': fingerprint}
diff --git a/python/workspace_state_snapshot.py b/python/workspace_state_snapshot.py
index e340b3f..73770d8 100644
--- a/python/workspace_state_snapshot.py
+++ b/python/workspace_state_snapshot.py
@@ -1,16 +1,21 @@
-"""Recoverable current app state; no query results, waveforms, or action replay.
+"""Recoverable current app state; no waveforms or action replay.
-The live JSON is replaced atomically after successful saves. A standalone SQLite
-snapshot is sealed once per active UTC day. Restore is an explicit offline tool;
-source imports and H5 files remain separate and must match the snapshot.
+Verified changes update a compact SQLite mirror before save acknowledgment; a
+small JSON pointer locates it. Full capture remains the fallback and old JSON /
+SQLite snapshots remain readable. Restore is an explicit offline tool; sources
+remain separate and must match. Frozen selections keep their explicit members.
"""
from __future__ import annotations
import argparse
+from contextlib import closing
import fcntl
import datetime as dt
import hashlib
import json
+import logging
import os
+import secrets
+import stat
from pathlib import Path
import sqlite3
import tempfile
@@ -31,7 +36,7 @@ def serialized(value):
return json.dumps(value, default=encode, sort_keys=True, separators=(',', ':'), allow_nan=False)
-def capture(project_dir, connection):
+def capture(project_dir, connection, *, include_tables=True, ordered=True):
root = Path(project_dir).resolve()
project = json.loads((root/'project.json').read_text())
identity = project['project_uuid']
@@ -43,14 +48,31 @@ def capture(project_dir, connection):
existing = {row[0] for row in connection.query(
"SELECT table_name FROM information_schema.tables WHERE table_schema='recording_workspace'").fetchall()}
tables = {table: [] for table in TABLES}
- for table in TABLES:
+ for table in TABLES if include_tables else ():
if table not in existing:
continue
- rows = connection.query(f'SELECT * FROM recording_workspace.`{table}` WHERE project_uuid=%s',
- args=(identity,), as_dict=True).fetchall()
+ restriction, arguments = '', (identity,)
+ if table == 'search_preset_version':
+ if 'search_preset' not in existing:
+ continue
+ restriction = (' AND EXISTS (SELECT 1 FROM recording_workspace.search_preset AS current '
+ 'WHERE current.project_uuid=search_preset_version.project_uuid '
+ 'AND current.preset_uuid=search_preset_version.preset_uuid '
+ 'AND current.version=search_preset_version.version)')
+ elif table == 'explorer_revision':
+ revisions = {row['revision_uuid'] for row in tables.get('protocol_binding', [])}
+ revisions.update(value.get('initial_revision_uuid') for value in protocols.values() if isinstance(value, dict))
+ revisions.discard(None)
+ if not revisions:
+ continue
+ revision_ids = sorted(revisions)
+ restriction = ' AND revision_uuid IN (' + ','.join(['%s'] * len(revision_ids)) + ')'
+ arguments += tuple(revision_ids)
+ rows = connection.query(f'SELECT * FROM recording_workspace.`{table}` WHERE project_uuid=%s' + restriction,
+ args=arguments, as_dict=True).fetchall()
json_fields = {row[0] for row in connection.query(
f'SHOW COLUMNS FROM recording_workspace.`{table}`').fetchall() if row[1] == 'json'}
- tables[table] = [{key:json.loads(value) if key in json_fields and isinstance(value, (str,bytes)) else value
+ tables[table] = [{key:_column_value(value, key in json_fields)
for key,value in dict(row).items()} for row in rows]
# Only saved current query versions and active membership snapshots belong
# in recovery state. Historical recipes/exports stay immutable in live SQL.
@@ -62,8 +84,9 @@ def capture(project_dir, connection):
revisions.update(value.get('initial_revision_uuid') for value in protocols.values() if isinstance(value, dict))
if 'explorer_revision' in tables:
tables['explorer_revision'] = [row for row in tables['explorer_revision'] if row['revision_uuid'] in revisions]
- for table, rows in tables.items():
- tables[table] = sorted(rows, key=serialized)
+ if ordered:
+ for table, rows in tables.items():
+ tables[table] = sorted(rows, key=serialized)
sources = []
source_references = []
if 'source' in existing:
@@ -76,8 +99,15 @@ def capture(project_dir, connection):
source_references.sort(key=lambda row:row['source_sha256'])
state = dict(format=FORMAT, version=1, project=project, source_sha256s=sources,
source_references=source_references, protocols=protocols, tables=tables)
- # Normalize datetime values before hashing and SQL restore.
- return json.loads(serialized(state))
+ return state
+
+
+def _column_value(value, json_column=False):
+ if json_column and isinstance(value, (str, bytes)):
+ return json.loads(value)
+ if isinstance(value, (dt.datetime, dt.date)):
+ return value.isoformat()
+ return value
@@ -147,13 +177,36 @@ def expand_queries(state, service):
if key in remap:definition['initial_revision_uuid']=remap[key]
-def atomic_write(path, data):
+def atomic_write(path, data, *, preserve_permissions=False):
if path.is_symlink() or path.parent.is_symlink():
raise ValueError('App state must use regular project files')
- fd, temporary = tempfile.mkstemp(dir=path.parent, prefix='.app-state-')
+ mode = None
+ if preserve_permissions:
+ try:
+ mode = stat.S_IMODE(path.stat().st_mode)
+ except FileNotFoundError:
+ pass
+ # General project JSON keeps normal creation permissions. Let open()
+ # apply the caller's umask atomically; never change the process umask in
+ # a threaded server. Snapshot callers retain mkstemp's private 0600.
+ for _ in range(100):
+ temporary = path.parent / ('.app-state-' + secrets.token_hex(16))
+ try:
+ fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL,
+ 0o600 if mode is not None else 0o666)
+ break
+ except FileExistsError:
+ continue
+ else:
+ raise FileExistsError('Could not allocate an exclusive project JSON temporary file')
+ else:
+ fd, temporary = tempfile.mkstemp(dir=path.parent, prefix='.app-state-')
try:
with os.fdopen(fd, 'wb') as handle:
- handle.write(data); handle.flush(); os.fsync(handle.fileno())
+ handle.write(data); handle.flush()
+ if mode is not None:
+ os.fchmod(handle.fileno(), mode)
+ os.fsync(handle.fileno())
os.replace(temporary, path)
directory_fd = os.open(path.parent, os.O_RDONLY)
try:os.fsync(directory_fd)
@@ -172,56 +225,112 @@ def save(project_dir, connection, *, day=None, service=None):
def _save(project_dir, connection, *, day=None, service=None):
+ # A verified native change feed makes ordinary tag saves proportional to
+ # changed rows. The full capture remains the correctness fallback when the
+ # authority cannot prove coverage, including unsupported SQL providers.
+ from workspace_recovery_store import inspect as inspect_recovery, write as write_recovery
root = Path(project_dir).resolve()
if connection.in_transaction:
raise ValueError('Snapshot must follow the completed app transaction')
- with connection.transaction:
- state = capture(root, connection)
+ current = inspect_recovery(root)
+ identity = json.loads((root / 'project.json').read_text())['project_uuid']
+ tracker = getattr(service, '_recovery_tracker', None) if service is not None else None
+ if (tracker is None or tracker.connection is not connection
+ or tracker.project_uuid != identity):
+ from workspace_recovery_generation import RecoveryTracker
+ tracker = RecoveryTracker(connection, identity)
if service is not None:
- compact_queries(state, service)
- if service is None and state['tables'].get('explorer_revision'):
- service = query_service(root)
- # Re-read after lazy service initialization: predicate/tag evaluation
- # and the captured current state must share the same SQL snapshot.
- with connection.transaction:
- state = capture(root, connection)
- compact_queries(state, service)
- data = serialized(state).encode()
- target = root/'app-state.json'
- changed = not target.exists() or target.read_bytes() != data
- if changed:
- atomic_write(target, data)
- folder = root/'backups/app-state'
- if (root/'backups').is_symlink() or folder.is_symlink():
- raise ValueError('App state backups cannot be symbolic links')
- folder.mkdir(parents=True, exist_ok=True)
- date = day or dt.datetime.now(dt.timezone.utc).date().isoformat()
- dt.date.fromisoformat(date)
- daily = folder/(date+'.sqlite')
- if not daily.exists():
- fd, temporary = tempfile.mkstemp(dir=folder, prefix='.snapshot-')
- os.close(fd)
- try:
- with sqlite3.connect(temporary) as database:
- database.execute('CREATE TABLE snapshot (format TEXT, version INTEGER, sha256 TEXT, document TEXT)')
- database.execute('INSERT INTO snapshot VALUES (?,?,?,?)',
- (FORMAT,1,hashlib.sha256(data).hexdigest(),data.decode()))
- with open(temporary,'rb') as handle:
- os.fsync(handle.fileno())
- os.chmod(temporary, 0o400)
+ service._recovery_tracker = tracker
+ if tracker is not None:
+ prior = current['header']['watermark'] if current is not None else None
+ result = None
+ with tracker.capture(prior) as plan:
+ if plan is not None:
+ state = capture(root, connection, include_tables=False)
+ keys = {table: list(names) for table, names in plan.primary_keys.items() if table in TABLES}
+ columns = {table: list(names) for table, names in plan.columns.items() if table in TABLES}
+ # These tables change which immutable recipes/versions belong
+ # in recovery. Reconcile them with a full pruned capture rather
+ # than adding unreferenced history to the current-state mirror.
+ full_tables = {'source', 'search_preset', 'search_preset_version',
+ 'protocol_binding', 'explorer_revision', 'dataset_revision'}
+ incremental = (current is not None and plan.complete
+ and bool(current['header'].get('table_seals'))
+ and keys == current['header']['keys'] and columns == current['header']['columns']
+ and state == current['header']['state']
+ and not any(plan.keys.get(table) for table in full_tables))
+ changes = deleted = None
+ if incremental:
+ changes, deleted = recovery_after_images(connection, identity, plan)
+ else:
+ state = capture(root, connection, ordered=False)
+ expected_header = {**state, 'tables': {table: [] for table in TABLES}}
+ if capture(root, connection, include_tables=False) != expected_header:
+ raise ValueError('Project or protocol files changed during recovery capture; retry the save')
+ plan.verify()
+ result = write_recovery(root, state=state, keys=keys, columns=columns,
+ watermark=plan.watermark, changes=changes, deleted=deleted, day=day)
+ if result is not None:
try:
- os.link(temporary, daily) # Never replace an existing daily snapshot.
- except FileExistsError:
- pass
- finally:
- Path(temporary).unlink(missing_ok=True)
- return {'path':str(target), 'bytes':len(data), 'changed':changed, 'daily_backup':str(daily)}
+ tracker.prune(plan.watermark)
+ except Exception:
+ # A durable mirror is already committed. Retaining extra feed
+ # rows is safe; losing them before that commit would not be.
+ logging.getLogger(__name__).exception('Recovery is durable but change-feed cleanup failed')
+ result['feed_cleanup_pending'] = True
+ return result
+ # Providers without a verified change authority still get compact current
+ # state, but must read canonical rows to prove that backup is complete.
+ with connection.transaction:
+ state = capture(root, connection, ordered=False)
+ keys, columns = recovery_schema(connection)
+ return write_recovery(root, state=state, keys=keys, columns=columns, watermark=None, day=day)
+
+
+def recovery_schema(connection):
+ existing = {row[0] for row in connection.query(
+ "SELECT table_name FROM information_schema.tables WHERE table_schema='recording_workspace'").fetchall()}
+ keys, columns = {}, {}
+ for table in TABLES:
+ if table not in existing:
+ continue
+ heading = connection.query(f'SHOW COLUMNS FROM recording_workspace.`{table}`').fetchall()
+ columns[table] = [row[0] for row in heading]
+ primary = connection.query(f'SHOW INDEX FROM recording_workspace.`{table}` WHERE Key_name=\'PRIMARY\'', as_dict=True).fetchall()
+ keys[table] = [row['Column_name'] for row in sorted(primary, key=lambda row: row['Seq_in_index'])]
+ if not keys[table]:
+ raise ValueError('Recovery requires an exact primary key for ' + table)
+ return keys, columns
+
+
+def recovery_after_images(connection, identity, plan):
+ """Fetch only changed complete keys within the authority's SQL snapshot."""
+ changes, deleted = {}, {}
+ for table, identities in plan.keys.items():
+ if table not in TABLES or not identities:
+ continue
+ names = plan.primary_keys[table]
+ deleted[table] = [list(key) for key in identities]
+ changes[table] = []
+ for offset in range(0, len(identities), 200):
+ batch = identities[offset:offset+200]
+ predicate = ' OR '.join('(' + ' AND '.join('`' + name + '`=%s' for name in names) + ')' for _ in batch)
+ args = (identity, *(value for key in batch for value in key))
+ rows = connection.query(f'SELECT * FROM recording_workspace.`{table}` '
+ f'WHERE project_uuid=%s AND ({predicate})', args=args, as_dict=True).fetchall()
+ for row in rows:
+ changes[table].append({key: _column_value(value, key in plan.json_columns[table])
+ for key, value in dict(row).items()})
+ return changes, deleted
def load(path):
path = Path(path)
if path.suffix == '.sqlite':
- with sqlite3.connect(path.resolve().as_uri()+'?mode=ro', uri=True) as database:
+ with closing(sqlite3.connect(path.resolve().as_uri()+'?mode=ro', uri=True)) as database:
+ if database.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='recovery_header'").fetchone():
+ from workspace_recovery_store import load_database
+ return load_database(path)
rows = database.execute('SELECT format,version,sha256,document FROM snapshot').fetchall()
if len(rows) != 1:
raise ValueError('Expected one app state snapshot')
@@ -231,6 +340,9 @@ def load(path):
state = json.loads(document)
else:
state = json.loads(path.read_text())
+ if state.get('format') == FORMAT and state.get('version') == 2:
+ from workspace_recovery_store import load_database, pointer_path
+ return load_database(pointer_path(path.parent, state), expected=state)
if state.get('format') != FORMAT or state.get('version') != 1 or set(state.get('tables', {}))-set(TABLES):
raise ValueError('Unsupported app state snapshot')
return state
diff --git a/python/workspace_tag_predicates.py b/python/workspace_tag_predicates.py
index 0da5c1a..2b35636 100644
--- a/python/workspace_tag_predicates.py
+++ b/python/workspace_tag_predicates.py
@@ -146,9 +146,15 @@ def snapshot(self,fields):
def catalog_fields(self,ids):
definitions=self.definitions()
if not definitions: return []
- states,_=self.snapshot({field['id'] for field in definitions})
+ all_fields={field['id'] for field in definitions};indexed={}
+ shared=getattr(self.service,'shared_annotations',None)
+ if shared is not None and hasattr(shared,'catalog_fields'):
+ indexed=shared.catalog_fields([self.service.rows[key] for key in ids],all_fields & SHARED_FIELDS.keys()) or {}
+ states,_=self.snapshot(all_fields-indexed.keys())
fields=[]
for definition in definitions:
+ if definition['id'] in indexed:
+ fields.append({**definition,**indexed[definition['id']]});continue
key=definition['id'];values={identity:{key:states[key].get(identity,[])} for identity in ids}
distinct={value_key(current[key]):current[key] for current in values.values()}
examples=[value_label(value)[:160] for value in sorted(distinct.values(),key=value_order)[:5]]
diff --git a/python/workspace_tree_pages.py b/python/workspace_tree_pages.py
index 89af097..fc6cd79 100644
--- a/python/workspace_tree_pages.py
+++ b/python/workspace_tree_pages.py
@@ -6,7 +6,9 @@
from __future__ import annotations
import re
+import sys
import uuid
+from itertools import chain
from workspace_recipes import checksum, parse_splits
from workspace_tree import (field_value_order, joint_definition, joint_value,
@@ -16,6 +18,50 @@
from workspace_service import validate_filters
+TREE_SCOPE_BYTE_BUDGET = 64 * 1024 * 1024
+TREE_SCOPE_CACHE_OVERHEAD = 16 * 1024 # Reserve for eight keys/entries and generation bookkeeping.
+
+
+def _retained_scope_bytes(result, base_rows, limit):
+ """Conservative incremental Python-object estimate, not an RSS ceiling.
+
+ Base rows remain owned by the service independently of this cache. Exclude
+ only those exact objects (and exact shared values in decorated row copies),
+ while counting the copies, curation state, projections and containers.
+ Equal-but-separately-decoded values are distinct allocations and count.
+ Walk lazily by identity and stop as soon as the admission budget is exceeded.
+ """
+ seen = set()
+ for row in result[0]:
+ original = base_rows.get(row['epoch_uuid'])
+ if row is original:
+ seen.add(id(row))
+ elif original is not None:
+ for key, value in row.items():
+ if key in original and value is original[key]:
+ seen.add(id(value))
+ total = 0
+ pending = [iter((result,))]
+ while pending:
+ try:
+ value = next(pending[-1])
+ except StopIteration:
+ pending.pop()
+ continue
+ identity = id(value)
+ if identity in seen:
+ continue
+ seen.add(identity)
+ total += sys.getsizeof(value)
+ if total > limit:
+ return total
+ if isinstance(value, dict):
+ pending.append(chain(value.keys(), value.values()))
+ elif isinstance(value, (list, tuple, set, frozenset)):
+ pending.append(iter(value))
+ return total
+
+
class StaleTreePage(ValueError):
pass
@@ -65,6 +111,70 @@ def __init__(self, service):
self.service = service
def _scope(self, body):
+ """Reuse navigation projections only for a checked immutable index.
+
+ Annotation membership is live and intentionally bypasses this cache.
+ Source eligibility and frozen protocol binding are part of every key.
+ Keeping this on the service also shares it with matching-epoch reads.
+ """
+ service = self.service
+ service._ready()
+ from workspace_disk_index import DiskMetadataIndex
+ from workspace_tag_predicates import referenced_fields
+ index = getattr(service, 'disk_index', None)
+ filters = validate_filters(body.get('filters'))
+ if (not isinstance(index, DiskMetadataIndex)
+ or any(field in filters for field in ('tag', 'tagged', 'tag_predicate'))
+ or referenced_fields(body.get('predicate', {'all': []}))):
+ return self._build_scope(body)
+ index._check() # A cache hit must still refuse changed/corrupt index bytes.
+ protocol = body.get('protocol_uuid')
+ if protocol is not None:
+ protocol = _uuid(protocol)
+ if 'predicate' in body:
+ raise ValueError('Protocol tree membership cannot be replaced by a source predicate')
+ binding = service.binding(protocol) if protocol else None
+ generation = (id(index), index.generation, id(service.rows),
+ id(service._fingerprints), id(service.protocols))
+ base_bytes = sys.getsizeof(service._fingerprints) + TREE_SCOPE_CACHE_OVERHEAD
+ if base_bytes >= TREE_SCOPE_BYTE_BUDGET:
+ service._tree_page_scope_cache = None
+ return self._build_scope(body)
+ cached = getattr(service, '_tree_page_scope_cache', None)
+ if cached is None or cached[0] != generation or cached[2] != service._fingerprints:
+ # The published fingerprint dictionary is also used by reconciliation
+ # and test adapters. Detect in-place edits, not only refresh swaps.
+ # Equality is a linear lightweight check; it avoids sorting/encoding
+ # the full membership for every navigation request.
+ cached = service._tree_page_scope_cache = (generation, {}, dict(service._fingerprints))
+ cache = cached[1]
+ key = checksum({'protocol': protocol, 'binding': binding and {
+ 'version': binding['version'], 'revision_uuid': binding.get('revision_uuid')},
+ 'source_scope': service.source_scope()['revision'] if protocol is None else None,
+ 'predicate': body.get('predicate'), 'filters': filters,
+ 'splits': body.get('splits', 'date,cell,block'),
+ 'depth': len(body.get('path', [])), 'anchor': bool(body.get('anchor_uuid'))})
+ if key in cache:
+ entry = cache.pop(key)
+ cache[key] = entry
+ return entry[0]
+ result = self._build_scope(body)
+ rows, _, values, _, _, _ = result
+ # Bound retained row references plus projected values across all scopes.
+ # An oversize projection may serve this request but is never retained.
+ weight = len(rows) + sum(len(value) for value in values.values())
+ if weight <= 2_000_000:
+ byte_limit = TREE_SCOPE_BYTE_BUDGET - base_bytes
+ retained_bytes = _retained_scope_bytes(result, service.rows, byte_limit)
+ if retained_bytes > byte_limit:
+ return result
+ cache[key] = (result, weight, retained_bytes)
+ while (len(cache) > 8 or sum(entry[1] for entry in cache.values()) > 2_000_000
+ or sum(entry[2] for entry in cache.values()) > byte_limit):
+ cache.pop(next(iter(cache)))
+ return result
+
+ def _build_scope(self, body):
service = self.service
service._ready()
protocol = body.get('protocol_uuid')
diff --git a/python/workspace_updates.py b/python/workspace_updates.py
index 482e80a..e1202c6 100644
--- a/python/workspace_updates.py
+++ b/python/workspace_updates.py
@@ -30,7 +30,7 @@
from urllib.request import Request, urlopen
ROOT = Path(__file__).resolve().parents[1]
-REPOSITORY = 'maxwellsdm1867/epicTreeGUI'
+REPOSITORY = 'maxwellsdm1867/Rieke-OS'
API = f'https://api.github.com/repos/{REPOSITORY}/releases/latest'
TRUST_KEY = 'release-signing-public.pem'
MANIFEST_ASSET = 'rieke-release-manifest.json'
@@ -79,10 +79,21 @@ def installation_status(root=ROOT):
metadata = release_metadata(root)
trusted = (Path(root) / TRUST_KEY).is_file()
installation = managed_installation(root)
+ staged = None
+ if installation:
+ try:
+ candidate = _read(installation / 'staged.json')['version']
+ _version(candidate)
+ ready = _read(installation / 'releases' / candidate / '.release-ready.json')
+ if ready.get('version') == candidate and _version(candidate) > _version(metadata['version']):
+ staged = candidate
+ except (OSError, ValueError, KeyError, TypeError):
+ pass
return {'installed': metadata['version'], 'state': 'not_checked', 'available': None,
'release_url': None, 'checked_at': None, 'can_install': False,
'installation': str(installation) if installation else None,
'can_stage': bool(installation) and trusted and bool(shutil.which('openssl')),
+ 'staged_version': staged,
'message': ('Check for an official release.' if trusted else
'Release checking is available. Installation awaits a trusted signed release and managed launcher.')}
@@ -124,7 +135,7 @@ def check_for_updates(root=ROOT, *, force=False):
with _cache_lock:
cached = _cache.get(str(root))
if not force and cached and time.monotonic() - cached[0] < 900:
- return dict(cached[1])
+ return {**cached[1], 'staged_version': installation_status(root)['staged_version']}
result = installation_status(root)
result['checked_at'] = datetime.datetime.now(datetime.timezone.utc).isoformat()
try:
@@ -150,6 +161,14 @@ def check_for_updates(root=ROOT, *, force=False):
else f'The release server returned HTTP {error.code}. Try again later.'))
except (OSError, URLError, ValueError, TypeError) as error:
result.update(state='error', message='Could not verify release availability. Your current app remains usable.')
+ if result['state'] == 'error' and cached and cached[1]['state'] == 'update_available':
+ # Keep the last discovered release through transient offline/server
+ # failures, including when the page is reopened against this service.
+ previous = cached[1]
+ result.update(state='update_available', available=previous['available'],
+ release_url=previous['release_url'], release_notes=previous.get('release_notes', ''),
+ checked_at=previous['checked_at'], check_error=result['message'],
+ message=previous['message'])
_cache[str(root)] = (time.monotonic(), dict(result))
return result
@@ -282,12 +301,27 @@ def stage_release(installation, *, root=ROOT):
raise ValueError('Artifact must belong to this official release')
if type(artifact.get('size')) is not int or not 0 < artifact['size'] <= MAX_ARTIFACT:
raise ValueError('Invalid release artifact size')
+ if active:
+ old = _read(installation / 'releases' / active['version'] / '.release-ready.json')
+ if old['database_compatibility'] != manifest.get('database_compatibility'):
+ raise ValueError('This update requires a separately verified project migration; automatic download is disabled')
+ destination = installation / 'releases' / version
+ command = shlex.join([sys.executable, str(installation / 'manager.py'),
+ '--installation', str(installation), 'activate-and-launch'])
+ result = {'state': 'staged', 'version': version, 'apply_command': command,
+ 'message': 'Update ready. Close Rieke OS and all project services; the next launch will use this version.'}
+ if destination.exists():
+ # Other tabs/project services may already have prepared this exact
+ # signed artifact. Reuse only its matching completed receipt.
+ ready = _read(destination / '.release-ready.json') if (destination / '.release-ready.json').is_file() else {}
+ if ready != {'version': version, 'commit': manifest['commit'], 'sha256': artifact['sha256'],
+ 'database_compatibility': manifest['database_compatibility']}:
+ raise ValueError('Release directory already exists; inspect the previous stage before retrying')
+ _atomic(installation / 'staged.json', {'version': version})
+ return result
archive = _download(url, artifact['size'])
if len(archive) != artifact['size'] or hashlib.sha256(archive).hexdigest() != artifact.get('sha256'):
raise ValueError('Release artifact checksum or size does not match')
- destination = installation / 'releases' / version
- if destination.exists():
- raise ValueError('Release directory already exists; inspect the previous stage before retrying')
destination.mkdir(parents=True)
try:
_extract(archive, destination)
@@ -307,10 +341,7 @@ def stage_release(installation, *, root=ROOT):
# Keep failure evidence; never delete anything outside this newly created release.
_atomic(destination / '.stage-failed.json', {'version': version, 'message': 'Stage did not finish; active release unchanged.'})
raise
- command = shlex.join([sys.executable, str(installation / 'manager.py'),
- '--installation', str(installation), 'activate-and-launch'])
- return {'state': 'staged', 'version': version, 'apply_command': command,
- 'message': 'Verified and prepared. Close all Rieke OS launchers and project services, then run the apply command. It waits up to five minutes and never kills active writers.'}
+ return result
def activate_staged(installation):
@@ -324,10 +355,14 @@ def activate_staged(installation):
raise ValueError('Staged release is not ready')
active = _read(installation / 'active.json') if (installation / 'active.json').exists() else None
if active:
+ _version(active['version'])
+ if _version(version) <= _version(active['version']):
+ raise ValueError('Staged update must be newer than the active release')
old = _read(installation / 'releases' / active['version'] / '.release-ready.json')
if old['database_compatibility'] != ready['database_compatibility']:
raise ValueError('This update requires a separately verified project migration; automatic activation is disabled')
_atomic(installation / 'active.json', {'version': version, 'previous': active['version'] if active else None})
+ (installation / 'staged.json').unlink()
return {'state': 'activated', 'version': version}
@@ -363,6 +398,13 @@ def main(argv=None):
elif args.command == 'activate-and-launch':
return activate_and_launch(installation)
else:
+ if args.command == 'launch' and (installation / 'staged.json').is_file():
+ try:
+ activate_staged(installation)
+ except (OSError, ValueError) as error:
+ # A busy installation or incompatible update must not prevent
+ # launching the current release. Never stop another service.
+ print(f'Update deferred; launching the current version: {error}', file=sys.stderr)
with installation_lock(installation) as lock_fd:
env = dict(os.environ, RIEKE_INSTALLATION_ROOT=str(installation))
extra = args.arguments[1:] if args.arguments[:1] == ['--'] else args.arguments
diff --git a/rieke-release.json b/rieke-release.json
index ce31537..e935435 100644
--- a/rieke-release.json
+++ b/rieke-release.json
@@ -1,9 +1,11 @@
{
"format": "rieke-application-release",
- "version": "0.1.0",
+ "version": "0.1.3",
"channel": "stable",
- "repository": "maxwellsdm1867/epicTreeGUI",
- "workspace_formats": [1],
+ "repository": "maxwellsdm1867/Rieke-OS",
+ "workspace_formats": [
+ 1
+ ],
"database_compatibility": 1,
"updater_protocol": 1
}
diff --git a/tools/desktop_artifact_e2e.py b/tools/desktop_artifact_e2e.py
new file mode 100644
index 0000000..f899c14
--- /dev/null
+++ b/tools/desktop_artifact_e2e.py
@@ -0,0 +1,401 @@
+#!/usr/bin/env python3
+"""Audit exact desktop DMG/ZIP bytes and exercise their relocated private runtime.
+
+Uses scratch copies only; the provided reference app and artifacts are read-only.
+The reference may be a built candidate; this never updates the user's installed app.
+This unsigned local-host evidence cannot qualify a clean machine or signed update.
+"""
+from __future__ import annotations
+import argparse
+import base64
+import copy
+import hashlib
+import json
+import os
+from pathlib import Path
+import plistlib
+import shutil
+import stat
+import subprocess
+import sys
+import tempfile
+import time
+import zipfile
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT / 'tools'))
+from desktop_runtime_manifest import inventory, native_audit
+from desktop_release import artifact_inventory, verify_metadata, validate_evidence, baseline
+
+
+def digest(path, algorithm='sha256'):
+ with Path(path).open('rb') as handle:
+ return hashlib.file_digest(handle, algorithm).digest()
+
+
+def execute(command, **options):
+ return subprocess.run([str(part) for part in command], check=True, capture_output=True, **options)
+
+
+def resource_modes(runtime, manifest):
+ failures = []
+ for relative, expected in manifest['resources'].items():
+ file = runtime / relative
+ if 'executable' in expected and bool(file.stat().st_mode & 0o111) != expected['executable']:
+ failures.append(relative)
+ return failures
+
+
+def inspect_bundle(bundle):
+ runtime = bundle / 'Contents/Resources/runtime'
+ manifest = json.loads((runtime / 'runtime-manifest.json').read_text())
+ plist = plistlib.loads((bundle / 'Contents/Info.plist').read_bytes())
+ actual = inventory(runtime)
+ if actual != manifest['resources']:
+ raise ValueError('Artifact runtime bytes/links/modes differ from signed-resource manifest')
+ modes = resource_modes(runtime, manifest)
+ if modes:
+ raise ValueError('Packaged executable modes changed')
+ if plist['CFBundleIdentifier'] != 'org.riekeos.desktop' or plist['CFBundleShortVersionString'] != manifest['application_version']:
+ raise ValueError('App identity/version differs from runtime')
+ if plist.get('LSMinimumSystemVersion') != manifest.get('minimum_macos_version'):
+ raise ValueError('App/runtime OS minimums differ')
+ if not os.access(bundle / 'Contents/MacOS/Rieke OS', os.X_OK):
+ raise ValueError('App entry is not executable')
+ escaping = []
+ for file in bundle.rglob('*'):
+ if file.is_symlink() and (os.path.isabs(os.readlink(file)) or not file.resolve().is_relative_to(bundle.resolve()) or not file.exists()):
+ escaping.append(file.relative_to(bundle).as_posix())
+ if escaping:
+ raise ValueError('Complete app contains escaping/broken absolute links')
+ dependency = json.loads((runtime / 'dependency-inventory.json').read_text())
+ license_coverage=dependency.get('native_license_coverage',{})
+ missing_notices=[package['name'] for package in dependency['native_packages']
+ if not license_coverage.get(package['name']) or
+ any(not (runtime/name).is_file() for name in license_coverage.get(package['name'],[]))]
+ application = runtime / 'application'
+ forbidden = [file.relative_to(runtime).as_posix() for file in application.rglob('*') if file.is_file() and
+ (file.suffix.lower() in {'.h5', '.hdf5', '.mat'} or file.name in {'native-credentials.json','catalog.json','project.json','native-runtime.json'} or '.git' in file.parts)]
+ if forbidden:
+ raise ValueError('Private project/data files entered application resources')
+ python_paths = []
+ for file in (runtime / 'python').rglob('*.pth'):
+ for line in file.read_text().splitlines():
+ line = line.strip()
+ if not line or line.startswith(('#', 'import ', 'import\t')):
+ continue
+ if Path(line).is_absolute() or not (file.parent / line).resolve().is_relative_to(runtime.resolve()):
+ python_paths.append(file.relative_to(runtime).as_posix())
+ if python_paths or list(runtime.rglob('pyvenv.cfg')):
+ raise ValueError('Python has editable/external/venv path state')
+ missing = [name for name in ('epicTreeGUI.m','src/loadEpicTreeData.m','src/buildTreeFromEpicData.m',
+ 'src/tree/epicTreeTools.m','src/tree/launchWorkspaceTree.m','src/tree/readWorkspaceTags.m',
+ 'src/tree/validateWorkspaceTags.m','src/tree/workspaceTag.m','src/tree/writeWorkspaceTags.m')
+ if not (application / name).is_file()]
+ signature = subprocess.run(['/usr/bin/codesign','--verify','--deep','--strict',str(bundle)], capture_output=True)
+ shell_paths = ('Contents/Info.plist', 'Contents/MacOS/Rieke OS', 'Contents/Resources/app.asar',
+ 'Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework')
+ shell_hashes = {name:digest(bundle / name).hex() for name in shell_paths}
+ return {'manifest_sha256':digest(runtime/'runtime-manifest.json').hex(), 'runtime_resources':len(actual),
+ 'shell_resource_sha256':shell_hashes,
+ 'internal_links':sum('symlink' in entry for entry in actual.values()),
+ 'python_distributions':len(dependency['python_distributions']), 'native_packages':len(dependency['native_packages']),
+ 'native_license_texts':len(dependency['native_license_files']),
+ 'native_packages_missing_license_coverage':missing_notices,
+ 'minimum_macos_version':manifest['minimum_macos_version'],
+ 'excluded_optional_features':manifest.get('excluded_optional_features',[]),
+ 'development_paths_in_operational_python_paths':[], 'private_application_data_files':[],
+ 'missing_matlab_export_resources':missing,
+ 'developer_id_qualified':False, 'codesign_structural_verification':signature.returncode==0,
+ 'source_dirty':manifest.get('source_dirty'), 'application_version':manifest['application_version']}, manifest
+
+
+NETWORK_GUARD = r'''
+import ipaddress,socket
+_original_connect=socket.socket.connect
+_original_ex=socket.socket.connect_ex
+def _allowed(sock,address):
+ if sock.family == socket.AF_UNIX: return
+ host=address[0]
+ if host == 'localhost':return
+ try:
+ if ipaddress.ip_address(host).is_loopback:return
+ except ValueError:pass
+ raise OSError('Artifact test denies non-loopback network access')
+def connect(sock,address):
+ _allowed(sock,address);return _original_connect(sock,address)
+def connect_ex(sock,address):
+ _allowed(sock,address);return _original_ex(sock,address)
+socket.socket.connect=connect
+socket.socket.connect_ex=connect_ex
+_original_resolver=socket.getaddrinfo
+def getaddrinfo(host,*args,**kwargs):
+ if host not in (None,'localhost','127.0.0.1','::1'):
+ try:
+ if not ipaddress.ip_address(host).is_loopback:raise OSError('External DNS disabled')
+ except ValueError:raise OSError('External DNS disabled')
+ return _original_resolver(host,*args,**kwargs)
+socket.getaddrinfo=getaddrinfo
+'''
+
+DATABASE_PROBE = r'''
+import hashlib,json,os,sys,socket
+from pathlib import Path
+resources=Path(sys.argv[1]).resolve();runtime=resources/'runtime'
+sys.path.insert(0,str(runtime/'application/python'))
+os.environ['RIEKE_DESKTOP_RUNTIME']=str(runtime)
+os.environ['RIEKE_INSTALLATION_ROOT']=str(Path.home()/'state')
+try:socket.getaddrinfo('dependency-provider.invalid',443)
+except OSError:pass
+else:raise ValueError('External DNS guard was not enforced')
+with socket.socket() as blocked:
+ try:blocked.connect(('192.0.2.1',443))
+ except OSError:pass
+ else:raise ValueError('External connection guard was not enforced')
+from workspace_bootstrap import prepare_desktop_parser_config,probe_runtime,validate_desktop_runtime
+prepare_desktop_parser_config(Path.home()/'state')
+validate_desktop_runtime(runtime,verify_hashes=True)
+parser=probe_runtime(str(runtime/'python/bin/python3.11'),str(runtime/'parser'))
+from workspace_projects import create_project
+from workspace_native_mysql import ensure_native_database,stop_native_database,connection_parameters,native_binary
+import pymysql,subprocess
+project=Path(create_project(Path.home()/'projects','Artifact native restore',code_root=runtime/'application')['path'])
+def connect():return pymysql.connect(**connection_parameters(project),autocommit=True)
+def rows():
+ with connect() as c:
+ with c.cursor() as cursor:
+ cursor.execute('SELECT id,amount,payload,JSON_EXTRACT(tags,"$.name") FROM `schema`.artifact_fixture ORDER BY id')
+ return [(row[0],str(row[1]),row[2].hex(),row[3]) for row in cursor.fetchall()]
+try:
+ ensure_native_database(project)
+ with connect() as c:
+ with c.cursor() as cursor:
+ cursor.execute('CREATE DATABASE `schema`')
+ cursor.execute('CREATE TABLE `schema`.artifact_fixture(id INTEGER PRIMARY KEY,amount DECIMAL(20,8),payload BLOB,tags JSON)')
+ cursor.executemany('INSERT INTO `schema`.artifact_fixture VALUES(%s,%s,%s,%s)',[(1,'1.23456789',bytes([0,1,255]),json.dumps({'name':'tag α'})),(2,'-9876.50000000',bytes(range(32)),json.dumps({'name':'tag β'})),(3,'0.00000001',b'',json.dumps({'name':'empty'}))])
+ expected=rows()
+ config=connection_parameters(project);env=dict(os.environ,MYSQL_PWD=config['password'])
+ common=['--no-defaults','--protocol=TCP','--host=127.0.0.1','--port='+str(config['port']),'--user=root']
+ dump=subprocess.check_output([str(native_binary('mysqldump')),*common,'--single-transaction','--skip-lock-tables','--no-tablespaces','--set-gtid-purged=OFF','--hex-blob','--skip-comments','--databases','schema'],env=env,timeout=60)
+ backup=Path.home()/'database-backup.sql';backup.write_bytes(dump)
+ stop_native_database(project)
+ if json.loads((project/'database/native-owner.json').read_text()).get('clean_shutdown') is not True:raise ValueError('Initial clean shutdown not recorded')
+ ensure_native_database(project)
+ if rows()!=expected:raise ValueError('Restart changed exact decimal/binary/JSON values')
+ with connect() as c:
+ with c.cursor() as cursor:cursor.execute('DROP DATABASE `schema`')
+ config=connection_parameters(project);env=dict(os.environ,MYSQL_PWD=config['password'])
+ common=['--no-defaults','--protocol=TCP','--host=127.0.0.1','--port='+str(config['port']),'--user=root']
+ subprocess.run([str(native_binary('mysql')),*common,'--binary-mode','--local-infile=0'],input=dump,env=env,check=True,capture_output=True,timeout=60)
+ if rows()!=expected:raise ValueError('Logical restore changed exact decimal/binary/JSON values')
+ print('RIEKE_ARTIFACT_DATABASE='+json.dumps({'parser':parser,'mysql_restart':'passed','logical_backup_restore':'passed','fixture_rows':len(expected),'fixture_values_sha256':hashlib.sha256(json.dumps(expected).encode()).hexdigest(),'backup_sha256':hashlib.sha256(dump).hexdigest(),'external_dns_and_connections_denied':True,'development_path_excluded':True,'fresh_home':True}))
+finally:stop_native_database(project)
+validate_desktop_runtime(runtime,verify_hashes=True)
+'''
+
+
+def isolated_database_probe(resources, scratch):
+ home=scratch/'isolated HOME with spaces';home.mkdir()
+ guard=scratch/'no external network';guard.mkdir()
+ (guard/'sitecustomize.py').write_text(NETWORK_GUARD)
+ env={'HOME':str(home),'TMPDIR':str(scratch),'PATH':'/usr/bin:/bin','PYTHONDONTWRITEBYTECODE':'1',
+ 'PYTHONNOUSERSITE':'1','PYTHONPATH':str(guard),'LANG':'en_US.UTF-8','MPLCONFIGDIR':str(home/'matplotlib'),
+ 'HTTP_PROXY':'http://127.0.0.1:9','HTTPS_PROXY':'http://127.0.0.1:9','NO_PROXY':'localhost,127.0.0.1,::1'}
+ result=execute([resources/'runtime/python/bin/python3.11','-B','-c',DATABASE_PROBE,resources],env=env,cwd=home,text=True,timeout=300)
+ return json.loads(next(line.split('=',1)[1] for line in result.stdout.splitlines() if line.startswith('RIEKE_ARTIFACT_DATABASE=')))
+
+
+def extracted_scientific_probe(resources, scratch, recording):
+ """Reuse real workflow assertions while forbidding external network/dependency access."""
+ from desktop_backend_smoke import run as backend_run
+ output=scratch/'extracted-backend-smoke.json'
+ guard=scratch/'scientific network guard';guard.mkdir()
+ (guard/'sitecustomize.py').write_text(NETWORK_GUARD)
+ original=subprocess.Popen
+ def guarded_spawn(*arguments,**options):
+ env=dict(options.get('env') or os.environ)
+ env.update(PYTHONPATH=str(guard),HTTP_PROXY='http://127.0.0.1:9',HTTPS_PROXY='http://127.0.0.1:9',
+ NO_PROXY='localhost,127.0.0.1,::1')
+ options['env']=env
+ return original(*arguments,**options)
+ try:
+ subprocess.Popen=guarded_spawn
+ backend_run(resources,output,recording=recording)
+ finally:subprocess.Popen=original
+ return json.loads(output.read_text())
+
+
+def expected_rejection(name, action, results):
+ try:action()
+ except Exception as error:
+ results.append({'case':name,'rejected':True,'error_type':type(error).__name__})
+ else:raise ValueError('Fault accepted: '+name)
+
+
+def fault_checks(directory, manifest, scratch, bundle):
+ faults=[]
+ expected_rejection('foreign publication repository',lambda:baseline('v'+manifest['application_version'],'untrusted/repository'),faults)
+ expected_rejection('dirty or unreviewed source baseline',lambda:baseline('v'+manifest['application_version'],'maxwellsdm1867/Rieke-OS'),faults)
+ artifacts=artifact_inventory(directory)
+ evidence={'format':'rieke-desktop-qualification','version':1,'application_version':manifest['application_version'],
+ 'platform':'darwin','architecture':'arm64','source_commit':manifest['source_commit'],'artifacts':artifacts,'requirements':{}}
+ expected_rejection('missing R01-R12 qualification',lambda:validate_evidence(evidence,artifacts,manifest['application_version']),faults)
+ wrong=copy.deepcopy(evidence);wrong['artifacts']={}
+ expected_rejection('qualification exact artifact hash mismatch',lambda:validate_evidence(wrong,artifacts,manifest['application_version']),faults)
+ faultdir=scratch/'metadata faults';faultdir.mkdir()
+ for file in directory.iterdir():
+ if file.is_file() and file.suffix in ('.zip','.dmg','.blockmap'):os.link(file,faultdir/file.name)
+ original=(directory/'latest-mac.yml').read_text()
+ variants={'wrong metadata version':original.replace('version: '+manifest['application_version'],'version: 9.9.9'),
+ 'metadata checksum corruption':original.replace('sha512:','sha512: invalid',1),
+ 'unsafe metadata artifact path':original.replace('url: Rieke-OS','url: ../Rieke-OS',1),
+ 'metadata size mismatch':original.replace('size:','size: 1 #',1)}
+ for name,text in variants.items():
+ (faultdir/'latest-mac.yml').write_text(text)
+ expected_rejection(name,lambda:verify_metadata(faultdir,manifest['application_version']),faults)
+ missing_dmg = next(faultdir.glob('*.dmg'))
+ missing_dmg.unlink()
+ expected_rejection('incomplete release artifact set', lambda:artifact_inventory(faultdir), faults)
+ damaged_zip=scratch/'actual candidate CRC corruption.zip'
+ execute(['/bin/cp','-c',next(directory.glob('*.zip')),damaged_zip])
+ import struct
+ with zipfile.ZipFile(damaged_zip) as archive:
+ entry=next(item for item in archive.infolist() if item.filename.endswith('/Contents/Info.plist'))
+ with damaged_zip.open('r+b') as handle:
+ handle.seek(entry.header_offset)
+ header=handle.read(30)
+ filename_length,extra_length=struct.unpack_from('v.compatibleCandidate({...good,source_dirty:true},current,'0.1.1'));
+reject('incompatible database',()=>v.compatibleCandidate({...good,database_compatibility:999},current,'0.1.1'));
+reject('incompatible workspace',()=>v.compatibleCandidate({...good,workspace_formats:[999]},current,'0.1.1'));
+reject('wrong native architecture',()=>v.compatibleCandidate({...good,architecture:'x64'},current,'0.1.1'));
+reject('older candidate version',()=>v.compatibleCandidate({...good,application_version:'0.0.9'},current,'0.0.9'));
+reject('unsupported host OS',()=>v.compatibleMacMinimum('15.0','14.2'));
+reject('runtime traversal path',()=>v.safeResource('/tmp','../escape'));
+(async()=>{try{await v.signingIdentity(process.argv[3]);throw Error('unsigned app accepted')}catch(e){if(e.message==='unsigned app accepted')throw e;tests.push({case:'unsigned real app rejected by update signing gate',rejected:true});}
+const installer=require(process.argv[4]);try{await installer.installCompleteBundle({source:process.argv[3],destination:p.join(process.argv[5],'must not install','Rieke OS.app')});throw Error('unsigned install accepted')}catch(e){if(e.message==='unsigned install accepted')throw e;tests.push({case:'unsigned real app rejected by first-open signed installer',rejected:true});}
+process.stdout.write(JSON.stringify(tests));})();
+'''
+ result=execute(['node','-e',javascript,ROOT/'desktop/updater-validation.cjs',bundle/'Contents/Resources/runtime/runtime-manifest.json',bundle,ROOT/'desktop/bootstrap.cjs',scratch],text=True)
+ faults.extend(json.loads(result.stdout))
+ runtime=bundle/'Contents/Resources/runtime'
+ verify="const v=require(process.argv[1]),fs=require('fs');v.verifyResources(process.argv[2],JSON.parse(fs.readFileSync(process.argv[3],'utf8')).resources).catch(e=>{process.stderr.write(e.message);process.exitCode=1;})"
+ def verify_corruption():
+ execute(['node','-e',verify,ROOT/'desktop/updater-validation.cjs',runtime,runtime/'runtime-manifest.json'],text=True)
+ extra=runtime/'.artifact-unexpected-file';extra.write_text('untracked resource')
+ try:expected_rejection('unexpected actual runtime resource',verify_corruption,faults)
+ finally:extra.unlink()
+ target=runtime/'application/python/workspace_desktop.py';data=target.read_bytes()
+ target.write_bytes(data+b'\n# corrupt scratch artifact only\n')
+ try:expected_rejection('corrupted actual runtime code resource',verify_corruption,faults)
+ finally:target.write_bytes(data)
+ target=runtime/'python/bin/python3.11';mode=stat.S_IMODE(target.stat().st_mode);target.chmod(mode & ~0o111)
+ try:expected_rejection('changed actual runtime executable permissions',verify_corruption,faults)
+ finally:target.chmod(mode)
+ target=runtime/'parser/src/retinanalysis';link=os.readlink(target);target.unlink();target.symlink_to(scratch)
+ try:expected_rejection('escaping actual parser wheel symlink',verify_corruption,faults)
+ finally:target.unlink();target.symlink_to(link)
+ return faults
+
+
+def run(args):
+ directory=args.directory.resolve();reference=args.installed.resolve()
+ scratch=Path(tempfile.mkdtemp(prefix="Rieke artifact audit ' unusual "))
+ report={'format':'rieke-desktop-artifact-e2e','version':1,'production_ready':False,'local_host_only':True,
+ 'checks':[],'failures':[],'not_validated':['Developer ID signing/notarization','real signed old-to-new update',
+ 'another user/clean machine','minimum supported macOS 14.0 device','source-user migration']}
+ report['installation_reference_kind']=('built-candidate' if reference == (ROOT/'desktop/dist/mac-arm64/Rieke OS.app').resolve()
+ else 'provided-readonly-bundle')
+ report['user_installation_updated']=False
+ artifacts=artifact_inventory(directory);report['artifacts']=artifacts
+ report['host_macos']=execute(['/usr/bin/sw_vers','-productVersion'],text=True).stdout.strip()
+ bundles={}
+ try:
+ zipfile_path=next(directory.glob('*.zip'));dmg=next(directory.glob('*.dmg'))
+ script="process.stdout.write(require(process.argv[1]).ARCHIVE_CHECK)"
+ check=execute(['node','-e',script,ROOT/'desktop/updater-validation.cjs'],text=True).stdout
+ execute([sys.executable,'-c',check,zipfile_path],timeout=60)
+ report['checks'].append('actual updater ZIP path/link/bounds audit')
+ ziproot=scratch/"ZIP extracted ' relocated";ziproot.mkdir()
+ execute(['/usr/bin/ditto','-x','-k',zipfile_path,ziproot],timeout=180)
+ bundles['zip']=ziproot/'Rieke OS.app'
+ execute(['/usr/bin/hdiutil','verify',dmg],timeout=180)
+ mount=scratch/'read-only DMG mount';mount.mkdir()
+ execute(['/usr/bin/hdiutil','attach','-readonly','-nobrowse','-mountpoint',mount,dmg],timeout=180)
+ try:
+ dmgroot=scratch/'DMG copied relocated';dmgroot.mkdir()
+ execute(['/usr/bin/ditto','--rsrc','--extattr','--acl',mount/'Rieke OS.app',dmgroot/'Rieke OS.app'],timeout=180)
+ bundles['dmg']=dmgroot/'Rieke OS.app'
+ finally:execute(['/usr/bin/hdiutil','detach',mount],timeout=30)
+ report['checks'].append('actual DMG integrity/mount/copy preserving bundle')
+ bundles['reference']=reference
+ inspections={};manifests={}
+ for name,bundle in bundles.items():
+ inspections[name],manifests[name]=inspect_bundle(bundle)
+ if inspections[name]['missing_matlab_export_resources']:
+ report['failures'].append({'case':name+' MATLAB export closure','missing':inspections[name]['missing_matlab_export_resources']})
+ if inspections[name]['native_packages_missing_license_coverage']:
+ report['failures'].append({'case':name+' native license coverage',
+ 'missing':inspections[name]['native_packages_missing_license_coverage']})
+ report['bundles']=inspections
+ if len({item['manifest_sha256'] for item in inspections.values()})!=1:
+ raise ValueError('DMG/ZIP/reference runtime manifests differ')
+ if any(item['shell_resource_sha256']!=inspections['zip']['shell_resource_sha256'] for item in inspections.values()):
+ raise ValueError('DMG/ZIP/reference Electron shell bytes differ')
+ report['checks'].append('DMG/ZIP/read-only reference exact resources, modes, versions and contained links')
+ audit=native_audit(bundles['zip']/'Contents/Resources/runtime');report['native_audit']=audit
+ if audit['problems']:raise ValueError('Artifact native closure is external or incomplete')
+ report['checks'].append('all packaged native load paths resolve privately or to macOS system libraries')
+ verify_metadata(directory,manifests['zip']['application_version'])
+ report['checks'].append('final updater YAML SHA512/size matches exact ZIP/DMG bytes')
+ report['faults']=fault_checks(directory,manifests['zip'],scratch,bundles['zip'])
+ report['checks'].append('baseline/metadata/compatibility/signature fault rejections')
+ before=inventory(bundles['zip']/'Contents/Resources/runtime')
+ report['bundled_database']=isolated_database_probe(bundles['zip']/'Contents/Resources',scratch)
+ report['checks'].append('extracted bundled parser +native MySQL decimal/blob/JSON restart and logical backup/restore under isolated HOME/PATH/network guard')
+ report['resources_unchanged_after_native_workflow']=before==inventory(bundles['zip']/'Contents/Resources/runtime')
+ if not report['resources_unchanged_after_native_workflow']:raise ValueError('Native workflow changed artifact resources')
+ if args.recording:
+ report['extracted_scientific_backend']=extracted_scientific_probe(bundles['dmg']/'Contents/Resources',scratch,args.recording)
+ report['checks'].append('DMG-extracted real H5 import/tag/query/export/restart/drain and resource invariance')
+ if artifact_inventory(directory)!=artifacts:raise ValueError('Read-only tests changed published artifact bytes')
+ report['checks'].append('distribution artifact bytes unchanged after testing')
+ except (Exception,SystemExit) as error:
+ message=str(error)[:500] if isinstance(error,ValueError) else 'Scratch subprocess check failed; private local diagnostics retained.'
+ report['failures'].append({'case':'artifact test exception','error_type':type(error).__name__,'message':message})
+ # Private failure diagnostics stay local and never enter release evidence.
+ detail={'error':repr(error)}
+ for key in ('stdout','stderr'):
+ data=getattr(error,key,None)
+ if data:
+ detail[key]=data.decode(errors='replace') if isinstance(data,bytes) else data
+ (scratch/'private-error.txt').write_text(json.dumps(detail,indent=2))
+ report['passed']=not report['failures']
+ args.output.parent.mkdir(parents=True,exist_ok=True)
+ args.output.write_text(json.dumps(report,indent=2)+'\n')
+ print(json.dumps(report,indent=2))
+ if report['passed'] and not args.keep_scratch:shutil.rmtree(scratch)
+ else:print('Private scratch retained:',scratch)
+ return 0 if report['passed'] else 1
+
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--directory',type=Path,default=ROOT/'desktop/dist')
+ parser.add_argument('--installed',type=Path,default=Path.home()/'Applications/Rieke OS.app')
+ parser.add_argument('--recording',type=Path)
+ parser.add_argument('--output',type=Path,default=ROOT/'docs/dev/desktop-artifact-e2e.json')
+ parser.add_argument('--keep-scratch',action='store_true')
+ raise SystemExit(run(parser.parse_args()))
diff --git a/tools/desktop_backend_smoke.py b/tools/desktop_backend_smoke.py
new file mode 100644
index 0000000..9f5b6aa
--- /dev/null
+++ b/tools/desktop_backend_smoke.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+"""Exercise the real bundled WSGI/Python/MySQL boundary under fresh user state.
+
+This is local unsigned evidence, not clean-machine or signed-update qualification.
+Never kill children on failure: an unacknowledged scientific service is retained.
+"""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import secrets
+import socket
+import subprocess
+import tempfile
+import threading
+import time
+from urllib.error import HTTPError
+from urllib.request import Request, urlopen
+from urllib.parse import urlsplit
+import uuid
+
+
+def inventory(runtime):
+ # Hash paths as well as bytes so new bytecode/config files cannot hide.
+ files = {}
+ for path in sorted(runtime.rglob('*')):
+ if path.is_symlink():
+ files[str(path.relative_to(runtime))] = {'symlink': os.readlink(path)}
+ elif path.is_file():
+ with path.open('rb') as stream:
+ files[str(path.relative_to(runtime))] = hashlib.file_digest(stream, 'sha256').hexdigest()
+ return files
+
+
+def run(resources, output, project=True, recording=None):
+ resources = Path(resources).resolve(strict=True)
+ runtime = resources / 'runtime'
+ before = inventory(runtime)
+ state = Path(tempfile.mkdtemp(prefix='rieke-desktop-smoke-'))
+ capability = secrets.token_hex(32)
+ session_id = str(uuid.uuid4())
+ with socket.socket() as listener:
+ listener.bind(('127.0.0.1', 0))
+ port = listener.getsockname()[1]
+ origin = f'http://127.0.0.1:{port}'
+ def call(path, body=None, headers=None, timeout=10, base=origin):
+ auth = {'X-Rieke-Desktop-Capability': capability, 'X-Workspace-Request': '1', 'Content-Type': 'application/json'}
+ auth.update(headers or {})
+ data = None if body is None else json.dumps(body).encode()
+ with urlopen(Request(base + path, data=data, headers=auth), timeout=timeout) as response:
+ return json.load(response)
+ log = (state / 'backend.log').open('wb')
+ env = {'HOME': str(state / 'home'), 'PATH': '/usr/bin:/bin', 'LANG': 'en_US.UTF-8',
+ 'TMPDIR': os.environ.get('TMPDIR', '/tmp'), 'PYTHONDONTWRITEBYTECODE': '1', 'PYTHONNOUSERSITE': '1',
+ 'RIEKE_DESKTOP_CAPABILITY': capability, 'RECORDING_WORKSPACE_ROOT': str(state / 'projects')}
+ Path(env['HOME']).mkdir()
+ command = [str(runtime / 'python/bin/python3.11'), '-B', str(runtime / 'application/python/workspace_desktop.py'),
+ '--resources', str(resources), '--manifest', str(runtime / 'runtime-manifest.json'),
+ '--user-state', str(state / 'user-state'), '--session-id', session_id, '--port', str(port)]
+ process = subprocess.Popen(command, cwd=state, env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=log)
+ bound = threading.Event()
+ packet = {}
+ def read_bound():
+ for line in iter(process.stdout.readline, b''):
+ log.write(line)
+ log.flush()
+ if line.startswith(b'RIEKE_DESKTOP_BOUND='):
+ try:
+ packet.update(json.loads(line.split(b'=', 1)[1]))
+ finally:
+ bound.set()
+ break
+ threading.Thread(target=read_bound, daemon=True).start()
+ result = {'format': 'rieke-desktop-backend-smoke', 'version': 1, 'production_ready': False,
+ 'checks': [], 'limitations': ['Unsigned developer host', 'No installer or old-to-new update exercised',
+ 'No recording import/query/export/source-user migration exercised']}
+ started = time.monotonic()
+ error = None
+ try:
+ deadline = time.monotonic() + 180
+ while time.monotonic() < deadline:
+ if process.poll() is not None:
+ raise ValueError(f'Bundled backend exited with {process.returncode}; private logs retained')
+ if not bound.is_set():
+ bound.wait(.2)
+ continue
+ if packet.get('pid') != process.pid or packet.get('session_id') != session_id or packet.get('port') != port:
+ raise ValueError('Private listener bind receipt identity mismatch')
+ try:
+ health = call('/api/desktop/health', timeout=2)
+ break
+ except OSError:
+ time.sleep(.2)
+ else:
+ raise ValueError('Bundled backend readiness timeout; service retained')
+ manifest = json.loads((runtime / 'runtime-manifest.json').read_text())
+ result['runtime_manifest_sha256'] = hashlib.sha256((runtime / 'runtime-manifest.json').read_bytes()).hexdigest()
+ result['runtime_resource_count'] = len(manifest['resources'])
+ result['runtime_identity'] = {key: manifest[key] for key in ('application_version', 'source_commit',
+ 'parser_commit', 'platform', 'architecture', 'python_version', 'mysql_version')}
+ expected = {'pid': process.pid, 'session_id': session_id, 'application_version': manifest['application_version'],
+ 'source_commit': manifest['source_commit'], 'workspace_formats': manifest['workspace_formats'],
+ 'database_compatibility': manifest['database_compatibility']}
+ if not health.get('ready') or any(health.get(k) != v for k, v in expected.items()):
+ raise ValueError('Health identity mismatch')
+ result['checks'].append('exact bundled readiness/session/release')
+ result['readiness_seconds'] = round(time.monotonic() - started, 3)
+ for headers in ({'X-Rieke-Desktop-Capability': 'wrong'}, {'Origin': 'https://foreign.invalid'},
+ {'Host': f'localhost:{port}'}):
+ try:
+ call('/api/desktop/health', headers=headers)
+ except HTTPError as response:
+ if response.code != 403:
+ raise
+ else:
+ raise ValueError('Authorization fault was accepted')
+ result['checks'].append('wrong capability/origin/host rejected')
+ try:
+ call('/api/app/updates/stage', {})
+ except HTTPError as response:
+ if response.code != 409:
+ raise
+ else:
+ raise ValueError('Source updater remained enabled')
+ result['checks'].append('source updater disabled')
+ scientific_project = None
+ if project:
+ created = call('/api/projects', {'name': 'Desktop smoke project', 'project_directory': str(state / 'project')})
+ scientific_project = created['project']
+ opened = call('/api/projects/open-folder', {'directory': scientific_project['path']}, timeout=180)
+ project_origin = opened['url'].rstrip('/')
+ authorization = call('/api/desktop/authorize-project', {'port': urlsplit(project_origin).port})
+ if authorization['record'].get('bound') is not True:
+ raise ValueError('Project listener ownership was not bound before authorization')
+ child = authorization['health']
+ result['checks'].append('main-only authorization validates live bound project before navigation')
+ if child.get('project_uuid') != scientific_project['uuid'] or child.get('project_path') != scientific_project['path']:
+ raise ValueError('Project service identity mismatch')
+ result['checks'].append('native project created/opened using bundled runtime')
+ registry = call('/api/desktop/health')['services']
+ if len(registry) != 1 or registry[0]['pid'] != child['pid']:
+ raise ValueError('Project registry did not own service')
+ result['checks'].append('global service registry owns project process')
+ if recording:
+ recording = Path(recording).resolve(strict=True)
+ with recording.open('rb') as stream:
+ original_checksum = hashlib.file_digest(stream, 'sha256').hexdigest()
+ import_started = time.monotonic()
+ submitted = call('/api/imports', {'source_path': str(recording)}, base=project_origin)
+ try:
+ call('/api/desktop/drain', {}, timeout=35)
+ except HTTPError as response:
+ if response.code != 409:
+ raise
+ else:
+ raise ValueError('Active recording import did not defer update drain')
+ result['checks'].append('real import writer defers global update drain')
+ deadline = time.monotonic() + 900
+ while time.monotonic() < deadline:
+ jobs = call('/api/jobs', base=project_origin)['jobs']
+ job = next(item for item in jobs if item['job_uuid'] == submitted['job_uuid'])
+ if job['status'] in ('complete', 'complete_with_warnings'):
+ if job.get('requires_reconciliation'):
+ raise ValueError('Real import requires reconciliation')
+ break
+ if job['status'] in ('failed', 'interrupted'):
+ raise ValueError('Real recording import failed: ' + job.get('error', 'unknown'))
+ time.sleep(1)
+ else:
+ raise ValueError('Real recording import timed out; workers retained')
+ result['import_seconds'] = round(time.monotonic() - import_started, 3)
+ overview = call('/api/overview', base=project_origin, timeout=45)
+ if overview['counts']['epochs'] < 1 or not overview['protocols']:
+ raise ValueError('Real recording import produced no epochs/protocols')
+ result['scientific_counts'] = {key: overview['counts'][key] for key in ('sources', 'cells', 'epochs', 'protocols')}
+ protocol_uuid = overview['protocols'][0]['protocol_uuid']
+ page = call('/api/protocols/' + protocol_uuid + '/epochs?limit=1', base=project_origin)
+ epoch_uuid = page['epochs'][0]['epoch_uuid']
+ profile = call('/api/annotation-profiles', {'display_name': 'Desktop qualification'}, base=project_origin)
+ call('/api/annotation-profiles/selected', {'profile_uuid': profile['profile_uuid']}, base=project_origin)
+ tag = 'desktop-qualification'
+ call('/api/annotations', {'target_kind': 'epoch', 'target_uuids': [epoch_uuid],
+ 'profile_uuid': profile['profile_uuid'], 'tags_add': [tag],
+ 'expected_revisions': {epoch_uuid: 0}}, base=project_origin)
+ query = call('/api/explore/run', {'predicate': {'field': 'annotations/epoch/tags',
+ 'operator': 'contains', 'value': tag}, 'splits': 'date,protocol,cell'},
+ base=project_origin, timeout=45)
+ if query['matched_count'] != 1:
+ raise ValueError('Tagged query did not preserve exact epoch membership')
+ protocol = call('/api/protocols/' + protocol_uuid, base=project_origin)
+ exported = call('/api/protocols/' + protocol_uuid + '/exports',
+ {'format': 'reference-json', 'query_revision': protocol['query_revision'],
+ 'filters': {'epoch_uuid': epoch_uuid}, 'name': 'Desktop qualification'},
+ base=project_origin, timeout=60)
+ artifact = Path(exported['artifact_path'])
+ exported_package = json.loads(artifact.read_text())
+ if [row['epoch_uuid'] for row in exported_package['epochs']] != [epoch_uuid]:
+ raise ValueError('Export epoch identity changed')
+ if tag not in [item['tag'] for item in exported_package['epochs'][0]['annotations']['epoch_tags']]:
+ raise ValueError('Export did not retain shared annotation')
+ with artifact.open('rb') as stream:
+ export_checksum = hashlib.file_digest(stream, 'sha256').hexdigest()
+ result['checks'].append('real import/tag/exact tagged query/reference export')
+ call('/api/project/close', {}, base=project_origin, timeout=45)
+ deadline = time.monotonic() + 35
+ while call('/api/desktop/health')['services'] and time.monotonic() < deadline:
+ time.sleep(.2)
+ reopened = call('/api/projects/open-folder', {'directory': scientific_project['path']}, timeout=180)
+ project_origin = reopened['url'].rstrip('/')
+ authorization = call('/api/desktop/authorize-project', {'port': urlsplit(project_origin).port})
+ if authorization['health']['project_uuid'] != scientific_project['uuid']:
+ raise ValueError('Restarted project authorization changed identity')
+ annotation = call('/api/annotations/read', {'target_kind': 'epoch', 'target_uuids': [epoch_uuid]}, base=project_origin)
+ if tag not in [item['tag'] for item in annotation['targets'][epoch_uuid]['tags']]:
+ raise ValueError('Restart lost shared annotation')
+ restarted_query = call('/api/explore/run', {'predicate': {'field': 'annotations/epoch/tags',
+ 'operator': 'contains', 'value': tag}, 'splits': 'date,protocol,cell'}, base=project_origin)
+ if restarted_query['matched_count'] != 1:
+ raise ValueError('Restart changed saved scientific query membership')
+ with artifact.open('rb') as stream:
+ if hashlib.file_digest(stream, 'sha256').hexdigest() != export_checksum:
+ raise ValueError('Restart changed export artifact')
+ with recording.open('rb') as stream:
+ if hashlib.file_digest(stream, 'sha256').hexdigest() != original_checksum:
+ raise ValueError('Original recording changed')
+ result['checks'].append('restart preserves tags/query/export and original H5 checksum')
+ result['limitations'][-1] = 'Source-user migration and real signed update not exercised'
+ if call('/api/desktop/drain', {}, timeout=65).get('ready') is not True:
+ raise ValueError('Drain was not acknowledged')
+ if scientific_project:
+ owner = json.loads((Path(scientific_project['path']) / 'database/native-owner.json').read_text())
+ if owner.get('clean_shutdown') is not True:
+ raise ValueError('Native database clean shutdown missing')
+ if call('/api/desktop/health')['services']:
+ raise ValueError('Project services did not exit')
+ result['checks'].append('registered project/MySQL drained and exited with clean receipt')
+ call('/api/desktop/stop', {})
+ process.wait(timeout=35)
+ if process.returncode != 0:
+ raise ValueError('Bundled backend shutdown was not clean')
+ result['checks'].append('root WSGI process graceful exit')
+ except Exception as failure:
+ error = failure
+ result['error'] = str(failure)
+ # Retrying orderly drain is permitted; no signals or force cancellation.
+ try:
+ if process.poll() is None and call('/api/desktop/drain', {}, timeout=65).get('ready'):
+ call('/api/desktop/stop', {})
+ process.wait(timeout=35)
+ except Exception:
+ pass
+ finally:
+ log.close()
+ after = inventory(runtime)
+ result['packaged_resources_unchanged'] = before == after
+ if before != after:
+ result['changed_resource_paths'] = [name for name in before.keys() | after.keys() if before.get(name) != after.get(name)]
+ error = error or ValueError('Bundled resources changed after launch')
+ result['passed'] = error is None
+ # Public receipt contains no local paths, capabilities or credentials.
+ Path(output).parent.mkdir(parents=True, exist_ok=True)
+ Path(output).write_text(json.dumps(result, indent=2) + '\n')
+ print(json.dumps(result, indent=2))
+ if error:
+ print('Private test data/logs retained:', state)
+ if error:
+ raise SystemExit(1)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--resources', type=Path, default=Path(__file__).resolve().parents[1] / 'desktop/build')
+ parser.add_argument('--output', type=Path, default=Path(__file__).resolve().parents[1] / 'docs/dev/desktop-backend-smoke.json')
+ parser.add_argument('--launcher-only', action='store_true')
+ parser.add_argument('--recording', type=Path, help='External Symphony H5; never copied into the app')
+ args = parser.parse_args()
+ run(args.resources, args.output, project=not args.launcher_only, recording=args.recording)
diff --git a/tools/desktop_build_runtime.py b/tools/desktop_build_runtime.py
new file mode 100644
index 0000000..a6c0781
--- /dev/null
+++ b/tools/desktop_build_runtime.py
@@ -0,0 +1,434 @@
+#!/usr/bin/env python3
+"""Build a private, relocatable desktop runtime before signing.
+
+Build dependencies (uv, C++ toolchain, npm) are allowed here, never at launch.
+Downloads are SHA-256 pinned. Does not sign with Developer ID or qualify release.
+"""
+from __future__ import annotations
+import argparse
+import hashlib
+import io
+import json
+import os
+from pathlib import Path
+import shutil
+import re
+import subprocess
+import sys
+import tarfile
+import tempfile
+import zipfile
+from urllib.request import urlopen
+
+ROOT = Path(__file__).resolve().parents[1]
+OPTIONAL_PID_ATTACH = 'python/lib/python3.11/site-packages/debugpy/_vendored/pydevd/pydevd_attach_to_process/attach.dylib'
+MATLAB_EXPORT_HELPERS = ('launchWorkspaceTree', 'readWorkspaceTags', 'validateWorkspaceTags', 'workspaceTag', 'writeWorkspaceTags')
+sys.path.insert(0, str(ROOT / 'python'))
+from workspace_bootstrap import verify_checkout
+from workspace_mysql_runtime import install_mysql_runtime, mysql_runtime
+
+
+def run(command, **kwargs):
+ subprocess.run([str(x) for x in command], check=True, **kwargs)
+
+
+def digest(path):
+ with Path(path).open('rb') as handle:
+ return hashlib.file_digest(handle, 'sha256').hexdigest()
+
+
+def patch_parser(checkout):
+ """Patch only config ownership, leaving parser algorithms byte-identical."""
+ path = checkout / 'src/retinanalysis/config/settings.py'
+ text = path.read_text()
+ old = 'config_path = ir.files(retinanalysis) / os.path.join("config", "config.ini")'
+ new = '''# Rieke desktop wheels store mutable configuration outside signed resources.
+config_path = os.environ.get("RIEKE_PARSER_CONFIG")
+if config_path:
+ if not os.path.isabs(config_path):
+ raise ValueError("RIEKE_PARSER_CONFIG must be an absolute user-state path")
+else:
+ config_path = ir.files(retinanalysis) / os.path.join("config", "config.ini")'''
+ if text.count(old) != 1:
+ raise ValueError('Pinned parser config patch no longer applies exactly')
+ path.write_text(text.replace(old, new))
+ (checkout / 'src/retinanalysis/config/config.ini').unlink(missing_ok=True)
+ return {'path': 'src/retinanalysis/config/settings.py',
+ 'original_sha256': hashlib.sha256(text.encode()).hexdigest(),
+ 'patched_sha256': digest(path), 'purpose': 'RIEKE_PARSER_CONFIG external user state'}
+
+
+def stage_tracked_parser(checkout, staged):
+ """Only reviewed tracked parser/submodule source can enter the wheel build."""
+ names = subprocess.check_output(['git', '-C', checkout, 'ls-files', '--recurse-submodules', '-z']).decode().split('\0')
+ for name in filter(None, names):
+ source, target = checkout / name, staged / name
+ if Path(name).is_absolute() or '..' in Path(name).parts:
+ raise ValueError('Tracked parser source path escapes staging')
+ if source.is_symlink():
+ if not source.resolve().is_relative_to(checkout):
+ raise ValueError('Tracked parser source link escapes checkout')
+ target.parent.mkdir(parents=True, exist_ok=True)
+ target.symlink_to(os.readlink(source))
+ elif source.is_file():
+ target.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy2(source, target)
+
+
+def download_python(spec, cache):
+ path = cache / 'standalone-python.tar.gz'
+ if not path.exists():
+ temporary = path.with_suffix('.download')
+ try:
+ with urlopen(spec['url'], timeout=60) as source, temporary.open('wb') as out:
+ shutil.copyfileobj(source, out)
+ if digest(temporary) != spec['sha256']:
+ raise ValueError('Standalone Python download SHA-256 mismatch')
+ temporary.replace(path)
+ finally:
+ temporary.unlink(missing_ok=True)
+ if digest(path) != spec['sha256']:
+ raise ValueError('Cached standalone Python SHA-256 mismatch')
+ return path
+
+
+def copy_application(root, output, frontend_source=None):
+ application = output / 'application'
+ if application.exists():
+ shutil.rmtree(application)
+ (application / 'python').mkdir(parents=True)
+ # Explicit allowlist excludes tests, projects, source runtime, credentials and receipts.
+ for path in (root / 'python').glob('*.py'):
+ shutil.copy2(path, application / 'python' / path.name)
+ for name in ('workspace-source.json', 'workspace-mysql-runtime.json'):
+ shutil.copy2(root / 'python' / name, application / 'python' / name)
+ shutil.copy2(root / 'rieke-release.json', application / 'rieke-release.json')
+ shutil.copy2(root / 'workspace-app/package.json', application / 'workspace-app-package.json')
+ copy_matlab_application(root, application)
+ dist = Path(frontend_source) if frontend_source else root / 'workspace-app/dist'
+ if not (dist / 'index.html').is_file():
+ raise ValueError('Build the frontend before assembling desktop resources')
+ frontend = output / 'frontend'
+ if frontend.exists():
+ shutil.rmtree(frontend)
+ shutil.copytree(dist, frontend)
+ (application / 'workspace-app').mkdir()
+ # Existing Flask static route contract, contained relocatable link.
+ (application / 'workspace-app/dist').symlink_to('../../frontend', target_is_directory=True)
+ shutil.copy2(root / 'workspace-app/package.json', application / 'workspace-app/package.json')
+
+
+def copy_matlab_application(root, application):
+ """Ship the source closure required by MAT exports and their GUI launcher.
+
+ MATLAB is not required to create exports. These reviewed source helpers let
+ a recipient with MATLAB use the exported launcher without the source clone.
+ Recordings, examples, tests and arbitrary adjacent files are not resources.
+ """
+ root, application = Path(root), Path(application)
+ required = [root / 'epicTreeGUI.m', root / 'src/loadEpicTreeData.m',
+ root / 'src/buildTreeFromEpicData.m', root / 'src/tree/epicTreeTools.m']
+ required.extend(root / 'src/tree' / (name + '.m') for name in MATLAB_EXPORT_HELPERS)
+ if any(not path.is_file() for path in required):
+ raise ValueError('MATLAB export/launcher source closure is incomplete')
+ paths = [root / 'epicTreeGUI.m', *sorted((root / 'src').rglob('*.m'))]
+ for source in paths:
+ if source.is_symlink() or not source.resolve().is_relative_to(root.resolve()):
+ raise ValueError('MATLAB application source escapes the reviewed code tree')
+ target = application / source.relative_to(root)
+ target.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy2(source, target)
+
+
+def build_frontend():
+ # Source development keeps old chunks for in-flight browsers. A desktop
+ # release includes only a fresh build, not that accumulated history.
+ dist = ROOT / 'desktop/build/renderer'
+ run(['npm', 'run', 'build', '--', '--outDir', dist, '--emptyOutDir'], cwd=ROOT / 'workspace-app')
+ return dist
+
+
+def relocate_native(output):
+ """Remove build-host loader paths and close wheel/native library references."""
+ from desktop_runtime_manifest import MACHO
+ modified = []
+ for path in sorted(output.rglob('*')):
+ if path.is_symlink() or not path.is_file():
+ continue
+ with path.open('rb') as handle:
+ if handle.read(4) not in MACHO:
+ continue
+ header = subprocess.check_output(['/usr/bin/otool', '-l', path], text=True)
+ changes = []
+ rpaths = set(re.findall(r'cmd LC_RPATH\s+cmdsize \d+\s+path (.*?) \(offset', header))
+ for value in sorted(rpaths):
+ if value.startswith('/') and not value.startswith(('/usr/lib/', '/System/Library/')):
+ changes.extend(['-delete_rpath', value])
+ deps = set(re.findall(r'cmd LC_(?:LOAD_DYLIB|LOAD_WEAK_DYLIB|REEXPORT_DYLIB|LAZY_LOAD_DYLIB|LOAD_UPWARD_DYLIB)\s+cmdsize \d+\s+name (.*?) \(offset', header))
+ for value in sorted(deps):
+ if value.startswith('/') and not value.startswith(('/usr/lib/', '/System/Library/')):
+ candidates = [path.parent / Path(value).name,
+ output / 'mysql/lib' / Path(value).name]
+ target = next((p for p in candidates if p.exists()), None)
+ if target is None:
+ raise ValueError(f'Cannot relocate external native dependency {path.name}: {value}')
+ changes.extend(['-change', value, '@loader_path/' + os.path.relpath(target, path.parent)])
+ # PyTorch loads its package globally but sibling wheel extensions should
+ # have explicit contained paths rather than depending on import order.
+ torch = output / 'python/lib/python3.11/site-packages/torch/lib'
+ if any(value.startswith('@rpath/libtorch') or value == '@rpath/libc10.dylib' for value in deps):
+ contained = '@loader_path/' + os.path.relpath(torch, path.parent)
+ if contained not in rpaths:
+ changes.extend(['-add_rpath', contained])
+ if changes:
+ run(['/usr/bin/install_name_tool', *changes, path], capture_output=True)
+ # ARM executables need valid signatures after relocation. This is
+ # build-time local signing; Developer ID signing follows in CI.
+ run(['/usr/bin/codesign', '--force', '--sign', '-', path], capture_output=True)
+ modified.append(path.relative_to(output).as_posix())
+ return modified
+
+
+def dependency_inventory(output):
+ """Retain package metadata and native archive license texts, without build paths."""
+ script = '''import importlib.metadata as m,json
+print(json.dumps(sorted([{'name':d.metadata['Name'],'version':d.version,
+ 'license':d.metadata.get('License-Expression') or d.metadata.get('License') or 'see distribution metadata'}
+ for d in m.distributions()],key=lambda d:d['name'].lower())))'''
+ python = json.loads(subprocess.check_output([output / 'python/bin/python3.11', '-c', script], text=True))
+ lock = json.loads((output / 'mysql-lock.json').read_text())
+ packages = lock['platforms']['darwin-arm64']['packages']
+ copied = []
+ for package in packages:
+ archive = ROOT / '.rieke-runtime/mysql-packages' / package['url'].rsplit('/', 1)[-1]
+ if not archive.is_file() or digest(archive) != package['sha256']:
+ raise ValueError(f'Native license archive missing or checksum mismatch: {package["name"]}')
+ with zipfile.ZipFile(archive) as compressed:
+ info = next(name for name in compressed.namelist() if name.startswith('info-') and name.endswith('.tar.zst'))
+ data = subprocess.check_output([output / 'mysql/bin/zstd', '--decompress', '--stdout'],
+ input=compressed.read(info))
+ with tarfile.open(fileobj=io.BytesIO(data)) as tar:
+ for member in tar.getmembers():
+ if not member.isfile() or not member.name.startswith('info/licenses/'):
+ continue
+ relative = Path(member.name).relative_to('info/licenses')
+ if relative.is_absolute() or '..' in relative.parts:
+ raise ValueError('Native license archive path escape')
+ target = output / 'licenses/native' / package['name'] / relative
+ target.parent.mkdir(parents=True, exist_ok=True)
+ with tar.extractfile(member) as source, target.open('wb') as out:
+ shutil.copyfileobj(source, out)
+ copied.append(target.relative_to(output).as_posix())
+ (output / 'dependency-inventory.json').write_text(json.dumps(
+ {'python_distributions': python, 'native_packages': packages,
+ 'native_license_files': copied}, indent=2) + '\n')
+
+
+def refresh_native_license_inventory(output, root=ROOT):
+ """Fill archives lacking notices from exact upstream versions/hash pins."""
+ root, output = Path(root), Path(output)
+ pin = root / 'desktop/native-license-sources.json'
+ sources = json.loads(pin.read_text())
+ if sources.get('format') != 'rieke-native-license-sources' or sources.get('version') != 1:
+ raise ValueError('Unrecognized native license source lock')
+ inventory_path = output / 'dependency-inventory.json'
+ value = json.loads(inventory_path.read_text())
+ versions = {package['name']: package['version'] for package in value['native_packages']}
+ coverage = {name: [] for name in versions}
+ files = set(value['native_license_files'])
+ for relative in files:
+ parts = Path(relative).parts
+ if len(parts) > 2 and parts[:2] == ('licenses', 'native') and parts[2] in coverage:
+ coverage[parts[2]].append(relative)
+ cache = root / 'desktop/build/license-cache'
+ cache.mkdir(parents=True, exist_ok=True)
+ for source in sources['sources']:
+ names = source.get('applies_to', [source['package']])
+ if any(versions.get(name) != source['version'] for name in names):
+ raise ValueError('Native license source version differs from bundled dependency')
+ if not source['url'].startswith('https://raw.githubusercontent.com/') or not re.fullmatch('[a-f0-9]{64}', source['sha256']):
+ raise ValueError('Native license source must use an exact SHA-256 pin')
+ name = source['file']
+ if Path(name).name != name or name in ('.', '..'):
+ raise ValueError('Unsafe native license filename')
+ cached = cache / name
+ if not cached.exists():
+ with urlopen(source['url'], timeout=30) as response:
+ data = response.read(1024 * 1024)
+ if hashlib.sha256(data).hexdigest() != source['sha256']:
+ raise ValueError('Upstream native license text checksum mismatch')
+ cached.write_bytes(data)
+ if cached.is_symlink() or digest(cached) != source['sha256']:
+ raise ValueError('Cached native license text checksum mismatch')
+ target = output / 'licenses/native' / source['package'] / name
+ target.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy2(cached, target)
+ relative = target.relative_to(output).as_posix()
+ files.add(relative)
+ for package in names:
+ coverage[package].append(relative)
+ # ICU's archive installs its notice in the runtime share tree itself.
+ icu = output / 'mysql/share/icu' / versions.get('icu', 'missing') / 'LICENSE'
+ if icu.is_file():
+ relative = icu.relative_to(output).as_posix()
+ files.add(relative)
+ coverage['icu'].append(relative)
+ if any(not notices for notices in coverage.values()):
+ raise ValueError('Bundled native dependency is missing its license notice')
+ value['native_license_files'] = sorted(files)
+ value['native_license_coverage'] = {name: sorted(set(notices)) for name, notices in coverage.items()}
+ value['native_license_sources'] = sources['sources']
+ inventory_path.write_text(json.dumps(value, indent=2) + '\n')
+ shutil.copy2(pin, output / 'native-license-sources.json')
+
+
+def exclude_optional_features(output):
+ """Omit the debugger PID injection feature, which desktop never exposes.
+
+ debugpy's normal package, metadata and licenses remain pinned and installed.
+ This production profile does not ship a debugger attach-to-existing-process
+ interface. Its optional helper has a higher deployment floor than the app.
+ """
+ output = Path(output)
+ helper = output / OPTIONAL_PID_ATTACH
+ original_sha256 = None
+ if helper.is_file():
+ original_sha256 = digest(helper)
+ helper.unlink()
+ elif (output / 'build-receipt.json').exists():
+ previous = json.loads((output / 'build-receipt.json').read_text())
+ for entry in previous.get('excluded_optional_features', []):
+ if entry.get('path') == OPTIONAL_PID_ATTACH:
+ original_sha256 = entry.get('original_sha256')
+ if not original_sha256:
+ raise ValueError('Optional debugger helper exclusion has no pinned build provenance')
+ features = [{'path': OPTIONAL_PID_ATTACH, 'distribution': 'debugpy',
+ 'purpose': 'Attach a debugger to an existing process PID',
+ 'reason': 'Desktop scientific UI exposes no debugger PID attach feature; helper requires macOS 15',
+ 'original_sha256': original_sha256}]
+ inventory = output / 'dependency-inventory.json'
+ if inventory.exists():
+ value = json.loads(inventory.read_text())
+ value['excluded_optional_features'] = features
+ inventory.write_text(json.dumps(value, indent=2) + '\n')
+ return features
+
+
+def build(output, skip_frontend=False):
+ spec = json.loads((ROOT / 'desktop/python-runtime.json').read_text())
+ if sys.platform != spec['platform'] or os.uname().machine != 'arm64':
+ raise ValueError('Initial desktop runtime build supports macOS Apple Silicon only')
+ parser_spec = json.loads((ROOT / 'python/workspace-source.json').read_text())
+ checkout = ROOT / '.rieke-runtime/retinanalysis'
+ if not checkout.exists():
+ checkout = ROOT / 'desktop/build/parser-source'
+ if not checkout.exists():
+ run(['git', 'clone', '--no-checkout', parser_spec['repository'], checkout])
+ run(['git', '-C', checkout, 'checkout', '--detach', parser_spec['commit']])
+ run(['git', '-C', checkout, 'submodule', 'update', '--init', '--recursive'])
+ checkout = verify_checkout(checkout, parser_spec)
+ if parser_spec['python'] != spec['python_version']:
+ raise ValueError('Source and desktop Python pins differ')
+ output.mkdir(parents=True, exist_ok=True)
+ cache = ROOT / 'desktop/build/cache'
+ cache.mkdir(parents=True, exist_ok=True)
+ archive = download_python(spec, cache)
+ interpreter = output / 'python/bin/python3.11'
+ if not interpreter.exists():
+ with tarfile.open(archive) as tar:
+ for member in tar.getmembers():
+ # Python >=3.12 data filter not available in the source runtime.
+ target = output / member.name
+ if not target.resolve().is_relative_to(output.resolve()):
+ raise ValueError('Standalone Python archive path escape')
+ if member.issym() or member.islnk():
+ linked = target.parent / member.linkname if member.issym() else output / member.linkname
+ if not linked.resolve().is_relative_to(output.resolve()):
+ raise ValueError('Standalone Python archive link escape')
+ tar.extractall(output)
+ env = dict(os.environ, PYTHONDONTWRITEBYTECODE='1')
+ run(['uv', 'pip', 'install', '--python', interpreter, '--system', '--require-hashes',
+ '-r', ROOT / 'python/workspace-runtime.lock', '-r', ROOT / 'desktop/requirements.lock'], env=env)
+ with tempfile.TemporaryDirectory(prefix='parser-wheel-', dir=cache) as temporary:
+ staged = Path(temporary) / 'parser'
+ stage_tracked_parser(checkout, staged)
+ patch = patch_parser(staged)
+ run([interpreter, '-m', 'hatchling', 'build', '-t', 'wheel'], cwd=staged, env=env)
+ wheel = next((staged / 'dist').glob('*.whl'))
+ parser_wheel = cache / wheel.name
+ shutil.copy2(wheel, parser_wheel)
+ # Build native parser utilities from pinned submodule, never editable installs.
+ utilities = staged / parser_spec['utilities']
+ run([interpreter, 'setup.py', 'bdist_wheel', '--dist-dir', cache], cwd=utilities, env=env)
+ vision_wheel = next(cache.glob('vision_utils-1.1.1-*.whl'))
+ run(['uv', 'pip', 'install', '--python', interpreter, '--system', '--no-deps', '--reinstall',
+ parser_wheel, vision_wheel], env=env)
+ prefix = output / 'mysql'
+ if not prefix.exists():
+ if not (ROOT / '.rieke-runtime/mysql').exists():
+ install_mysql_runtime(ROOT)
+ mysql = mysql_runtime(ROOT)
+ shutil.copytree(mysql['root'], prefix, symlinks=True,
+ ignore=shutil.ignore_patterns('conda-meta', 'include', 'pkgconfig', 'cmake', '*.a', '*.la'))
+ # Keep source provenance and licenses without embedding conda build-user history.
+ shutil.copy2(ROOT / 'python/workspace-mysql-runtime.json', output / 'mysql-lock.json')
+ shutil.copy2(ROOT / 'desktop/python-runtime.json', output / 'python-lock.json')
+ licenses = output / 'licenses'
+ licenses.mkdir(exist_ok=True)
+ shutil.copy2(checkout / 'LICENSE.txt', licenses / 'retinanalysis.txt')
+ vision_license = checkout / parser_spec['submodule'] / 'LICENSE'
+ if vision_license.exists():
+ shutil.copy2(vision_license, licenses / 'vision-utils.txt')
+ site = output / 'python/lib/python3.11/site-packages'
+ # Console script absolute shebangs and installation receipts are build artifacts,
+ # not runtime authority. Runtime starts the interpreter and registered Python entry.
+ for path in (output / 'python/bin').iterdir():
+ if path.is_file() and not path.is_symlink() and path.read_bytes()[:2] == b'#!':
+ path.unlink()
+ for path in (output / 'python/bin').iterdir():
+ if path.is_symlink() and not path.exists():
+ path.unlink()
+ for pattern in ('direct_url.json', 'uv_cache.json', 'uv_build.json'):
+ for path in site.rglob(pattern):
+ path.unlink()
+ for path in output.rglob('__pycache__'):
+ shutil.rmtree(path)
+ view = output / 'parser/src'
+ view.mkdir(parents=True, exist_ok=True)
+ link = view / 'retinanalysis'
+ if not link.exists():
+ link.symlink_to('../../python/lib/python3.11/site-packages/retinanalysis', target_is_directory=True)
+ frontend = ROOT / 'workspace-app/dist' if skip_frontend else build_frontend()
+ copy_application(ROOT, output, frontend)
+ relocated = relocate_native(output)
+ dependency_inventory(output)
+ refresh_native_license_inventory(output)
+ exclusions = exclude_optional_features(output)
+ receipt = {'python_archive_sha256': spec['sha256'], 'parser_source_commit':
+ subprocess.check_output(['git', '-C', checkout, 'rev-parse', 'HEAD'], text=True).strip(),
+ 'parser_patch': patch, 'parser_wheel_sha256': digest(parser_wheel),
+ 'vision_wheel_sha256': digest(vision_wheel),
+ 'python_lock_sha256': digest(ROOT / 'python/workspace-runtime.lock'),
+ 'desktop_requirements_sha256': digest(ROOT / 'desktop/requirements.lock')}
+ receipt['relocated_native_files'] = relocated
+ receipt['excluded_optional_features'] = exclusions
+ (output / 'build-receipt.json').write_text(json.dumps(receipt, indent=2) + '\n')
+ run([sys.executable, ROOT / 'tools/desktop_runtime_manifest.py', '--runtime', output, '--write'])
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--output', type=Path, default=ROOT / 'desktop/build/runtime')
+ parser.add_argument('--skip-frontend', action='store_true')
+ parser.add_argument('--refresh-application', action='store_true')
+ args = parser.parse_args()
+ if args.refresh_application:
+ frontend = ROOT / 'workspace-app/dist' if args.skip_frontend else build_frontend()
+ copy_application(ROOT, args.output.resolve(), frontend)
+ refresh_native_license_inventory(args.output.resolve())
+ run([sys.executable, ROOT / 'tools/desktop_runtime_manifest.py', '--runtime', args.output.resolve(), '--write'])
+ else:
+ build(args.output.resolve(), args.skip_frontend)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/desktop_no_docker_smoke.py b/tools/desktop_no_docker_smoke.py
new file mode 100644
index 0000000..853adee
--- /dev/null
+++ b/tools/desktop_no_docker_smoke.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+"""Exercise a read-only desktop candidate with Docker execution/socket access denied.
+
+Fresh project/test state only. Never stops or queries the user's Docker daemon.
+Negative-control attempts are recorded separately from the scientific workload.
+"""
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import tempfile
+
+from desktop_artifact_e2e import NETWORK_GUARD
+from desktop_backend_smoke import run as backend_run
+
+
+DOCKER_GUARD = r'''
+import json,os,shlex,shutil,socket,subprocess
+def _record(kind,**fields):
+ entry=json.dumps(dict(kind=kind,pid=os.getpid(),**fields))+'\n'
+ fd=os.open(os.environ['RIEKE_NO_DOCKER_AUDIT'],os.O_WRONLY|os.O_CREAT|os.O_APPEND,0o600)
+ try:os.write(fd,entry.encode())
+ finally:os.close(fd)
+_record('guard_loaded',docker_cli_unavailable=shutil.which('docker') is None,
+ sanitized_path=os.environ.get('PATH')=='/usr/bin:/bin')
+_connect=socket.socket.connect
+_connect_ex=socket.socket.connect_ex
+def _unix_allowed(sock,address):
+ if sock.family!=socket.AF_UNIX:return
+ value=os.fsdecode(address)
+ # The only Unix transport needed by the tested production backend is its
+ # owned native MySQL socket. All other Unix sockets, including Docker aliases,
+ # are unavailable to Python and its import workers in this test.
+ if os.path.basename(value)=='mysql.sock' and os.path.basename(os.path.dirname(value)).startswith('rieke-mysql-'):
+ _record('native_mysql_socket_allowed');return
+ _record('unix_socket_denied',docker_socket=('docker' in value.lower()))
+ raise OSError('No-Docker test denies non-MySQL Unix socket access')
+def connect(sock,address):
+ _unix_allowed(sock,address);return _connect(sock,address)
+def connect_ex(sock,address):
+ _unix_allowed(sock,address);return _connect_ex(sock,address)
+socket.socket.connect=connect
+socket.socket.connect_ex=connect_ex
+_popen=subprocess.Popen
+def popen(args,*positional,**options):
+ tokens=shlex.split(args) if isinstance(args,str) else [os.fsdecode(a) for a in args]
+ candidates=tokens if options.get('shell') else tokens[:1]
+ if any(os.path.basename(token) in {'docker','docker-compose','compose','podman'} for token in candidates):
+ _record('docker_process_denied');raise OSError('No-Docker test denies container CLI execution')
+ if tokens:_record('process_allowed',executable=os.path.basename(tokens[0]))
+ return _popen(args,*positional,**options)
+subprocess.Popen=popen
+'''
+
+
+CONTROLS = r'''
+import socket,subprocess,shutil
+assert shutil.which('docker') is None
+for operation in ('connect','connect_ex'):
+ with socket.socket(socket.AF_UNIX) as client:
+ try:getattr(client,operation)('/var/run/docker.sock')
+ except OSError:pass
+ else:raise AssertionError('Docker socket guard inactive')
+for command in (['docker','version'],['docker-compose','version'],['compose','version']):
+ try:subprocess.run(command,check=True,capture_output=True)
+ except OSError:pass
+ else:raise AssertionError('Container command guard inactive')
+'''
+
+
+def events(path):
+ return [json.loads(line) for line in path.read_text().splitlines()] if path.exists() else []
+
+
+def counts(items):
+ kinds={entry['kind'] for entry in items}
+ return {kind:sum(entry['kind']==kind for entry in items) for kind in sorted(kinds)}
+
+
+def run(args):
+ resources=args.resources.resolve(strict=True)
+ runtime=resources/'runtime'
+ manifest_path=runtime/'runtime-manifest.json'
+ manifest=json.loads(manifest_path.read_text())
+ scratch=Path(tempfile.mkdtemp(prefix='rieke-no-docker-'))
+ guard=scratch/'guard';guard.mkdir()
+ (guard/'sitecustomize.py').write_text(NETWORK_GUARD+'\n'+DOCKER_GUARD)
+ control_log=scratch/'control.jsonl';work_log=scratch/'workload.jsonl'
+ home=scratch/'control-home';home.mkdir()
+ env={'HOME':str(home),'PATH':'/usr/bin:/bin','LANG':'en_US.UTF-8','PYTHONPATH':str(guard),
+ 'PYTHONDONTWRITEBYTECODE':'1','PYTHONNOUSERSITE':'1','RIEKE_NO_DOCKER_AUDIT':str(control_log)}
+ subprocess.run([str(runtime/'python/bin/python3.11'),'-B','-c',CONTROLS],env=env,cwd=home,
+ check=True,capture_output=True,timeout=20)
+ original=subprocess.Popen
+ owned_states=set()
+ def guarded_spawn(command,*positional,**options):
+ child_env=dict(options.get('env') or os.environ)
+ child_env.update(PYTHONPATH=str(guard),RIEKE_NO_DOCKER_AUDIT=str(work_log),
+ HTTP_PROXY='http://127.0.0.1:9',HTTPS_PROXY='http://127.0.0.1:9',
+ NO_PROXY='localhost,127.0.0.1,::1')
+ options['env']=child_env
+ if '--user-state' in command:
+ state=Path(command[command.index('--user-state')+1]).parent
+ if state.name.startswith('rieke-desktop-smoke-'):owned_states.add(state)
+ return original(command,*positional,**options)
+ backend_output=scratch/'backend.json'
+ error=None
+ try:
+ subprocess.Popen=guarded_spawn
+ backend_run(resources,backend_output,recording=args.recording.resolve(strict=True))
+ except (Exception,SystemExit) as failure:
+ error=type(failure).__name__
+ finally:subprocess.Popen=original
+ controls=events(control_log);work=events(work_log)
+ backend=json.loads(backend_output.read_text()) if backend_output.exists() else {}
+ denied=[entry for entry in work if entry['kind'] in {'docker_process_denied','unix_socket_denied'}]
+ loaded=[entry for entry in work if entry['kind']=='guard_loaded']
+ control_ok=(counts(controls).get('docker_process_denied')==3 and counts(controls).get('unix_socket_denied')==2)
+ passed=bool(control_ok and backend.get('passed') and loaded and not denied and not error
+ and all(e['docker_cli_unavailable'] and e['sanitized_path'] for e in loaded))
+ report={'format':'rieke-desktop-no-docker-e2e','version':1,'passed':passed,
+ 'production_ready':False,'local_host_only':True,'user_app_untouched':True,
+ 'host_docker_untouched':True,'resources_reference_kind':'built-candidate',
+ 'runtime_manifest_sha256':hashlib.sha256(manifest_path.read_bytes()).hexdigest(),
+ 'runtime_identity':{k:manifest[k] for k in ('application_version','platform','architecture','python_version','mysql_version')},
+ 'negative_controls':{'passed':control_ok,'counters':counts(controls),
+ 'denied_before_actual_exec_or_connect':True},
+ 'workload':{'counters':counts(work),'docker_or_other_unix_attempts':len(denied),
+ 'all_guarded_python_processes_hide_docker_cli':bool(loaded) and all(e['docker_cli_unavailable'] for e in loaded),
+ 'non_mysql_unix_sockets_denied':True,'external_network_denied':True,
+ 'fresh_home_and_sanitized_path':bool(loaded) and all(e['sanitized_path'] for e in loaded)},
+ 'scientific_backend':backend,
+ 'limits':['Local host only; this does not replace clean-machine or signed-release qualification.',
+ 'Python process/socket interception plus exact native MySQL binary path; no kernel sandbox claim.']}
+ if error:report['error_type']=error
+ args.output.parent.mkdir(parents=True,exist_ok=True)
+ args.output.write_text(json.dumps(report,indent=2)+'\n')
+ print(json.dumps(report,indent=2))
+ if passed:
+ for state in owned_states:shutil.rmtree(state)
+ shutil.rmtree(scratch)
+ else:print('Private diagnostics retained:',scratch)
+ return 0 if passed else 1
+
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--resources',type=Path,default=Path(__file__).resolve().parents[1]/'desktop/dist/mac-arm64/Rieke OS.app/Contents/Resources')
+ parser.add_argument('--recording',type=Path,required=True)
+ parser.add_argument('--output',type=Path,default=Path(__file__).resolve().parents[1]/'docs/dev/desktop-no-docker-e2e.json')
+ raise SystemExit(run(parser.parse_args()))
diff --git a/tools/desktop_release.py b/tools/desktop_release.py
new file mode 100644
index 0000000..cee195c
--- /dev/null
+++ b/tools/desktop_release.py
@@ -0,0 +1,200 @@
+#!/usr/bin/env python3
+"""Fail-closed desktop baseline, artifact evidence and serialized promotion controls.
+
+This tool cannot create scientific qualification evidence; reviewed real-device
+receipts must be supplied independently against the exact candidate hashes.
+"""
+from __future__ import annotations
+import argparse
+import base64
+import hashlib
+import json
+from pathlib import Path
+import re
+import subprocess
+from urllib.request import Request, urlopen
+
+ROOT = Path(__file__).resolve().parents[1]
+REPOSITORY = 'maxwellsdm1867/Rieke-OS'
+REQUIREMENTS = [f'R{index:02}' for index in range(1, 13)]
+
+
+def run(*arguments):
+ return subprocess.check_output(arguments, text=True).strip()
+
+
+def version(value):
+ if not isinstance(value, str) or not re.fullmatch(r'(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)', value):
+ raise ValueError('A stable version is required')
+ return tuple(map(int, value.split('.')))
+
+
+def baseline(tag, repository):
+ if repository != REPOSITORY or not re.fullmatch(r'v\d+\.\d+\.\d+', tag):
+ raise ValueError('Desktop release requires the canonical repository and stable version tag')
+ release = json.loads((ROOT / 'rieke-release.json').read_text())
+ frontend = json.loads((ROOT / 'workspace-app/package.json').read_text())
+ desktop = json.loads((ROOT / 'desktop/package.json').read_text())
+ if release['version'] != tag[1:] or any(item['version'] != release['version'] for item in (frontend, desktop)):
+ raise ValueError('Release, frontend, desktop and tag versions differ')
+ version(release['version'])
+ if release['repository'] != REPOSITORY or release['channel'] != 'stable':
+ raise ValueError('Desktop release provider or channel differs')
+ if run('git', '-C', str(ROOT), 'status', '--porcelain'):
+ raise ValueError('Desktop release requires a clean reviewed checkout')
+ commit = run('git', '-C', str(ROOT), 'rev-parse', 'HEAD')
+ if run('git', '-C', str(ROOT), 'rev-parse', tag + '^{commit}') != commit:
+ raise ValueError('Desktop build must use the exact reviewed tag commit')
+ return {'format': 'rieke-desktop-baseline', 'version': 1, 'source_commit': commit,
+ 'application_version': release['version'], 'repository': REPOSITORY,
+ 'workspace_formats': release['workspace_formats'], 'database_compatibility': release['database_compatibility']}
+
+
+def digest(file, algorithm='sha256'):
+ with file.open('rb') as handle:
+ return hashlib.file_digest(handle, algorithm).digest()
+
+
+def artifact_inventory(directory):
+ result = {}
+ for file in sorted(directory.iterdir()):
+ if file.is_symlink():
+ raise ValueError('Release artifact must not be a symlink')
+ if file.is_file() and (file.suffix in {'.dmg', '.zip', '.blockmap'} or file.name == 'latest-mac.yml'):
+ result[file.name] = {'sha256': digest(file).hex(), 'size': file.stat().st_size}
+ if not any(name.endswith('.dmg') for name in result) or not any(name.endswith('.zip') for name in result) or 'latest-mac.yml' not in result:
+ raise ValueError('Complete DMG, updater ZIP and latest-mac.yml are required')
+ return result
+
+
+def verify_metadata(directory, expected_version):
+ # Read electron-builder's own YAML parser from the exact desktop lockfile.
+ script = "const fs=require('fs'),yaml=require('js-yaml');process.stdout.write(JSON.stringify(yaml.load(fs.readFileSync(process.argv[1],'utf8'))))"
+ metadata = json.loads(subprocess.check_output(['node', '-e', script, str(directory / 'latest-mac.yml')], cwd=ROOT / 'desktop', text=True))
+ if metadata.get('version') != expected_version or not isinstance(metadata.get('files'), list):
+ raise ValueError('Updater metadata has wrong version or missing files')
+ found_zip = False
+ for entry in metadata['files']:
+ name = entry.get('url')
+ if not isinstance(name, str) or Path(name).name != name or not re.fullmatch(r'[A-Za-z0-9._-]+', name):
+ raise ValueError('Updater asset name is unsafe')
+ file = directory / name
+ if not file.is_file() or file.is_symlink() or entry.get('sha512') != base64.b64encode(digest(file, 'sha512')).decode():
+ raise ValueError('Updater asset hash differs from final bytes')
+ if entry.get('size') != file.stat().st_size:
+ raise ValueError('Updater asset size differs from final bytes')
+ found_zip |= name.endswith('.zip') and 'arm64' in name
+ if not found_zip:
+ raise ValueError('Updater metadata must contain the macOS arm64 ZIP')
+ selected = next((entry for entry in metadata['files'] if entry['url'] == metadata.get('path')), None)
+ if selected is None or selected.get('sha512') != metadata.get('sha512') or not selected['url'].endswith('.zip'):
+ raise ValueError('Legacy updater fields disagree with selected ZIP metadata')
+ return metadata
+
+
+def validate_evidence(evidence, artifacts, expected_version):
+ if evidence.get('format') != 'rieke-desktop-qualification' or evidence.get('version') != 1:
+ raise ValueError('Real desktop qualification evidence is required')
+ if evidence.get('application_version') != expected_version or evidence.get('platform') != 'darwin' or evidence.get('architecture') != 'arm64':
+ raise ValueError('Qualification identity differs from candidate')
+ if evidence.get('artifacts') != artifacts:
+ raise ValueError('Qualification was not run against these exact artifact hashes')
+ if not re.fullmatch(r'[a-f0-9]{40}', evidence.get('source_commit', '')):
+ raise ValueError('Qualification requires reviewed source provenance')
+ gates = evidence.get('requirements', {})
+ for identifier in REQUIREMENTS:
+ gate = gates.get(identifier, {})
+ if gate.get('passed') is not True or not gate.get('receipts') or not isinstance(gate['receipts'], list):
+ raise ValueError(f'{identifier} has no reviewed real-artifact qualification receipt')
+ for receipt in gate['receipts']:
+ if not isinstance(receipt, dict) or not re.fullmatch(r'[a-f0-9]{64}', receipt.get('sha256', '')) or not receipt.get('description'):
+ raise ValueError(f'{identifier} receipt is incomplete')
+ return evidence
+
+
+def gh_json(*arguments, payload=None):
+ command = ['gh', 'api', *arguments]
+ if payload is not None:
+ command += ['--input', '-']
+ output = subprocess.check_output(command, input=json.dumps(payload) if payload is not None else None, text=True)
+ return json.loads(output)
+
+
+def promote(directory, tag, evidence_path):
+ expected_version = tag.removeprefix('v')
+ version(expected_version)
+ artifacts = artifact_inventory(directory)
+ verify_metadata(directory, expected_version)
+ evidence = validate_evidence(json.loads(evidence_path.read_text()), artifacts, expected_version)
+ remote_tag = gh_json(f'repos/{REPOSITORY}/git/ref/tags/{tag}')['object']
+ if remote_tag['type'] == 'tag':
+ remote_tag = gh_json(remote_tag['url'])['object']
+ if remote_tag.get('type') != 'commit' or remote_tag.get('sha') != evidence['source_commit']:
+ raise ValueError('Qualification source commit differs from canonical release tag')
+ releases = gh_json(f'repos/{REPOSITORY}/releases?per_page=100')
+ published = [release for release in releases if not release['draft'] and not release['prerelease'] and re.fullmatch(r'v\d+\.\d+\.\d+', release['tag_name'])]
+ if any(version(release['tag_name'][1:]) > version(expected_version) for release in published):
+ raise ValueError('A newer stable version is already published')
+ release = next((item for item in releases if item['tag_name'] == tag), None)
+ if release is None:
+ release = gh_json(f'repos/{REPOSITORY}/releases', '-X', 'POST', payload={'tag_name': tag, 'target_commitish': evidence['source_commit'], 'name': 'Rieke OS ' + tag,
+ 'draft': True, 'prerelease': False, 'make_latest': 'false'})
+ if release['prerelease']:
+ raise ValueError('Stable and prerelease channels cannot share a release')
+ existing = {asset['name']: asset for asset in gh_json(f"repos/{REPOSITORY}/releases/{release['id']}/assets?per_page=100")}
+ for name, record in artifacts.items():
+ if name in existing:
+ asset_digest = existing[name].get('digest')
+ if existing[name]['size'] != record['size'] or asset_digest != 'sha256:' + record['sha256']:
+ raise ValueError('Retry would replace existing release bytes; promotion rejected')
+ elif not release['draft']:
+ raise ValueError('Published release is incomplete; do not mutate its artifact set')
+ else:
+ subprocess.run(['gh', 'release', 'upload', tag, str(directory / name), '--repo', REPOSITORY], check=True)
+ if release['draft']:
+ gh_json(f"repos/{REPOSITORY}/releases/{release['id']}", '-X', 'PATCH', payload={'draft': False, 'make_latest': 'false'})
+ # Expose the complete release without moving latest, then verify final public
+ # download URLs with no authentication before changing stable selection.
+ for name, record in artifacts.items():
+ url = f'https://github.com/{REPOSITORY}/releases/download/{tag}/{name}'
+ hasher, size = hashlib.sha256(), 0
+ with urlopen(Request(url, headers={'User-Agent': 'Rieke-OS-release-verifier'}), timeout=60) as response:
+ if not response.url.startswith('https://'):
+ raise ValueError('Public release asset redirected outside HTTPS')
+ while chunk := response.read(1024 * 1024):
+ hasher.update(chunk); size += len(chunk)
+ if size != record['size'] or hasher.hexdigest() != record['sha256']:
+ raise ValueError('Public artifact bytes differ; latest has not been promoted')
+ gh_json(f"repos/{REPOSITORY}/releases/{release['id']}", '-X', 'PATCH', payload={'make_latest': 'true'})
+ return {'published': tag, 'artifacts': artifacts}
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('command', choices=['baseline', 'inventory', 'verify', 'promote'])
+ parser.add_argument('--tag')
+ parser.add_argument('--repository', default=REPOSITORY)
+ parser.add_argument('--directory', type=Path, default=ROOT / 'desktop/dist')
+ parser.add_argument('--evidence', type=Path)
+ parser.add_argument('--output', type=Path)
+ args = parser.parse_args()
+ if args.command == 'baseline':
+ result = baseline(args.tag, args.repository)
+ elif args.command == 'inventory':
+ result = {'format': 'rieke-desktop-artifacts', 'version': 1, 'artifacts': artifact_inventory(args.directory)}
+ elif args.command == 'verify':
+ artifacts = artifact_inventory(args.directory)
+ verify_metadata(args.directory, args.tag.removeprefix('v'))
+ result = validate_evidence(json.loads(args.evidence.read_text()), artifacts, args.tag.removeprefix('v'))
+ else:
+ if args.repository != REPOSITORY:
+ raise ValueError('Publication is restricted to the canonical desktop repository')
+ result = promote(args.directory, args.tag, args.evidence)
+ serialized = json.dumps(result, indent=2) + '\n'
+ if args.output:
+ args.output.write_text(serialized)
+ print(serialized)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/desktop_runtime_audit.py b/tools/desktop_runtime_audit.py
new file mode 100644
index 0000000..4631eee
--- /dev/null
+++ b/tools/desktop_runtime_audit.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+"""Read-only preflight of a source runtime before desktop packaging.
+
+This does not execute Python packages, open projects, start SQL, or certify an
+installer. An absence of known static blockers is not production qualification.
+"""
+import argparse
+import configparser
+import datetime
+import json
+import os
+from pathlib import Path, PureWindowsPath
+import subprocess
+import tempfile
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def _inside(path, root):
+ try:
+ return Path(path).resolve().is_relative_to(root)
+ except (OSError, RuntimeError):
+ return False
+
+
+def _absolute(value):
+ return Path(value).is_absolute() or PureWindowsPath(value).is_absolute()
+
+
+def _json(path):
+ if path.stat().st_size > 1024 * 1024:
+ raise ValueError('Metadata exceeds the preflight size limit')
+ value = json.loads(path.read_text())
+ if not isinstance(value, dict):
+ raise ValueError('Metadata must be a JSON object')
+ return value
+
+
+def audit_source_runtime(application):
+ root = Path(application).resolve(strict=True)
+ issues = []
+ examined = {'pth_files': 0, 'runtime_symlinks': 0}
+
+ def issue(code, work, path, message):
+ issues.append({'code': code, 'work': work, 'path': str(path.relative_to(root)), 'message': message})
+
+ def metadata(path, work):
+ if not _inside(path, root):
+ issue('metadata_path_escape', work, path, 'Metadata redirects outside the application; it was not read.')
+ return {}
+ try:
+ return _json(path)
+ except (OSError, ValueError, TypeError) as error:
+ issue('metadata_unavailable', work, path, f'Cannot inspect metadata ({type(error).__name__}).')
+ return {}
+
+ runtime = root / '.rieke-runtime'
+ config = metadata(runtime / 'runtime.json', 'E02')
+ source = metadata(root / 'python/workspace-source.json', 'E01')
+ release = metadata(root / 'rieke-release.json', 'E01')
+ package = metadata(root / 'workspace-app/package.json', 'E01')
+ if release.get('version') and package.get('version') != release['version']:
+ issue('version_mismatch', 'E01', root / 'rieke-release.json', 'Application and frontend versions differ.')
+ if release and release.get('repository') != 'maxwellsdm1867/Rieke-OS':
+ issue('release_repository_mismatch', 'E01', root / 'rieke-release.json', 'Desktop publication must use Rieke-OS.')
+ if source.get('commit') and config.get('retinanalysis_commit') != source['commit']:
+ issue('parser_receipt_mismatch', 'E02', runtime / 'runtime.json', 'Parser runtime receipt differs from the source pin.')
+
+ python_value = config.get('python', '.rieke-runtime/venv/bin/python')
+ parser_value = config.get('retinanalysis', '.rieke-runtime/retinanalysis')
+ for key, value in (('python', python_value), ('retinanalysis', parser_value)):
+ if not isinstance(value, str) or not value:
+ issue('invalid_runtime_path', 'E02', runtime / 'runtime.json', f'Invalid {key} path.')
+ continue
+ path = root / value
+ if _absolute(value):
+ issue('absolute_runtime_config', 'E02', runtime / 'runtime.json', f'{key} uses an absolute build-machine path.')
+ if not _inside(path, root):
+ issue('external_runtime_path', 'E02', runtime / 'runtime.json', f'{key} resolves outside the application.')
+ if not path.exists():
+ issue('runtime_component_missing', 'E02', runtime / 'runtime.json', f'{key} is missing.')
+
+ # Inspect only application runtime code, never project/database directories.
+ # Refuse to traverse a runtime directory that redirects outside the app.
+ if runtime.is_dir() and _inside(runtime, root):
+ for path in sorted(runtime.rglob('*')):
+ if path.is_symlink():
+ examined['runtime_symlinks'] += 1
+ if not _inside(path, root):
+ issue('external_runtime_symlink', 'E02', path, 'Link points outside the application or forms a loop.')
+ elif os.path.isabs(os.readlink(path)):
+ issue('absolute_runtime_symlink', 'E02', path, 'Absolute link must be made relocatable during the build.')
+ if path.name == 'pyvenv.cfg' and path.is_file() and _inside(path, root):
+ for line in path.read_text().splitlines():
+ key, separator, value = line.partition('=')
+ if separator and key.strip() == 'home' and _absolute(value.strip()):
+ issue('absolute_venv_home', 'E02', path, 'Venv records an absolute interpreter home; do not copy it as a portable runtime.')
+ if path.suffix == '.pth' and path.is_file() and _inside(path, root):
+ examined['pth_files'] += 1
+ for line in path.read_text().splitlines():
+ line = line.strip()
+ if not line or line.startswith(('#', 'import ', 'import\t')):
+ continue
+ if _absolute(line):
+ issue('absolute_python_import_path', 'E02', path, 'Python imports use an absolute editable/dependency path.')
+ elif not _inside(path.parent / line, root):
+ issue('external_python_import_path', 'E02', path, 'Relative Python import path escapes the application.')
+ for path in sorted(runtime.rglob('config.ini')):
+ if not _inside(path, root):
+ continue
+ if 'retinanalysis' not in path.parts:
+ continue
+ parser_config = configparser.ConfigParser(interpolation=None)
+ try:
+ parser_config.read(path)
+ except (OSError, UnicodeError, configparser.Error):
+ issue('parser_config_unreadable', 'E03', path, 'Parser configuration cannot be inspected.')
+ continue
+ values = list(parser_config.defaults().values())
+ values.extend(value for section in parser_config.sections() for value in parser_config[section].values())
+ if any(_absolute(value) for value in values):
+ issue('parser_config_build_paths', 'E03', path, 'Parser configuration embeds instance-specific paths inside application code.')
+ else:
+ issue('unsafe_or_missing_runtime', 'E02', runtime, 'Runtime is missing or redirects outside the application; it was not traversed.')
+
+ parser_path = root / parser_value if isinstance(parser_value, str) else None
+ if parser_path and _inside(parser_path, root) and (parser_path / '.git').exists():
+ try:
+ revision = subprocess.check_output(['git', '-C', str(parser_path), 'rev-parse', 'HEAD'], text=True, stderr=subprocess.DEVNULL, timeout=10).strip()
+ allowed = [source.get('commit'), *source.get('compatible_local_commits', [])]
+ if revision not in allowed:
+ issue('parser_source_mismatch', 'E02', runtime / 'runtime.json', 'Actual parser checkout differs from the allowed source revisions.')
+ except (OSError, subprocess.SubprocessError):
+ issue('parser_source_unverified', 'E02', runtime / 'runtime.json', 'Source checkout provenance could not be inspected.')
+ else:
+ issue('parser_source_unverified', 'E02', runtime / 'runtime.json', 'This source audit cannot verify parser checkout provenance; a packaged wheel needs a build receipt instead.')
+
+ return {'format': 'rieke-desktop-source-preflight', 'version': 1,
+ 'checked_at': datetime.datetime.now(datetime.timezone.utc).isoformat(),
+ 'status': 'blocked' if issues else 'no_known_static_blockers',
+ 'production_ready': False, 'project_code_executed': False,
+ 'versions': {'application': release.get('version'), 'frontend': package.get('version'),
+ 'python_pin': source.get('python'), 'parser_pin': source.get('commit')},
+ 'examined': examined, 'blockers': issues,
+ 'not_validated': ['portable package assembly', 'dynamic library closure', 'real imports and database operations',
+ 'signing and notarization', 'Electron startup and shutdown', 'clean-machine install', 'signed old-to-new update']}
+
+
+def main(argv=None):
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--application', type=Path, default=ROOT)
+ parser.add_argument('--output', type=Path)
+ args = parser.parse_args(argv)
+ report = audit_source_runtime(args.application)
+ text = json.dumps(report, indent=2) + '\n'
+ if args.output:
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ temporary = None
+ try:
+ with tempfile.NamedTemporaryFile('w', dir=args.output.parent, delete=False) as handle:
+ temporary = Path(handle.name)
+ handle.write(text)
+ os.replace(temporary, args.output)
+ finally:
+ if temporary:
+ temporary.unlink(missing_ok=True)
+ print(text, end='')
+ return 1 if report['blockers'] else 0
+
+
+if __name__ == '__main__':
+ raise SystemExit(main())
diff --git a/tools/desktop_runtime_manifest.py b/tools/desktop_runtime_manifest.py
new file mode 100644
index 0000000..3f55972
--- /dev/null
+++ b/tools/desktop_runtime_manifest.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+"""Inventory and audit unsigned desktop resources; this is not qualification."""
+from __future__ import annotations
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import subprocess
+
+ROOT = Path(__file__).resolve().parents[1]
+MACHO = {b'\xfe\xed\xfa\xce', b'\xce\xfa\xed\xfe', b'\xfe\xed\xfa\xcf',
+ b'\xcf\xfa\xed\xfe', b'\xca\xfe\xba\xbe', b'\xbe\xba\xfe\xca'}
+
+
+def inventory(runtime):
+ runtime = Path(runtime).resolve(strict=True)
+ result = {}
+ for path in sorted(runtime.rglob('*')):
+ relative = path.relative_to(runtime).as_posix()
+ if relative in ('runtime-manifest.json', 'runtime-audit.json'):
+ continue
+ if path.is_symlink():
+ target = os.readlink(path)
+ if Path(target).is_absolute() or not path.resolve().is_relative_to(runtime):
+ raise ValueError(f'Runtime link escapes packaged resources: {relative}')
+ if not path.exists():
+ raise ValueError(f'Runtime link is broken: {relative}')
+ result[relative] = {'symlink': target}
+ elif path.is_file():
+ with path.open('rb') as handle:
+ sha = hashlib.file_digest(handle, 'sha256').hexdigest()
+ result[relative] = {'sha256': sha, 'size': path.stat().st_size,
+ 'executable': bool(path.stat().st_mode & 0o111)}
+ return result
+
+
+def native_audit(runtime):
+ problems, binaries, minimum = [], 0, set()
+ for path in sorted(runtime.rglob('*')):
+ if path.is_symlink() or not path.is_file():
+ continue
+ with path.open('rb') as handle:
+ if handle.read(4) not in MACHO:
+ continue
+ binaries += 1
+ relative = path.relative_to(runtime).as_posix()
+ header = subprocess.check_output(['/usr/bin/otool', '-l', path], text=True)
+ rpaths = re.findall(r'cmd LC_RPATH\s+cmdsize \d+\s+path (.*?) \(offset', header)
+ for value in rpaths:
+ if value.startswith('/') and not value.startswith(('/usr/lib/', '/System/Library/')):
+ problems.append({'path': relative, 'code': 'external_rpath', 'value': value})
+ minimum.update(re.findall(r'\bminos ([\d.]+)', header))
+ minimum.update(re.findall(r'cmd LC_VERSION_MIN_MACOSX\s+cmdsize \d+\s+version ([\d.]+)', header))
+ # otool -L includes the dylib's own ID and per-architecture headers.
+ # Those are not loaded dependencies. Audit the actual load commands.
+ deps = re.findall(r'cmd LC_(?:LOAD_DYLIB|LOAD_WEAK_DYLIB|REEXPORT_DYLIB|LAZY_LOAD_DYLIB|LOAD_UPWARD_DYLIB)\s+cmdsize \d+\s+name (.*?) \(offset', header)
+ for value in deps:
+ if value.startswith(('/usr/lib/', '/System/Library/')):
+ continue
+ if value.startswith('/'):
+ problems.append({'path': relative, 'code': 'external_native_dependency', 'value': value})
+ elif value.startswith('@loader_path/'):
+ if not (path.parent / value[len('@loader_path/'):]).exists():
+ problems.append({'path': relative, 'code': 'missing_native_dependency', 'value': value})
+ elif value.startswith('@rpath/'):
+ filename = value[len('@rpath/'):]
+ # Direct rpaths plus parent runtime library search; loader inheritance
+ # must still be exercised in the relocation smoke qualification.
+ candidates = [Path(r.replace('@loader_path', str(path.parent))) / filename for r in rpaths]
+ candidates.extend(parent / 'lib' / filename for parent in path.parents if parent.is_relative_to(runtime))
+ if not any(candidate.exists() for candidate in candidates):
+ problems.append({'path': relative, 'code': 'unresolved_native_dependency', 'value': value})
+ return {'native_binaries': binaries, 'declared_macos_minimums': sorted(minimum),
+ 'problems': problems, 'production_ready': False,
+ 'not_validated': ['Developer ID signing and notarization', 'clean-machine installation',
+ 'signed old-to-new update', 'complete scientific workflow qualification']}
+
+
+def make_manifest(runtime, root=ROOT):
+ release = json.loads((runtime / 'application/rieke-release.json').read_text())
+ source = json.loads((runtime / 'application/python/workspace-source.json').read_text())
+ mysql = json.loads((runtime / 'mysql-lock.json').read_text())
+ python = json.loads((runtime / 'python-lock.json').read_text())
+ commit = subprocess.check_output(['git', '-C', root, 'rev-parse', 'HEAD'], text=True).strip()
+ dirty = bool(subprocess.check_output(['git', '-C', root, 'status', '--porcelain'], text=True).strip())
+ receipt = json.loads((runtime / 'build-receipt.json').read_text())
+ return {'format': 'rieke-desktop-runtime', 'version': 1,
+ 'application_version': release['version'], 'source_commit': commit,
+ 'source_dirty': dirty, 'parser_commit': source['commit'],
+ 'platform': 'darwin', 'architecture': 'arm64', 'python_version': python['python_version'],
+ 'mysql_version': mysql['mysql_version'], 'workspace_formats': release['workspace_formats'],
+ 'database_compatibility': release['database_compatibility'],
+ 'excluded_optional_features': receipt.get('excluded_optional_features', []),
+ 'resources': inventory(runtime)}
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--runtime', type=Path, default=ROOT / 'desktop/build/runtime')
+ parser.add_argument('--write', action='store_true')
+ parser.add_argument('--rehash-existing', action='store_true',
+ help='Preserve build identity and refresh hashes after nested signing')
+ args = parser.parse_args()
+ runtime = args.runtime.resolve(strict=True)
+ if args.rehash_existing:
+ manifest = json.loads((runtime / 'runtime-manifest.json').read_text())
+ if manifest.get('format') != 'rieke-desktop-runtime' or manifest.get('version') != 1:
+ raise ValueError('Cannot rehash an unrecognized manifest')
+ manifest['resources'] = inventory(runtime)
+ else:
+ manifest = make_manifest(runtime)
+ audit = native_audit(runtime)
+ minimums = audit['declared_macos_minimums']
+ if minimums:
+ manifest['minimum_macos_version'] = max(minimums, key=lambda value: tuple(int(part) for part in value.split('.')))
+ if args.write:
+ (runtime / 'runtime-manifest.json').write_text(json.dumps(manifest, indent=2) + '\n')
+ (runtime / 'runtime-audit.json').write_text(json.dumps(audit, indent=2) + '\n')
+ else:
+ existing = json.loads((runtime / 'runtime-manifest.json').read_text())
+ if existing['resources'] != manifest['resources']:
+ raise ValueError('Packaged resources differ from runtime manifest')
+ print(json.dumps({'resources': len(manifest['resources']), **audit}, indent=2))
+ if audit['problems']:
+ raise SystemExit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/desktop_runtime_smoke.py b/tools/desktop_runtime_smoke.py
new file mode 100644
index 0000000..1ff6747
--- /dev/null
+++ b/tools/desktop_runtime_smoke.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+"""Exercise a relocated runtime under a fresh HOME without development PATH.
+
+This local-host test does not substitute for another user/clean macOS machine.
+"""
+import argparse
+import json
+import os
+from pathlib import Path
+import subprocess
+import tempfile
+
+ROOT = Path(__file__).resolve().parents[1]
+PROBE = r'''
+import json,os,sys
+from pathlib import Path
+runtime=Path(sys.argv[1]).resolve()
+sys.path.insert(0,str(runtime/'application/python'))
+os.environ['RIEKE_DESKTOP_RUNTIME']=str(runtime)
+from workspace_bootstrap import prepare_desktop_parser_config,probe_runtime,validate_desktop_runtime
+from workspace_mysql_runtime import mysql_runtime
+manifest=validate_desktop_runtime(runtime,verify_hashes=True)
+prepare_desktop_parser_config(Path.home()/'state')
+parser=probe_runtime(str(runtime/'python/bin/python3.11'),str(runtime/'parser'))
+mysql=mysql_runtime(runtime/'application')
+validate_desktop_runtime(runtime,verify_hashes=True)
+print('RIEKE_SMOKE='+json.dumps({'parser':parser,'mysql_version':mysql['version'],
+ 'resource_hashes_before_and_after':'unchanged','application_version':manifest['application_version']}))
+'''
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--runtime', type=Path, default=ROOT/'desktop/build/runtime')
+ parser.add_argument('--output', type=Path)
+ args=parser.parse_args()
+ runtime=args.runtime.resolve(strict=True)
+ with tempfile.TemporaryDirectory(prefix='Rieke relocated app ') as temp:
+ base=Path(temp)
+ # APFS clones preserve symlinks and permissions without duplicate storage.
+ subprocess.run(['/bin/cp','-cR',str(runtime),str(base/'runtime')],check=True)
+ home=base/'fresh user state';home.mkdir()
+ env={'HOME':str(home),'PATH':'/usr/bin:/bin','TMPDIR':temp,'LANG':'en_US.UTF-8',
+ 'PYTHONDONTWRITEBYTECODE':'1','PYTHONNOUSERSITE':'1', 'MPLCONFIGDIR':str(home/'matplotlib')}
+ result=subprocess.run([str(base/'runtime/python/bin/python3.11'),'-c',PROBE,str(base/'runtime')],
+ env=env,cwd=home,text=True,capture_output=True,check=True,timeout=180)
+ value=json.loads(next(line.split('=',1)[1] for line in result.stdout.splitlines() if line.startswith('RIEKE_SMOKE=')))
+ report={'format':'rieke-desktop-runtime-local-smoke','version':1,'status':'passed',
+ 'local_host_only':True,'production_ready':False,'isolated_home':True,
+ 'development_path_excluded':True,'relocated_path_with_spaces':True,**value,
+ 'not_validated':['different user or clean machine','native database/scientific workflows',
+ 'Developer ID and notarization','signed old-to-new update']}
+ text=json.dumps(report,indent=2)+'\n'
+ if args.output:
+ args.output.parent.mkdir(parents=True,exist_ok=True);args.output.write_text(text)
+ print(text,end='')
+
+if __name__=='__main__':main()
diff --git a/tools/desktop_scientific_e2e.py b/tools/desktop_scientific_e2e.py
new file mode 100644
index 0000000..1017384
--- /dev/null
+++ b/tools/desktop_scientific_e2e.py
@@ -0,0 +1,714 @@
+#!/usr/bin/env python3
+"""Broad native scientific/failure E2E against an exact packaged runtime.
+
+Only creates isolated scratch projects. Public receipts omit recordings, project
+paths, capabilities and credentials. Signed updates are a separate release gate.
+"""
+from __future__ import annotations
+import argparse
+import concurrent.futures
+import contextlib
+import hashlib
+import json
+import os
+from pathlib import Path
+import secrets
+import signal
+import socketserver
+import http.server
+import shutil
+import socket
+import sqlite3
+import subprocess
+import tempfile
+import threading
+import time
+from urllib.error import HTTPError
+from urllib.parse import urlsplit
+from urllib.request import Request, urlopen
+import uuid
+
+from desktop_backend_smoke import inventory
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def sha(path):
+ with Path(path).open('rb') as stream:
+ return hashlib.file_digest(stream, 'sha256').hexdigest()
+
+
+class Harness:
+ def __init__(self, resources, state=None):
+ self.resources = Path(resources).resolve(strict=True)
+ self.runtime = self.resources / 'runtime'
+ self.manifest = json.loads((self.runtime / 'runtime-manifest.json').read_text())
+ self.state = Path(state or tempfile.mkdtemp(prefix='rieke-scientific-e2e-')).resolve()
+ self.state.mkdir(exist_ok=True)
+ (self.state / 'home').mkdir(exist_ok=True)
+ self.capability = secrets.token_hex(32)
+ self.session_id = str(uuid.uuid4())
+ self.env = {'HOME': str(self.state / 'home'), 'PATH': '/usr/bin:/bin', 'LANG': 'en_US.UTF-8',
+ 'TMPDIR': os.environ.get('TMPDIR', '/tmp'), 'PYTHONDONTWRITEBYTECODE': '1', 'PYTHONNOUSERSITE': '1',
+ 'RIEKE_DESKTOP_CAPABILITY': self.capability, 'RECORDING_WORKSPACE_ROOT': str(self.state / 'projects')}
+ self.python = self.runtime / 'python/bin/python3.11'
+ self.process = None
+ self.known_projects = set()
+ self.cases = []
+ self.failures = []
+
+ def python_code(self, code, *arguments, timeout=180):
+ environment = dict(self.env)
+ executable = self.python
+ application = self.runtime / 'application'
+ environment.update(RIEKE_DESKTOP_MODE='1', RIEKE_DESKTOP_RUNTIME=str(self.runtime),
+ RIEKE_DESKTOP_USER_STATE=str(self.state / 'user-state'),
+ RIEKE_PARSER_CONFIG=str(self.state / 'user-state/parser/config.ini'))
+ command = [str(executable), '-B', '-c',
+ "import sys; sys.path.insert(0, sys.argv[1]); " + code,
+ str(application / 'python'), *map(str, arguments)]
+ result = subprocess.run(command, cwd=self.state, env=environment,
+ capture_output=True, text=True, timeout=timeout)
+ if result.returncode:
+ (self.state / 'helper-failure.log').write_text(result.stdout + result.stderr)
+ raise RuntimeError('Bundled helper failed; private diagnostic retained')
+ markers = [line for line in result.stdout.splitlines() if line.startswith('E2E=')]
+ return json.loads(markers[-1][4:]) if markers else None
+
+ def start(self, timeout=180):
+ with socket.socket() as listener:
+ listener.bind(('127.0.0.1', 0))
+ self.port = listener.getsockname()[1]
+ self.origin = f'http://127.0.0.1:{self.port}'
+ self.log = (self.state / 'backend.log').open('ab')
+ command = [str(self.python), '-B', str(self.runtime / 'application/python/workspace_desktop.py'),
+ '--resources', str(self.resources), '--manifest', str(self.runtime / 'runtime-manifest.json'),
+ '--user-state', str(self.state / 'user-state'), '--session-id', self.session_id, '--port', str(self.port)]
+ self.process = subprocess.Popen(command, cwd=self.state, env=self.env,
+ stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=self.log)
+ packet, bound = {}, threading.Event()
+ def reader():
+ for line in iter(self.process.stdout.readline, b''):
+ self.log.write(line)
+ self.log.flush()
+ if line.startswith(b'RIEKE_DESKTOP_BOUND='):
+ packet.update(json.loads(line.split(b'=', 1)[1]))
+ bound.set()
+ break
+ threading.Thread(target=reader, daemon=True).start()
+ expected = {'pid': self.process.pid, 'session_id': self.session_id, 'port': self.port,
+ 'application_version': self.manifest['application_version'], 'source_commit': self.manifest['source_commit'],
+ 'workspace_formats': self.manifest['workspace_formats'], 'database_compatibility': self.manifest['database_compatibility']}
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ if self.process.poll() is not None:
+ raise RuntimeError('Packaged root exited before private bind receipt')
+ if not bound.wait(.2):
+ continue
+ if any(packet.get(key) != value for key, value in expected.items()):
+ raise RuntimeError('Private bind receipt identity mismatch')
+ try:
+ health = self.call('/api/desktop/health')
+ if not health.get('ready') or any(health.get(key) != value for key, value in expected.items() if key != 'port'):
+ raise RuntimeError('Root health identity mismatch')
+ return health
+ except OSError:
+ time.sleep(.1)
+ raise RuntimeError('Packaged root readiness timeout')
+
+ def call(self, route, body=None, *, origin=None, timeout=30, headers=None, raw=False, expect=None):
+ request_headers = {'X-Rieke-Desktop-Capability': self.capability, 'X-Workspace-Request': '1', 'Content-Type': 'application/json'}
+ request_headers.update(headers or {})
+ data = None if body is None else json.dumps(body).encode()
+ try:
+ with urlopen(Request((origin or self.origin) + route, data=data, headers=request_headers), timeout=timeout) as response:
+ value = response.read()
+ if expect and response.status != expect:
+ raise AssertionError('Expected failure status was accepted')
+ return value if raw else json.loads(value)
+ except HTTPError as error:
+ value = error.read()
+ if expect == error.code:
+ return json.loads(value) if value.startswith(b'{') else {'status': error.code}
+ (self.state / 'http-failure.log').write_bytes(value)
+ raise
+
+ def create(self, name):
+ directory = self.state / name
+ project = self.call('/api/projects', {'name': name, 'project_directory': str(directory)})['project']
+ self.known_projects.add(directory)
+ return project
+
+ def open(self, project):
+ opened = self.call('/api/projects/open-folder', {'directory': project['path']}, timeout=180)
+ origin = opened['url'].rstrip('/')
+ authorization = self.call('/api/desktop/authorize-project', {'port': urlsplit(origin).port})
+ if (authorization['health']['project_uuid'] != project['uuid']
+ or authorization['health']['project_path'] != str(Path(project['path']).resolve())
+ or authorization['record'].get('bound') is not True):
+ raise AssertionError('Project listener identity changed')
+ return origin, authorization['record']
+
+ def close_project(self, origin, project):
+ self.call('/api/project/close', {}, origin=origin, timeout=45)
+ deadline = time.monotonic() + 35
+ while time.monotonic() < deadline:
+ records = self.call('/api/desktop/health')['services']
+ if not any(record['project_path'] == project['path'] for record in records):
+ owner = json.loads((Path(project['path']) / 'database/native-owner.json').read_text())
+ assert owner['clean_shutdown'] is True
+ return
+ time.sleep(.2)
+ raise AssertionError('Project service did not exit')
+
+ def import_recording(self, origin, source, *, expected='complete', check_busy=False):
+ job = self.call('/api/imports', {'source_path': str(source)}, origin=origin)
+ if check_busy:
+ self.call('/api/desktop/drain', {}, expect=409, timeout=65)
+ deadline = time.monotonic() + 900
+ while time.monotonic() < deadline:
+ current = next(item for item in self.call('/api/jobs', origin=origin)['jobs'] if item['job_uuid'] == job['job_uuid'])
+ if current['status'] in ('complete', 'complete_with_warnings', 'duplicate', 'failed', 'interrupted'):
+ if expected == 'complete' and current['status'] not in ('complete', 'complete_with_warnings'):
+ raise AssertionError('Recording import failed')
+ if expected != 'complete' and current['status'] != expected:
+ raise AssertionError('Unexpected terminal import state')
+ return current
+ time.sleep(.3)
+ raise TimeoutError('Import completion deadline exceeded')
+
+ def export(self, origin, protocol_uuid, export_format, *, epoch=None):
+ revision = self.call('/api/protocols/' + protocol_uuid, origin=origin)['query_revision']
+ body = {'format': export_format, 'name': 'Isolated E2E export', 'query_revision': revision}
+ if epoch:
+ body['filters'] = {'epoch_uuid': epoch}
+ return self.call('/api/protocols/' + protocol_uuid + '/exports', body, origin=origin, timeout=120)
+
+ def check(self, name, function):
+ started = time.monotonic()
+ try:
+ evidence = function()
+ self.cases.append({'name': name, 'passed': True, 'seconds': round(time.monotonic() - started, 3),
+ **({'evidence': evidence} if evidence else {})})
+ print('PASS', name, flush=True)
+ return evidence
+ except Exception as error:
+ self.cases.append({'name': name, 'passed': False, 'seconds': round(time.monotonic() - started, 3),
+ 'error_type': type(error).__name__})
+ self.failures.append({'case': name, 'error': repr(error)})
+ (self.state / 'failures-private.json').write_text(json.dumps(self.failures, indent=2))
+ print('FAIL', name, type(error).__name__, flush=True)
+ return None
+
+ def cleanup_database(self, project, *, source=False):
+ self.python_code("from workspace_native_mysql import stop_native_database; import json; "
+ "stop_native_database(sys.argv[2]); print('E2E='+json.dumps({'stopped':True}))", project, source=source)
+
+ def stop(self):
+ if not self.process or self.process.poll() is not None:
+ return
+ result = self.call('/api/desktop/drain', {}, timeout=90)
+ assert result['ready'] is True
+ self.call('/api/desktop/stop', {})
+ self.process.wait(timeout=35)
+ assert self.process.returncode == 0
+ self.log.close()
+
+
+TRACE_CHECK = r'''
+import json, h5py, numpy as np, sqlite3, scipy.io, zipfile, io
+from workspace_sqlite import load_frozen_record
+spec=json.loads(open(sys.argv[2]).read())
+def raw(source,pointer,start,count):
+ with h5py.File(source,'r') as h5:
+ return h5[pointer.rstrip('/')+'/data'][start:start+count]['quantity'].astype(float)
+api=np.array(spec['api']['values'],dtype=float)
+expected=raw(spec['reference']['source_reference']['path'],spec['stream']['h5_path'],spec['api']['start'],spec['api']['count'])
+assert np.array_equal(api,expected), 'HTTP trace samples changed'
+assert spec['api']['sample_rate']==spec['stream']['sample_rate']
+assert spec['api']['units']==spec['stream']['units']
+with sqlite3.connect('file:'+sys.argv[3]+'?mode=ro',uri=True) as db:
+ assert db.execute('PRAGMA integrity_check').fetchone()[0]=='ok'
+ assert db.execute('PRAGMA foreign_key_check').fetchall()==[]
+ assert db.execute('SELECT epoch_uuid FROM epochs').fetchall()==[(spec['reference']['epoch_uuid'],)]
+ frozen=load_frozen_record(db,spec['reference']['epoch_uuid'])
+ pointer=db.execute("SELECT s.source_path,st.h5_path,st.sample_rate,st.units FROM streams st JOIN epochs e USING(epoch_uuid) JOIN sources s USING(source_sha256) WHERE st.stream_uuid=?",(spec['stream']['uuid'],)).fetchone()
+ assert np.array_equal(raw(pointer[0],pointer[1],spec['api']['start'],spec['api']['count']),expected)
+ assert pointer[2:]==(spec['stream']['sample_rate'],spec['stream']['units'])
+ assert frozen['parameters']==spec['reference']['parameters']
+mat_checked=False
+if len(sys.argv)>4:
+ with zipfile.ZipFile(sys.argv[4]) as archive:
+ mat=scipy.io.loadmat(io.BytesIO(archive.read('recordings.mat')),simplify_cells=True)
+ found=[]
+ def walk(obj):
+ if isinstance(obj,dict):
+ if obj.get('h5_uuid')==spec['reference']['epoch_uuid'] and 'responses' in obj:
+ found.append(obj)
+ for value in obj.values():walk(value)
+ elif isinstance(obj,(list,tuple,np.ndarray)):
+ for value in np.asarray(obj,dtype=object).reshape(-1):walk(value)
+ walk(mat)
+ assert len(found)==1, 'MAT epoch membership changed'
+ responses=found[0]['responses'];responses=[responses] if isinstance(responses,dict) else list(responses)
+ stream=next(item for item in responses if item['h5_uuid']==spec['stream']['uuid'])
+ assert np.array_equal(raw(stream['h5_file'],stream['h5_path'],spec['api']['start'],spec['api']['count']),expected)
+ assert stream['sample_rate']==spec['stream']['sample_rate'] and stream['units']==spec['stream']['units']
+ assert json.loads(found[0]['source_metadata_json'])['parameters']==spec['reference']['parameters']
+ mat_checked=True
+print('E2E='+json.dumps({'sample_count':len(expected),'exact_numeric_match':True,'sqlite_integrity':True,'mat_lazy_trace_match':mat_checked}))
+'''
+
+
+def exercise(h, recording):
+ project = h.create('primary-science')
+ origin, record = h.open(project)
+ baseline = h.import_recording(origin, recording, check_busy=True)
+ overview = h.call('/api/overview', origin=origin)
+ counts = {key: overview['counts'][key] for key in ('sources', 'cells', 'epochs', 'protocols')}
+ h.cases.append({'name': 'real-import-and-active-writer-drain-deferral', 'passed': True, 'evidence': counts})
+ protocol_uuid = overview['protocols'][0]['protocol_uuid']
+ page = h.call('/api/protocols/' + protocol_uuid + '/epochs?limit=3', origin=origin)
+ first, second = page['epochs'][:2]
+ epoch_uuid = first['epoch_uuid']
+ source_hash = sha(recording)
+ artifacts = {}
+
+ def duplicate():
+ same = h.import_recording(origin, recording, expected='duplicate')
+ renamed = h.state / 'renamed-recording.h5'
+ shutil.copy2(recording, renamed)
+ moved = h.import_recording(origin, renamed, expected='duplicate')
+ after = h.call('/api/overview', origin=origin)
+ assert {key: after['counts'][key] for key in counts} == counts
+ assert same['catalog_committed'] is False and moved['catalog_committed'] is False
+ return {'same_bytes_and_renamed_bytes_do_not_duplicate_catalog': True}
+ h.check('dedup-original-and-renamed-H5', duplicate)
+
+ def failed_import():
+ damaged = h.state / 'truncated-input.h5'
+ with recording.open('rb') as source:
+ damaged.write_bytes(source.read(1024))
+ job = h.import_recording(origin, damaged, expected='failed')
+ after = h.call('/api/overview', origin=origin)
+ assert {key: after['counts'][key] for key in counts} == counts
+ assert job['catalog_committed'] is False
+ return {'failed_parse_did_not_commit_catalog': True, 'previous_catalog_preserved': True}
+ h.check('truncated-import-preserves-catalog-and-releases-writer', failed_import)
+
+ def annotations():
+ profile = h.call('/api/annotation-profiles', {'display_name': 'Isolated scientific E2E'}, origin=origin)
+ h.call('/api/annotation-profiles/selected', {'profile_uuid': profile['profile_uuid']}, origin=origin)
+ h.call('/api/annotations', {'target_kind': 'epoch', 'target_uuids': [epoch_uuid],
+ 'profile_uuid': profile['profile_uuid'], 'tags_add': ['e2e-epoch'], 'expected_revisions': {epoch_uuid: 0}}, origin=origin)
+ h.call('/api/annotations', {'target_kind': 'cell', 'target_uuids': [first['cell_uuid']],
+ 'profile_uuid': profile['profile_uuid'], 'tags_add': ['e2e-cell'], 'expected_revisions': {first['cell_uuid']: 0}}, origin=origin)
+ one = h.call('/api/explore/run', {'predicate': {'field': 'annotations/epoch/tags', 'operator': 'contains', 'value': 'e2e-epoch'},
+ 'splits': 'date,protocol,cell'}, origin=origin)
+ inherited = h.call('/api/explore/run', {'predicate': {'field': 'annotations/effective/tags', 'operator': 'contains', 'value': 'e2e-cell'},
+ 'splits': 'date,protocol,cell'}, origin=origin)
+ expected_cell = next(cell['epochs'] for cell in overview['cells'] if cell['cell_uuid'] == first['cell_uuid'])
+ assert one['matched_count'] == 1 and inherited['matched_count'] == expected_cell
+ h.call('/api/annotations', {'target_kind': 'epoch', 'target_uuids': [epoch_uuid],
+ 'profile_uuid': profile['profile_uuid'], 'tags_add': ['stale-must-not-commit'], 'expected_revisions': {epoch_uuid: 0}}, origin=origin, expect=409)
+ return {'direct_tag_query_matches': 1, 'inherited_cell_tag_matches': expected_cell, 'stale_revision_rejected': True}
+ h.check('annotation-attribution-inheritance-and-query-semantics', annotations)
+
+ def current_backend_contracts():
+ fields = h.call('/api/metadata/fields', origin=origin)['fields']
+ assert fields and len({field['id'] for field in fields}) == len(fields)
+ assert all('values' not in field and 'value' not in field for field in fields)
+ h.call('/api/metadata/fields?cell_uuid=unregistered', origin=origin, expect=400)
+ ordinary = h.call('/api/protocols/' + protocol_uuid + '/epochs?limit=3', origin=origin)
+ combined = h.call('/api/protocols/' + protocol_uuid + '/epochs?limit=3&include_cells=true', origin=origin)
+ assert 'cells' not in ordinary
+ assert combined['epochs'] == ordinary['epochs'] and combined['total'] == ordinary['total']
+ assert sum(cell['epochs'] for cell in combined['cells']) == combined['total']
+ assert {row['cell_uuid'] for row in combined['epochs']} <= {cell['cell_uuid'] for cell in combined['cells']}
+ h.call('/api/protocols/' + protocol_uuid + '/epochs?include_cells=1', origin=origin, expect=400)
+ metadata = h.call('/api/metadata/status', origin=origin)
+ preparation = metadata['annotation_preparation']
+ assert metadata['status'] == 'ready' and preparation['status'] == 'ready', 'Native annotation preparation failed'
+ assert preparation['storage'] == 'native_sql'
+ deadline = time.monotonic() + 20
+ while time.monotonic() < deadline:
+ backup = h.call('/api/backup/status', origin=origin)
+ if backup['status'] == 'current' and backup['last_success'] and backup['completed_sequence'] > 0:
+ break
+ assert backup['status'] != 'degraded', 'Committed annotation backup failed'
+ time.sleep(.2)
+ else:
+ raise TimeoutError('Committed annotation backup did not become current')
+ health = h.call('/api/health', origin=origin)
+ assert health['backup']['status'] == 'current' and health['project_uuid'] == project['uuid']
+ events = h.call('/api/events?limit=1', origin=origin)
+ assert events['events'] and all('payload' not in event for event in events['events'])
+ detail = h.call('/api/events/' + events['events'][0]['event_uuid'], origin=origin)
+ assert 'payload' in detail['event']
+ return {'registered_metadata_field_count': len(fields), 'combined_epoch_cell_membership_matches': True,
+ 'native_annotation_preparation_ready': True, 'committed_backup_current': True,
+ 'event_summary_and_detail_contract': True}
+ h.check('current-metadata-fields-combined-cells-annotation-preparation-and-backup-APIs', current_backend_contracts)
+
+ def inclusion():
+ current = h.call('/api/protocols/' + protocol_uuid + '/epochs?limit=3', origin=origin)
+ excluded = next(item for item in current['epochs'] if item['epoch_uuid'] == second['epoch_uuid'])
+ h.call('/api/protocols/' + protocol_uuid + '/curation', {'epoch_uuids': [excluded['epoch_uuid']],
+ 'query_revision': current['query_revision'], 'expected_revisions': {excluded['epoch_uuid']: excluded['curation']['revision']},
+ 'changes': {'included': False}}, origin=origin)
+ protocol = h.call('/api/protocols/' + protocol_uuid, origin=origin)
+ h.call('/api/protocols/' + protocol_uuid + '/exports', {'format': 'reference-json',
+ 'query_revision': protocol['query_revision'], 'filters': {'epoch_uuid': excluded['epoch_uuid']}}, origin=origin, expect=400)
+ artifacts['membership-reference'] = h.export(origin, protocol_uuid, 'reference-json')
+ package = json.loads(Path(artifacts['membership-reference']['artifact_path']).read_text())
+ ids = {item['epoch_uuid'] for item in package['epochs']}
+ assert epoch_uuid in ids and excluded['epoch_uuid'] not in ids
+ assert len(ids) == protocol['counts']['included']
+ return {'excluded_epoch_not_exported': True, 'included_export_count': len(ids)}
+ h.check('inclusion-mask-export-membership-and-stale-protection', inclusion)
+
+ def export(format):
+ artifacts[format] = h.export(origin, protocol_uuid, format, epoch=epoch_uuid)
+ downloaded = h.call(artifacts[format]['download_url'], origin=origin, raw=True)
+ assert hashlib.sha256(downloaded).hexdigest() == artifacts[format]['artifact_sha256']
+ return {'download_matches_published_hash': True}
+ h.check('reference-export-real-artifact-download', lambda: export('reference-json'))
+ h.check('SQLite-export-real-artifact-download', lambda: export('wheeler-sqlite'))
+ h.check('MAT-export-real-artifact-download', lambda: export('epictree-mat'))
+
+ def trace():
+ package = json.loads(Path(artifacts['reference-json']['artifact_path']).read_text())
+ row = package['epochs'][0]
+ stream = next(item for item in row['streams'] if item['kind'] == 'responses' and item['sample_count'] > 50)
+ api = h.call('/api/epochs/' + epoch_uuid + '/trace?stream_uuid=' + stream['uuid'] + '&start=17&count=127', origin=origin)
+ spec = h.state / 'trace-private.json'
+ spec.write_text(json.dumps({'reference': row, 'stream': stream, 'api': api}))
+ arguments = [spec, artifacts['wheeler-sqlite']['artifact_path']]
+ if 'epictree-mat' in artifacts:
+ arguments.append(artifacts['epictree-mat']['artifact_path'])
+ return h.python_code(TRACE_CHECK, *arguments)
+ h.check('numeric-trace-fidelity-HTTP-and-frozen-export-pointers', trace)
+
+ def multiservice():
+ second_project = h.create('second-project')
+ second_origin, second_record = h.open(second_project)
+ records = h.call('/api/desktop/health')['services']
+ assert len(records) == 2 and len({item['pid'] for item in records}) == 2
+ assert h.call('/api/overview', origin=second_origin)['counts']['epochs'] == 0
+ assert h.call('/api/overview', origin=origin)['counts']['epochs'] == counts['epochs']
+ h.close_project(second_origin, second_project)
+ return {'isolated_processes': 2, 'scientific_catalog_not_shared': True}
+ h.check('simultaneous-project-services-are-owned-and-isolated', multiservice)
+
+ h.close_project(origin, project)
+ origin, record = h.open(project)
+ def reopen():
+ annotations = h.call('/api/annotations/read', {'target_kind': 'epoch', 'target_uuids': [epoch_uuid]}, origin=origin)
+ assert 'e2e-epoch' in [item['tag'] for item in annotations['targets'][epoch_uuid]['tags']]
+ current = h.call('/api/protocols/' + protocol_uuid + '/epochs?limit=3', origin=origin)
+ assert next(item for item in current['epochs'] if item['epoch_uuid'] == second['epoch_uuid'])['curation']['included'] is False
+ for artifact in artifacts.values():
+ assert sha(artifact['artifact_path']) == artifact['artifact_sha256']
+ assert sha(recording) == source_hash
+ return {'annotations_mask_exports_and_original_checksum_preserved': True}
+ h.check('project-restart-preserves-scientific-state-and-original', reopen)
+ h.close_project(origin, project)
+
+ def portability():
+ target = h.state / 'portable-package'
+ submitted = h.call('/api/projects/prepare-transfer', {'directory': project['path'], 'destination': str(target)})
+ h.call('/api/desktop/drain', {}, expect=409, timeout=65)
+ deadline = time.monotonic() + 600
+ while time.monotonic() < deadline:
+ job = h.call('/api/projects/transfers/' + submitted['job_id'])
+ if job['state'] != 'running':
+ assert job['state'] == 'complete', 'Portable preparation failed'
+ break
+ time.sleep(.3)
+ else:
+ raise TimeoutError('Portable preparation did not finish')
+ owner = json.loads((Path(project['path']) / 'database/native-owner.json').read_text())
+ if owner.get('clean_shutdown') is not True:
+ # This is a safety failure, even if the package itself is valid.
+ h.cases.append({'name': 'transfer-owned-MySQL-closes-before-job-ack', 'passed': False,
+ 'error_type': 'UnregisteredRunningDatabase'})
+ h.failures.append({'case': 'transfer-owned-MySQL-closes-before-job-ack', 'error': 'Transfer completed with native database still alive'})
+ h.cleanup_database(project['path'])
+ restored_path = h.state / 'portable-recipient'
+ submitted = h.call('/api/projects/restore-transfer', {'directory': str(target), 'destination': str(restored_path)})
+ deadline = time.monotonic() + 600
+ while time.monotonic() < deadline:
+ job = h.call('/api/projects/transfers/' + submitted['job_id'])
+ if job['state'] != 'running':
+ assert job['state'] == 'complete', 'Portable restore failed'
+ break
+ time.sleep(.3)
+ else:
+ raise TimeoutError('Portable restore did not finish')
+ restored_owner = json.loads((restored_path / 'database/native-owner.json').read_text())
+ assert restored_owner.get('clean_shutdown') is True, 'Restore database was not closed before job acknowledgement'
+ h.known_projects.add(restored_path)
+ restored_project = {'path': str(restored_path), 'uuid': project['uuid']}
+ restored_origin, restored_record = h.open(restored_project)
+ restored = h.call('/api/overview', origin=restored_origin)
+ assert {key: restored['counts'][key] for key in counts} == counts
+ tags = h.call('/api/annotations/read', {'target_kind': 'epoch', 'target_uuids': [epoch_uuid]}, origin=restored_origin)
+ assert 'e2e-epoch' in [item['tag'] for item in tags['targets'][epoch_uuid]['tags']]
+ exported = h.call('/api/exports', origin=restored_origin)['exports']
+ assert len(exported) == len(artifacts)
+ assert all(Path(item['artifact_path']).is_relative_to(restored_path) for item in exported)
+ h.close_project(restored_origin, restored_project)
+ return {'restored_counts_equal': True, 'annotations_and_exports_rebased': True, 'real_logical_backup_restore_verified': True}
+ h.check('portable-prepare-restore-saved-state-source-pointers-and-exports', portability)
+ return project, counts
+
+
+def legacy_copy(h, donor, counts):
+ """Real source SQL/data, fake read-only Docker inspect transport only."""
+ source = h.state / 'legacy-source'
+ subprocess.run(['/bin/cp', '-cR', donor['path'], str(source)], check=True)
+ native_catalog = json.loads((source / 'catalog.json').read_text())
+ source_settings = h.python_code("from workspace_project_database import ensure_project_database; "
+ "from workspace_portability import rebase_project_paths,_database_inventory; "
+ "from workspace_native_mysql import connection_parameters; import pymysql,json; "
+ "ensure_project_database(sys.argv[2]); rebase_project_paths(sys.argv[2]); "
+ "settings=connection_parameters(sys.argv[2]); connection=pymysql.connect(**settings,autocommit=True); "
+ "cursor=connection.cursor(); cursor.execute('SELECT @@hostname'); hostname=cursor.fetchone()[0]; "
+ "inventory=_database_inventory(connection); cursor.execute('SELECT COUNT(*) FROM recording_workspace.event'); history=cursor.fetchone()[0]; "
+ "connection.close(); print('E2E='+json.dumps({'settings':settings,'hostname':hostname,'inventory':inventory,'history':history}))", source)
+ native_catalog = json.loads((source / 'catalog.json').read_text())
+ legacy = {**native_catalog, 'connection': {'host': '127.0.0.1', 'port': source_settings['settings']['port'],
+ 'credential_provider': {'kind': 'docker-container-env', 'container': 'isolated-source-fixture'}}}
+ legacy.pop('managed_database', None)
+ (source / 'catalog.json').write_text(json.dumps(legacy))
+ scientific_before = inventory(source)
+ # Exclude a live server's operational files: the source's scientific SQL is
+ # compared separately by complete logical inventory below.
+ scientific_before = {key:value for key,value in scientific_before.items() if not key.startswith('database/')}
+ # AF_UNIX has a short path limit on macOS. Keep the test transport in a
+ # short owned directory; the production resolver follows the user's normal
+ # ~/.docker/run location and verifies the resulting socket's ownership.
+ socket_directory = Path(tempfile.mkdtemp(prefix='rieke-inspect-', dir='/tmp'))
+ docker_home = h.state / 'home/.docker'
+ docker_home.mkdir(parents=True, exist_ok=True)
+ (docker_home / 'run').symlink_to(socket_directory, target_is_directory=True)
+ socket_path = socket_directory / 'docker.sock'
+ requests = []
+ inspect = {'Id': 'isolated-source-fixture-identity', 'State': {'Running': True},
+ 'Config': {'Hostname': source_settings['hostname'],
+ 'Env': ['MYSQL_ROOT_PASSWORD=' + source_settings['settings']['password']]},
+ 'NetworkSettings': {'Ports': {'3306/tcp': [{'HostIp': '127.0.0.1',
+ 'HostPort': str(source_settings['settings']['port'])}]}}}
+ class Handler(http.server.BaseHTTPRequestHandler):
+ def do_GET(self):
+ requests.append((self.command,self.path))
+ assert self.path == '/containers/isolated-source-fixture/json'
+ body=json.dumps(inspect).encode(); self.send_response(200); self.send_header('Content-Length',str(len(body)));self.end_headers();self.wfile.write(body)
+ def log_message(self,*args):pass
+ class Server(socketserver.UnixStreamServer):pass
+ server=Server(str(socket_path),Handler)
+ threading.Thread(target=server.serve_forever,daemon=True).start()
+ target = h.state / 'migrated-desktop-copy'
+ try:
+ preview = h.call('/api/projects/inspect-folder', {'directory': str(source)})
+ assert preview['desktop_compatibility']['requires_migration'] is True
+ before_services = h.call('/api/desktop/health')['services']
+ rejection = h.call('/api/projects/open-folder', {'directory': str(source)}, expect=409)
+ assert rejection['code'] == 'legacy_project_requires_migration'
+ assert h.call('/api/desktop/health')['services'] == before_services
+ # Existing source lease is observed; no forced close or source mutation.
+ import fcntl
+ with (source / '.app-state-session.lock').open('r') as lease:
+ fcntl.flock(lease.fileno(), fcntl.LOCK_SH | fcntl.LOCK_NB)
+ submitted = h.call('/api/projects/migrate-source', {'directory': str(source), 'destination': str(target)})
+ deadline=time.monotonic()+30
+ while time.monotonic()=source_settings['history']>0, 'Source SQL-only history was lost'
+ protocols={item['protocol_uuid'] for item in overview['protocols']}
+ assert len(protocols)==counts['protocols']
+ # Historical exported bytes keep their sealed hashes. Active source
+ # manifests and a new export must resolve exclusively to the copy.
+ protocol = overview['protocols'][0]['protocol_uuid']
+ page = h.call('/api/protocols/'+protocol+'/epochs?limit=3',origin=origin)
+ epoch = next(item for item in page['epochs'] if item['curation']['included'])['epoch_uuid']
+ fresh = h.export(origin,protocol,'reference-json',epoch=epoch)
+ reference = json.loads(Path(fresh['artifact_path']).read_text())['epochs'][0]
+ assert Path(reference['source_reference']['path']).is_relative_to(target)
+ stream = next(item for item in reference['streams'] if item['kind']=='responses' and item['sample_count']>50)
+ api = h.call('/api/epochs/'+epoch+'/trace?stream_uuid='+stream['uuid']+'&start=17&count=127',origin=origin)
+ private_spec = _write_private_json(h.state/'migration-trace-private.json',{'reference':reference,'stream':stream,'api':api})
+ numeric = h.python_code("import json,h5py,numpy as np; spec=json.load(open(sys.argv[2])); "
+ "source=spec['reference']['source_reference']['path']; pointer=spec['stream']['h5_path']; "
+ "h5=h5py.File(source,'r'); values=h5[pointer.rstrip('/')+'/data'][17:144]['quantity']; "
+ "assert np.array_equal(np.asarray(spec['api']['values']),values); h5.close(); "
+ "print('E2E='+json.dumps({'matched_samples':len(values)}))",private_spec)
+ assert numeric['matched_samples']==127
+ h.close_project(origin,migrated)
+ return {'legacy_preflight_does_not_spawn_failed_service':True,'active_source_lease_defers_copy':True,
+ 'source_files_and_full_SQL_unchanged':True,'read_only_Docker_inspect_only':True,
+ 'scientific_counts_preserved':counts,'history_records_preserved':source_settings['history'],
+ 'export_artifacts_rebased_and_verified':len(exports),
+ 'new_export_resolves_copy_with_127_exact_trace_samples':True}
+ finally:
+ server.shutdown();server.server_close();socket_path.unlink(missing_ok=True)
+ (docker_home / 'run').unlink();socket_directory.rmdir()
+ # Restore our test fixture's native descriptor only after proving the
+ # migration itself made no source mutation, then close its owned server.
+ (source/'catalog.json').write_text(json.dumps(native_catalog))
+ h.cleanup_database(source)
+
+
+def _write_private_json(path,value):
+ path.write_text(json.dumps(value));path.chmod(0o600);return path
+
+
+def fault_recovery(h, recording):
+ """Crash our import monitor only; the scientific child finishes untouched."""
+ project = h.create('interrupted-monitor-fixture')
+ origin, record = h.open(project)
+ submitted = h.call('/api/imports', {'source_path': str(recording)}, origin=origin)
+ deadline = time.monotonic() + 90
+ while time.monotonic() < deadline:
+ job = next(item for item in h.call('/api/jobs', origin=origin)['jobs']
+ if item['job_uuid'] == submitted['job_uuid'])
+ progress = job.get('progress') or {}
+ worker_pid = progress.get('pid')
+ if type(worker_pid) is int and worker_pid != record['pid'] and progress.get('stage') not in {'complete', 'failed', 'duplicate'}:
+ break
+ time.sleep(.1)
+ else:
+ raise AssertionError('No active scientific child was observed')
+ # Verify the complete command belongs to this fixture before injecting the
+ # monitor fault. Never signal the MySQL or importer processes.
+ import fcntl
+ ownership = _write_private_json(h.state/'fault-ownership-private.json',
+ {'record':record,'worker_pid':worker_pid,'path':project['path']})
+ worker_created = h.python_code("import json,psutil,signal,os; from pathlib import Path; "
+ "spec=json.load(open(sys.argv[2])); record=spec['record']; monitor=psutil.Process(record['pid']); "
+ "assert monitor.create_time()==record['created_at']; assert str(Path(monitor.exe()).resolve())==record['executable']; "
+ "assert spec['path'] in monitor.cmdline(); worker=psutil.Process(spec['worker_pid']); "
+ "created=worker.create_time(); os.kill(record['pid'],signal.SIGTERM); print('E2E='+json.dumps(created))",ownership)
+ deadline = time.monotonic() + 10
+ while time.monotonic() < deadline:
+ if any(item.get('recovery_required') for item in h.call('/api/desktop/health')['services']
+ if item['project_uuid'] == project['uuid']):
+ break
+ time.sleep(.1)
+ rejected = h.call('/api/desktop/authorize-project', {'port': record['port']}, expect=409)
+ assert rejected.get('error')
+ h.call('/api/desktop/drain', {}, expect=409, timeout=65)
+ lease_path = Path(project['path']) / '.app-state-session.lock'
+ with lease_path.open('r') as lease:
+ try:
+ fcntl.flock(lease.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except BlockingIOError:
+ pass
+ else:
+ raise AssertionError('Importer lost its inherited session lease after monitor crash')
+ deadline = time.monotonic() + 300
+ while time.monotonic() < deadline:
+ alive = h.python_code("import json,psutil\ntry:\n"
+ " current=psutil.Process(int(sys.argv[2])); alive=current.create_time()==float(sys.argv[3]) and current.status()!=psutil.STATUS_ZOMBIE\n"
+ "except psutil.NoSuchProcess: alive=False\nprint('E2E='+json.dumps(alive))",worker_pid,worker_created)
+ if not alive:
+ break
+ time.sleep(.2)
+ else:
+ raise AssertionError('Scientific child remains active; preserve fixture for recovery')
+ with lease_path.open('r') as lease:
+ fcntl.flock(lease.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+ h.cleanup_database(project['path'])
+ assert not any(item['project_uuid'] == project['uuid'] for item in h.call('/api/desktop/health')['services'])
+ reopened, new_record = h.open(project)
+ assert new_record['pid'] != record['pid']
+ jobs = h.call('/api/jobs', origin=reopened)['jobs']
+ interrupted = next(item for item in jobs if item['job_uuid'] == submitted['job_uuid'])
+ assert interrupted['status'] == 'interrupted'
+ overview = h.call('/api/overview', origin=reopened)
+ assert overview['counts']['sources'] == 1 and overview['counts']['epochs'] == 1915
+ h.close_project(reopened, project)
+ return {'crashed_monitor_never_authorizes_reused_port': True,
+ 'active_importer_preserves_exclusive_session_lease': True,
+ 'importer_finishes_without_signal_or_force_kill': True,
+ 'interrupted_job_retained_and_committed_catalog_recovered': True,
+ 'explicit_owned_MySQL_shutdown_allows_clean_project_reopen': True}
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--resources', type=Path, default=ROOT / 'desktop/dist/mac-arm64/Rieke OS.app/Contents/Resources')
+ parser.add_argument('--recording', type=Path, default=ROOT / 'test_data/h5/2025-12-02_F.h5')
+ parser.add_argument('--output', type=Path, default=ROOT / 'docs/dev/desktop-scientific-e2e.json')
+ parser.add_argument('--state', type=Path)
+ args = parser.parse_args()
+ recording = args.recording.resolve(strict=True)
+ original = sha(recording)
+ h = Harness(args.resources, args.state)
+ before = inventory(h.runtime)
+ try:
+ h.start()
+ project, counts = exercise(h, recording)
+ h.check('full-legacy-source-copy-identity-history-and-scientific-state', lambda: legacy_copy(h, project, counts))
+ h.check('interrupted-import-monitor-lease-and-owned-service-recovery', lambda: fault_recovery(h, recording))
+ except Exception as error:
+ h.failures.append({'case': 'suite-setup-or-termination', 'error': repr(error)})
+ h.cases.append({'name': 'suite-setup-or-termination', 'passed': False, 'error_type': type(error).__name__})
+ (h.state / 'failures-private.json').write_text(json.dumps(h.failures, indent=2))
+ finally:
+ try:
+ h.stop()
+ h.cases.append({'name': 'all-owned-WSGI-project-and-root-services-drain-exit', 'passed': True})
+ except Exception as error:
+ h.cases.append({'name': 'all-owned-WSGI-project-and-root-services-drain-exit', 'passed': False, 'error_type': type(error).__name__})
+ after = inventory(h.runtime)
+ unchanged = before == after
+ source_unchanged = original == sha(recording)
+ receipt = {'format': 'rieke-desktop-scientific-e2e', 'version': 1, 'production_ready': False,
+ 'runtime_manifest_sha256': sha(h.runtime / 'runtime-manifest.json'), 'runtime_resource_count': len(h.manifest['resources']),
+ 'app_asar_sha256': sha(h.resources / 'app.asar'),
+ 'runtime_identity': {key: h.manifest[key] for key in ('application_version', 'source_commit', 'parser_commit', 'platform', 'architecture', 'python_version', 'mysql_version')},
+ 'cases': h.cases, 'packaged_resources_unchanged': unchanged, 'original_recording_unchanged': source_unchanged,
+ 'limitations': ['Unsigned developer machine; no signed update or clean-machine certification',
+ 'MATLAB runtime execution is not available; MAT structure and numeric lazy H5 pointers are checked with bundled scipy',
+ 'Legacy migration uses an isolated read-only Docker inspect transport with real MySQL SQL/H5 data; no live Docker daemon or user source database is used'],
+ 'passed': all(case['passed'] for case in h.cases) and unchanged and source_unchanged}
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ args.output.write_text(json.dumps(receipt, indent=2) + '\n')
+ print(json.dumps(receipt, indent=2))
+ print('Private diagnostics retained:', h.state)
+ raise SystemExit(0 if receipt['passed'] else 1)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/desktop_startup_failures.py b/tools/desktop_startup_failures.py
new file mode 100644
index 0000000..d915d08
--- /dev/null
+++ b/tools/desktop_startup_failures.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+"""Public startup/retry transport qualification using only owned empty fixtures."""
+import argparse
+import json
+from pathlib import Path
+
+from desktop_backend_smoke import inventory
+from desktop_scientific_e2e import Harness, ROOT, sha
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--resources', type=Path, default=ROOT / 'desktop/dist/mac-arm64/Rieke OS.app/Contents/Resources')
+ parser.add_argument('--output', type=Path, default=ROOT / 'docs/dev/desktop-startup-failures.json')
+ args = parser.parse_args()
+ harness = Harness(args.resources)
+ before = inventory(harness.runtime)
+ cases = []
+ try:
+ harness.start()
+ legacy = harness.create('owned-legacy-preflight-fixture')
+ directory = Path(legacy['path'])
+ catalog = json.loads((directory / 'catalog.json').read_text())
+ catalog.pop('managed_database', None)
+ catalog['connection'] = {'host': '127.0.0.1', 'port': 3306,
+ 'credential_provider': {'kind': 'docker-container-env', 'container': 'unavailable-test-source'}}
+ (directory / 'catalog.json').write_text(json.dumps(catalog))
+ # New project creation has not started SQL. Remove only its empty,
+ # never-opened descriptor to model the historical external profile.
+ database = directory / 'database'
+ assert set(database.iterdir()) == {database / 'service.json'}
+ (database / 'service.json').unlink()
+ fixture_before = inventory(directory)
+ records = harness.call('/api/desktop/health')['services']
+ rejected = harness.call('/api/projects/open-folder', {'directory': str(directory)}, expect=409)
+ assert rejected['code'] == 'legacy_project_requires_migration'
+ assert 'desktop copy' in rejected['error']
+ assert harness.call('/api/desktop/health')['services'] == records
+ assert fixture_before == inventory(directory)
+ assert not (harness.state / 'home/.docker').exists()
+ cases.append({'name': 'external-legacy-open-preflight-is-actionable-and-spawns-no-child', 'passed': True})
+ harness.call('/api/projects/open-folder', {'directory': str(harness.state / 'missing')}, expect=400)
+ cases.append({'name': 'missing-project-returned-as-retryable-transport-error', 'passed': True})
+ exited_pid = harness.process.pid
+ harness.stop()
+ registry = harness.state / 'user-state/desktop-services.json'
+ registry.write_text(json.dumps({'version': 1, 'services': [{
+ 'project_path': str(directory), 'project_uuid': legacy['uuid'], 'bound': False,
+ 'pid': exited_pid, 'created_at': 0, 'executable': str(harness.python.resolve())}], 'database_operations': []}))
+ harness.start()
+ assert harness.call('/api/desktop/health')['services'] == []
+ assert fixture_before == inventory(directory)
+ cases.append({'name': 'dead-bare-legacy-preflight-record-does-not-deadlock-launcher-restart', 'passed': True})
+ native = harness.create('native-after-rejected-startup')
+ origin, _ = harness.open(native)
+ assert harness.call('/api/health', origin=origin)['status'] == 'ready'
+ harness.close_project(origin, native)
+ cases.append({'name': 'native-project-opens-and-closes-after-startup-rejections', 'passed': True})
+ except Exception as error:
+ cases.append({'name': 'startup-transport-fixture', 'passed': False, 'error_type': type(error).__name__})
+ (harness.state / 'startup-failure-private.txt').write_text(repr(error))
+ finally:
+ try:
+ harness.stop()
+ except Exception as error:
+ cases.append({'name': 'owned-service-exit', 'passed': False, 'error_type': type(error).__name__})
+ unchanged = before == inventory(harness.runtime)
+ receipt = {'format': 'rieke-desktop-startup-failure-transport', 'version': 1,
+ 'application_version': harness.manifest['application_version'],
+ 'source_commit': harness.manifest['source_commit'],
+ 'runtime_manifest_sha256': sha(harness.runtime / 'runtime-manifest.json'),
+ 'app_asar_sha256': sha(harness.resources / 'app.asar'),
+ 'cases': cases, 'packaged_resources_unchanged': unchanged,
+ 'limitations': ['Dead legacy registry fixture is deliberately seeded in owned scratch state; no live user app or source database is touched'],
+ 'passed': all(case['passed'] for case in cases) and unchanged}
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ args.output.write_text(json.dumps(receipt, indent=2) + '\n')
+ print(json.dumps(receipt, indent=2))
+ return 0 if receipt['passed'] else 1
+
+
+if __name__ == '__main__':
+ raise SystemExit(main())
diff --git a/tools/desktop_test_release.py b/tools/desktop_test_release.py
new file mode 100644
index 0000000..084ac22
--- /dev/null
+++ b/tools/desktop_test_release.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+"""Create exact-byte metadata for an explicitly unsigned GitHub testing release.
+
+This is separate from production promotion and never claims Apple verification.
+"""
+from __future__ import annotations
+import argparse
+import base64
+import hashlib
+import json
+from pathlib import Path
+import plistlib
+import re
+import zipfile
+
+REPOSITORY='maxwellsdm1867/Rieke-OS'
+
+def hashes(path):
+ a,b=hashlib.sha256(),hashlib.sha512()
+ with Path(path).open('rb') as stream:
+ for block in iter(lambda:stream.read(1024*1024),b''):
+ a.update(block);b.update(block)
+ return a.hexdigest(),base64.b64encode(b.digest()).decode()
+
+def build_descriptor(bundle,archive):
+ bundle,archive=Path(bundle),Path(archive)
+ runtime=bundle/'Contents/Resources/runtime'
+ raw=(runtime/'runtime-manifest.json').read_bytes();manifest=json.loads(raw)
+ version=manifest.get('application_version')
+ if not isinstance(version,str) or not re.fullmatch(r'(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)',version):raise ValueError('Invalid application version')
+ if manifest.get('format')!='rieke-desktop-runtime' or manifest.get('version')!=1 or manifest.get('platform')!='darwin' or manifest.get('architecture')!='arm64':raise ValueError('Unsupported desktop runtime')
+ plist=plistlib.loads((bundle/'Contents/Info.plist').read_bytes())
+ if plist.get('CFBundleIdentifier')!='org.riekeos.desktop' or plist.get('CFBundleShortVersionString')!=version:raise ValueError('App identity or version differs')
+ if archive.name!=f'Rieke-OS-{version}-arm64.zip':raise ValueError('Archive name differs from version')
+ asar=hashes(bundle/'Contents/Resources/app.asar')[0]
+ with zipfile.ZipFile(archive) as z:
+ archived_manifest=z.read('Rieke OS.app/Contents/Resources/runtime/runtime-manifest.json')
+ archived_asar=hashlib.sha256(z.read('Rieke OS.app/Contents/Resources/app.asar')).hexdigest()
+ if archived_manifest!=raw or archived_asar!=asar:raise ValueError('Archive is not the inspected app')
+ sha256,sha512=hashes(archive)
+ return {'format':'rieke-desktop-test-release','version':1,'channel':'unsigned-testing','repository':REPOSITORY,
+ **{key:manifest[key] for key in ['application_version','platform','architecture','workspace_formats','database_compatibility','mysql_version','minimum_macos_version']},
+ 'archive':{'filename':archive.name,'size':archive.stat().st_size,'sha256':sha256,'sha512':sha512},
+ 'asar_sha256':asar,'runtime_manifest_sha256':hashlib.sha256(raw).hexdigest(),
+ 'source_commit':manifest.get('source_commit'),'source_dirty':manifest.get('source_dirty'),
+ 'production_ready':False}
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--app',type=Path,required=True);parser.add_argument('--archive',type=Path,required=True);parser.add_argument('--output',type=Path,required=True)
+ args=parser.parse_args();descriptor=build_descriptor(args.app,args.archive)
+ args.output.parent.mkdir(parents=True,exist_ok=True);args.output.write_text(json.dumps(descriptor,indent=2)+'\n')
+ print('Wrote unsigned test metadata for '+descriptor['application_version'])
diff --git a/workspace-app/README.md b/workspace-app/README.md
index bf962bb..3abd414 100644
--- a/workspace-app/README.md
+++ b/workspace-app/README.md
@@ -715,7 +715,9 @@ comparison with the existing Compact and VMN databases.
### Current-state recovery
-Current settings and query definitions are saved to `app-state.json`, with daily
-SQLite state snapshots under `backups/app-state/`. Ordinary edits no longer append
-a full action trail. See [storage and recovery](../docs/STORAGE_RECOVERY.md) for
-what is saved, how query-based pins are reconstructed, and the offline restore command.
+`app-state.json` points to a compact SQLite recovery database under
+`backups/app-state/`, with limited daily checkpoint history. Verified tag edits
+update changed records; frozen pins retain their exact membership. Ordinary
+edits do not append a full action trail. See
+[storage and recovery](../docs/STORAGE_RECOVERY.md) for backup contents, retention,
+older-format compatibility, and the offline restore command.
diff --git a/workspace-app/package-lock.json b/workspace-app/package-lock.json
index 1b8822e..2c56b97 100644
--- a/workspace-app/package-lock.json
+++ b/workspace-app/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "recording-workspace",
- "version": "0.1.0",
+ "version": "0.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "recording-workspace",
- "version": "0.1.0",
+ "version": "0.1.3",
"dependencies": {
"lucide-react": "1.48.0",
"react": "18.3.1",
@@ -14,6 +14,7 @@
},
"devDependencies": {
"@vitejs/plugin-react": "6.1.1",
+ "react-test-renderer": "18.3.1",
"vite": "8.3.1"
}
},
@@ -669,6 +670,16 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -745,6 +756,42 @@
"react": "^18.3.1"
}
},
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/react-shallow-renderer": {
+ "version": "16.15.0",
+ "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz",
+ "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4.1.1",
+ "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-test-renderer": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz",
+ "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "react-is": "^18.3.1",
+ "react-shallow-renderer": "^16.15.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
"node_modules/rolldown": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz",
diff --git a/workspace-app/package.json b/workspace-app/package.json
index 0e4637f..46a8e4f 100644
--- a/workspace-app/package.json
+++ b/workspace-app/package.json
@@ -1,7 +1,7 @@
{
"name": "recording-workspace",
"private": true,
- "version": "0.1.0",
+ "version": "0.1.3",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1 --port 5173 --strictPort",
@@ -17,6 +17,7 @@
},
"devDependencies": {
"vite": "8.3.1",
- "@vitejs/plugin-react": "6.1.1"
+ "@vitejs/plugin-react": "6.1.1",
+ "react-test-renderer": "18.3.1"
}
}
diff --git a/workspace-app/src/App.jsx b/workspace-app/src/App.jsx
index 567cf20..f937ed1 100644
--- a/workspace-app/src/App.jsx
+++ b/workspace-app/src/App.jsx
@@ -1,7 +1,12 @@
import {tagFilterLabel} from './protocolViewFilter.js';
+import {useWorkspaceChanges,useSummaryRevision} from './useWorkspaceChanges.js';
import H5Inbox from './components/H5Inbox.jsx';
+import BrowseFilesButton from './components/BrowseFilesButton.jsx';
import ProjectClosedNotice from './components/ProjectClosedNotice.jsx';
import AppUpdates from './components/AppUpdates.jsx';
+import {flushDesktopDrafts} from './desktopLifecycle.js';
+import {useDesktopDraft} from './useDesktopDraft.js';
+import DesktopDraftRecovery from './components/DesktopDraftRecovery.jsx';
import ProjectFolder from './components/ProjectFolder.jsx';
import LocalExportFolder from './components/LocalExportFolder.jsx';
import ExternalTagSync from './components/ExternalTagSync.jsx';
@@ -11,6 +16,7 @@ import AnnotationProfile from './components/AnnotationProfile.jsx';
import useProtocolTreeLayout from './useProtocolTreeLayout.js';
const ProtocolInfographic=lazy(()=>import('./components/ProtocolInfographic.jsx'));
import ProjectOnboarding from './components/ProjectOnboarding.jsx';
+import ProjectSetupDialog from './components/ProjectSetupDialog.jsx';
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react';
import { Activity, ArrowLeft, ArrowRight, ArrowUpRight, Check, ChevronDown, CircleDot, Database, Download, FileJson, FolderOpen, GitBranch, Layers, LayoutDashboard, ListFilter, PanelLeftClose, PanelLeftOpen, Plus, RefreshCw, Search, Settings2, ShieldCheck, Upload, Users, Clock3, X } from 'lucide-react';
import { api, useResource, number, duration, humanize, time, eligibleExportCount } from './api.js';
@@ -36,6 +42,7 @@ import Overview from './components/Overview.jsx';
import ProtocolExports,{ExportDestination} from './components/ProtocolExports.jsx';
import ProjectNavigator,{ProjectRail} from './components/ProjectNavigator.jsx';
import {projectBootstrap} from './projectNavigation.js';
+import {inspectAndOpenProject,localProjectUrl} from './projectTransfer.js';
import useWorkspaceNavigation from './useWorkspaceNavigation.js';
import {exportReuseRoute} from './exportReuse.js';
import {resolveProtocolSession,restoredEpochFocus} from './workspaceNavigation.js';
@@ -50,7 +57,7 @@ function useStableSummary(path,revision){
useEffect(()=>{if(resource.data&&!resource.error)committed.current={path,data:resource.data};},[path,resource.data,resource.error]);
return {...resource,data:resource.data||(!resource.error&&committed.current?.path===path?committed.current.data:null)};
}
-function Protocol({id,revision,onChange,onTagFilter,initialRecipe,initialInspection,onImport,onRefine,onReviewCandidate,onStores,onReuse,suggestion,suggestionsError,onRetrySuggestions,session,onSession,onQC,onReturnToTree,restoreSession=false}) {
+export function Protocol({id,revision,structureRevision=revision,annotationChange=null,onChange,onTagFilter,initialRecipe,initialInspection,onImport,onRefine,onReviewCandidate,onStores,onReuse,suggestion,suggestionsError,onRetrySuggestions,session,onSession,onQC,onReturnToTree,restoreSession=false}) {
const saved=useRef(resolveProtocolSession({saved:session,recipe:initialRecipe,inspection:initialInspection,restore:restoreSession})).current;
const inspectorSession=useRef(saved.inspector || null);
const [initialEpoch,setInitialEpoch]=useState(saved.initialEpoch || null);
@@ -61,9 +68,15 @@ function Protocol({id,revision,onChange,onTagFilter,initialRecipe,initialInspect
const splitOrder=treeLayout.order,setSplitOrder=treeLayout.remember;
const [exportOpen,setExportOpen]=useState(saved.tab==='export');
const [tab,setTab]=useState(saved.tab==='export'?'overview':saved.tab || 'overview'),[scope,setScope]=useState(saved.scope || null),[filters,setFilters]=useState(saved.filters || {}),[exporting,setExporting]=useState(false),[policy,setPolicy]=useState(saved.policy || 'include_unreviewed'),[exportName,setExportName]=useState(saved.exportName || ''),[format,setFormat]=useState(()=>saved.format || initialExportFormat(initialRecipe)),[result,setResult]=useState(null),[error,setError]=useState(''),[diff,setDiff]=useState(null),[refreshing,setRefreshing]=useState(false);
- const query=new URLSearchParams(filters).toString();
- const protocol=useStableSummary(`/protocols/${id}?${query}`,revision);
- const data=protocol.data,counts=data?.counts||{};
+ const filteredQuery=new URLSearchParams(filters).toString();
+ const inspecting=tab==='inspect';
+ const summaryRevision=useSummaryRevision(revision,!inspecting,structureRevision);
+ // Inspection pages own filtered membership and fresh edit authority. Changing
+ // a view filter must not wait for an aggregate overview or remount Inspector.
+ const protocol=useStableSummary(`/protocols/${id}?${inspecting?'':filteredQuery}`,summaryRevision);
+ const exportSummary=useResource(exportOpen&&inspecting?`/protocols/${id}?${filteredQuery}`:null,revision);
+ const exportResource=inspecting?exportSummary:protocol;
+ const data=protocol.data,exportData=exportOpen?exportResource.data:data,counts=exportData?.counts||{};
const snapshot=useRef(null);
snapshot.current={tab,scope,filters,splitOrder,policy,exportName,format,initialEpoch:restoredEpochFocus(inspectorSession.current,initialEpoch),inspector:inspectorSession.current,bindingRevision:data?.binding?.revision_uuid ?? saved.bindingRevision};
const rememberInspector=useCallback(value=>{inspectorSession.current=value;onSession?.({...snapshot.current,inspector:value,initialEpoch:value.focused});},[onSession]);
@@ -71,13 +84,13 @@ function Protocol({id,revision,onChange,onTagFilter,initialRecipe,initialInspect
function openCell(uuid){inspectorSession.current=null;setInitialEpoch(null);setScope(uuid);setTab('inspect');}
const eligible=eligibleExportCount(counts,policy);
- const exportBlocked=!!data?.source_eligibility?.propagation_required;
+ const exportBlocked=!!exportData?.source_eligibility?.propagation_required;
const held=Number.isFinite(eligible)?Math.max(0,counts.included-eligible):0;
function filter(key,value){setResult(null);setError('');setFilters(old=>{const next={...old};if(value)next[key]=value;else delete next[key];return next;});}
async function refreshQuery(){setRefreshing(true);setError('');try{setDiff(await api(`/protocols/${id}/refresh`,{method:'POST',body:{filters}}));protocol.reload();onChange();}catch(e){setError(e.message);}finally{setRefreshing(false);}}
- async function saveExport(){setExporting(true);setError('');try{const r=await api(`/protocols/${id}/exports`,{method:'POST',body:{format,name:exportName || data.definition.name,review_policy:policy,filters,query_revision:data.query_revision,split_order:splitOrder.join(', ')}});if(!validExportReceipt(r,format)){onChange();throw new Error('The server did not return a complete export receipt. Check this protocol’s Exports and Activity & logs before retrying.');}setResult(r);onChange();}catch(e){setError(e.message);}finally{setExporting(false);}}
+ async function saveExport(){if(exportResource.loading||!exportData)return;setExporting(true);setError('');try{const r=await api(`/protocols/${id}/exports`,{method:'POST',body:{format,name:exportName || data.definition.name,review_policy:policy,filters,query_revision:exportData.query_revision,split_order:splitOrder.join(', ')}});if(!validExportReceipt(r,format)){onChange();throw new Error('The server did not return a complete export receipt. Check this protocol’s Exports and Activity & logs before retrying.');}setResult(r);onChange();}catch(e){setError(e.message);}finally{setExporting(false);}}
const exportPanel=data&&(<>{setFormat(value);setResult(null);setError('');}}/>{EXPORT_FORMATS[format]?.description || 'Choose a supported output format to continue. The saved export keeps its original format.'}
Export scope · {number(counts.cells)} matching cells
Uses this protocol’s saved query and current filters. Focusing a cell or checking bulk-action targets does not restrict this export.
{filters.cell_type || 'All cell types'}{tagFilterLabel(filters)&&{tagFilterLabel(filters)}}{filters.group_label ? `Group: ${filters.group_label}` : 'All source groups'}{filters.cell_uuid&&Cell: {datedCellLabel(data.cells?.find(c=>c.cell_uuid===filters.cell_uuid) || {},true)}}
- Matching epochs
- {number(counts.epochs)}
- Included
- {number(counts.included)}
- Excluded
- {number(counts.excluded)}
- Already in saved exports
- {number(counts.exported ?? 0)}
{policy==='approved_only'&&- Not marked reviewed
- {number(held)}
}- Ready to export
- {exportBlocked?'Source update needed':Number.isFinite(eligible)?number(eligible):'Loading…'}
{exportBlocked?
This retained dataset includes sources excluded from queries. Open Data stores and propagate the proposed changes before exporting.
:eligible===0?
{policy==='approved_only'&&counts.included>0?'No included epochs are marked reviewed. Choose “Included epochs” to export without a review requirement, or mark review status during inspection.':tagFilterLabel(filters)?'No epochs match this tag filter. Clear or change the filter to export matching epochs.':'No epochs are included in this query. Change the query scope or include the epochs you want to export.'}
:
{policy==='include_unreviewed'?'All included epochs are eligible; review is not required.':'Only included epochs marked reviewed are eligible.'} The server rechecks membership before saving. Each export remains a separate saved revision.
}
{error&&{error}
}{result&&}Previous exports
>);
- const exportFooter=( Logged in project activity
);
+ const exportFooter=( Logged in project activity
);
return ;
+ {treeLayout.save.status==='error'&&
Tree layout could not be saved: {treeLayout.save.error}
}
{protocol.reload();treeLayout.reload();}}>{data&&<>{exportOpen&&setExportOpen(false)} footer={exportFooter}>{exportPanel}}{tab==='inspect'?{setFilters(next);setResult(null);setError('');}} onTagFilter={onTagFilter} key={browseRequest} initialEpochUuid={initialEpoch} initialNavigation={inspectorSession.current} onSessionChange={rememberInspector} onQC={onQC} protocol={data} splitRecipe={splitOrder} onSplitChange={setSplitOrder} cellScope={scope} filters={filters} revision={revision} structureRevision={structureRevision} annotationChange={annotationChange} onChange={onChange} onBack={()=>setTab('overview')} onImport={onImport} onStores={onStores} onExport={()=>setExportOpen(true)}/>:PROTOCOL WORKSPACE
{humanize(data.definition?.name)}
{data.binding?`Working dataset version ${data.binding.version}. Use Search predicate to compare and apply new source data.`:"A saved query into the project catalog. Inspection decisions stay linked to each epoch."}
{initialRecipe&&
Reused saved query; matching the current catalog. Previous export unchanged.
}{diff&&
{diff.message || (diff.baseline_created?'Saved a comparison baseline.':`Query refreshed: ${typeof diff.diff?.added==='number'?diff.diff.added:diff.diff?.added?.length||0} added · ${typeof diff.diff?.changed==='number'?diff.diff.changed:diff.diff?.changed?.length||0} changed · ${typeof diff.diff?.removed==='number'?diff.diff.removed:diff.diff?.removed?.length||0} no longer matching`)}<>{diff.candidate_revision_uuid&&}> }{error&&tab!=='export'&&
{error}
}
Loading dataset summary… }>Filter results{Object.keys(filters).length>0&&}Group labels are original metadata, not inferred drug phases.
<>{suggestionsError&&New-data suggestions could not load.
}{suggestion&&}Cells in this protocol
> }>};
}
function ImportPage({projectId,protocols,sources,onChange,onBack,jobs,suggestions=[],approvedHistory=[],suggestionsError,onRetrySuggestions,onReviewCandidate,onProtocol,queue}) {
const jobRecords=Array.isArray(jobs.data?.jobs)?jobs.data.jobs:[];
const blocked=queue.active;
const error=queue.error;
- return ADD TO MAIN CATALOG
Import H5s
Choose one or more H5 recordings. New imports are copied into this project and verified before parsing.
{queue.busy&&
Importing one file at a time · {queue.remaining} waiting. Keep this app tab open until the queue finishes.
}{error&&
{error}
}{suggestionsError&&
New-data suggestions could not load.
}
Import history
{jobs.connectionError&&jobs.data&&Connection interrupted. Showing the last confirmed job history; use Refresh jobs to check again.
} ;
+ return ADD TO MAIN CATALOG
Import H5s
Choose one or more H5 recordings. New imports are copied into this project and verified before parsing.
e.preventDefault()} onDrop={e=>{e.preventDefault();queue.enqueue(e.dataTransfer.files);}}>
Drop Symphony H5 files here
Rieke OS keeps a managed copy for lazy loading. Keep the original until the import confirms that its copy is ready. Identical files are recognized even when renamed.
queue.enqueue(files)}/>{queue.busy&&
Importing one file at a time · {queue.remaining} waiting. Keep this app tab open until the queue finishes.
}{error&&
{error}
}{suggestionsError&&
New-data suggestions could not load.
}
Import history
{jobs.connectionError&&jobs.data&&Connection interrupted. Showing the last confirmed job history; use Refresh jobs to check again.
} ;
}
function HistoryPage({kind,revision,onReuse}){const [reuseError,setReuseError]=useState(''),[reusing,setReusing]=useState(null);async function reuseExport(exported){const id=exported.dataset_uuid || exported.export_uuid;setReusing(id);setReuseError('');try{const recipe=await api(`/exports/${id}/reuse`);onReuse(recipe);}catch(error){setReuseError(error.message);}finally{setReusing(null);}}const result=useResource(kind==='exports'?'/exports':'/events',revision);return PROJECT RECORD
{kind==='exports'?'Project export log':'Activity & logs'}
{kind==='exports'?'Saved protocol and one-off search exports. Reuse opens the saved query for review; it never exports automatically.':'A persistent record of imports, tags, review decisions and exports.'}
{kind==='exports'&&
}{reuseError&&{reuseError}
}{kind==='exports'?{(result.data?.exports || []).length?(result.data.exports).map((e,i)=>
{e.format==='wheeler-sqlite'?
:}{e.name || e.filename || e.export_uuid}{e.export_scope?.kind==='explorer_candidate'?'One-off search':'Protocol export'} · {time(e.created_at || e.at)} · {number(e.epoch_count ?? e.membership?.length)} epochs · {exportFormatLabel(e.format)}
Export record & provenance
{JSON.stringify(e,null,2)} {(e.dataset_uuid || e.export_uuid)&&}{e.download_url&& {exportDownloadLabel(e.format)}}):
Use Search predicate to export a matching selection directly, or export from a protocol workspace.}
:} ;}
export default function App(){
@@ -107,17 +120,31 @@ export default function App(){
const rememberQC=useCallback(value=>{sessions.current.set(route.key,value);},[route.key]);
const rememberExplorer=useCallback(value=>{sessions.current.set(route.key,value);lastExplorerSession.current=value;},[route.key]);
const rememberProtocol=useCallback(value=>{sessions.current.set(route.key,value);if(protocol)protocolSessions.current.set(protocol,value);},[route.key,protocol]);
- const [hidden,setHidden]=useState(()=>{const saved=localStorage.getItem('workspace.sidebar.hidden');return saved===null?window.innerWidth<900:saved==='true';}),[revision,setRevision]=useState(0);
- const registry=useStableSummary('/projects',revision),isLauncher=registry.data?.launcher===true;
+ const [hidden,setHidden]=useState(()=>{const saved=localStorage.getItem('workspace.sidebar.hidden');return saved===null?window.innerWidth<900:saved==='true';});
+ const {revision,structureRevision,annotationChange,changed}=useWorkspaceChanges();
+ const overviewRevision=useSummaryRevision(revision,page==='overview',structureRevision);
+ const registry=useStableSummary('/projects',structureRevision),isLauncher=registry.data?.launcher===true;
const projectReady=!!registry.data&&!isLauncher;
- const overview=useStableSummary(projectReady?'/overview':null,revision),suggestionsResource=useResource(projectReady?'/protocol-suggestions':null,revision);
+ const overview=useStableSummary(projectReady?'/overview':null,overviewRevision),suggestionsResource=useResource(projectReady?'/protocol-suggestions':null,structureRevision);
const [reviewImportJob,setReviewImportJob]=useState(null);
const pendingImportReview=useRef(null);
+ const [,draftRestored]=useState(0);
+
function importCompleted(jobUuid){changed();if(jobUuid)pendingImportReview.current=jobUuid;}
- const suggestions=activeProtocolSuggestions(suggestionsResource.data),importJobs=useImportMonitor(revision,importCompleted,!!transfer,projectReady);
+ const suggestions=activeProtocolSuggestions(suggestionsResource.data),importJobs=useImportMonitor(structureRevision,importCompleted,!!transfer,projectReady);
const importQueue=useImportQueue(importJobs,setTransfer,changed);
+ const desktopDraft=useDesktopDraft({projectId:registry.data?(registry.data.current_project_uuid||'launcher'):null,busy:!!transfer||importQueue.busy,
+ snapshot:()=>({route,sessions:[...sessions.current],protocolSessions:[...protocolSessions.current],lastExplorerSession:lastExplorerSession.current,lastStoresSession:lastStoresSession.current}),
+ restore:value=>{
+ if(!value||typeof value!=='object')return;
+ const pairs=entries=>Array.isArray(entries)&&entries.every(pair=>Array.isArray(pair)&&pair.length===2&&typeof pair[0]==='string')?entries:[];
+ sessions.current=new Map(pairs(value.sessions));protocolSessions.current=new Map(pairs(value.protocolSessions));
+ lastExplorerSession.current=value.lastExplorerSession||null;lastStoresSession.current=value.lastStoresSession||null;
+ navigation.restore(value.route);draftRestored(count=>count+1);
+ }});
useEffect(()=>{if(!importQueue.busy&&pendingImportReview.current){setReviewImportJob(pendingImportReview.current);pendingImportReview.current=null;}},[importQueue.busy,revision]);
const [projectSetup,setProjectSetup]=useState(false);
+ const [projectSetupBusy,setProjectSetupBusy]=useState(false);
useEffect(()=>{if(transfer?.phase==='accepted'&&importJobs.data?.jobs?.some(job=>job.job_uuid===transfer.job_uuid))setTransfer(null);},[transfer,importJobs.data]);
const [openingProject,setOpeningProject]=useState(null),[projectError,setProjectError]=useState('');
function setPage(next){if(next!==page)navigation.go(next);}
@@ -138,5 +165,4 @@ export default function App(){
function openQC(cellUuid,epochUuid=null){navigation.go('cell-qc',{cell_uuid:cellUuid,epoch_uuid:epochUuid});}
function searchEpoch(result){if(result.protocol_uuid)openInspection(result.protocol_uuid,result);else openQC(result.cell_uuid,result.epoch_uuid);}
function searchPredicate(predicate){navigation.go('explore',{explorePredicate:predicate});}
- function changed(){setRevision(x=>x+1);}
- const data=overview.data;const projectId=registry.data?.current_project_uuid || data?.project?.project_uuid;const registeredProjects=registry.data?.projects || [];const currentProject=registeredProjects.find(project=>project.current)||registeredProjects.find(project=>project.uuid===projectId);const projectName=data?.project?.display_name || currentProject?.name || data?.project?.name || 'Project';useEffect(()=>{document.title=isLauncher?'Rieke OS · Projects':`${projectName} · Rieke OS`;},[projectName,isLauncher]);async function selectProject(project){if(openingProject||!project.available)return;if(project.current){setPage('overview');return;}setOpeningProject(project.name);setProjectError('');try{const result=await api(project.path?'/projects/open-folder':`/projects/${project.uuid}/open`,{method:'POST',body:project.path?{directory:project.path}:{}});if(typeof result.url!=='string')throw new Error('The project service did not return an app URL.');const destination=new URL(result.url,window.location.href);if(!['http:','https:'].includes(destination.protocol)||!['127.0.0.1','localhost','[::1]'].includes(destination.hostname))throw new Error('The project service returned an invalid local URL.');window.location.assign(destination.href);}catch(error){setProjectError(error.message);setOpeningProject(null);}}const setup={setProjectSetup(false);setFolderOpen(true);}} onWorkspaceChanged={registry.reload} registry={registry.data} onSelect={selectProject} onCreated={async project=>{registry.reload();await selectProject(project);}} onCancel={isLauncher?undefined:()=>setProjectSetup(false)} createOnly={false} loading={!!openingProject} error={projectError||registry.error}/>;if(isLauncher)return {setup}{folderOpen&&setFolderOpen(false)} onTransferComplete={()=>registry.reload()}/>};return / {page==='protocol'?humanize(data?.protocols?.find(p=>p.protocol_uuid===protocol)?.name):page==='explore'?'Search predicate':page==='import'?'Add data store':page==='stores'?'Data stores':page==='cell-qc'?'Cell QC':humanize(page)}openQC(result.cell_uuid,result.epoch_uuid)} onPredicate={searchPredicate}/> Local workspace{reviewImportJob&&}setPage('import')}/>{folderOpen&&
setFolderOpen(false)} onFiles={()=>setPage('files')}/>}{projectSetup&&{setup}
}{openingProject&& Opening {openingProject}…
}{projectError&&{projectError}
}{page==='cell-qc'?navigation.canBack?navigation.back():setPage('overview')}/>:page==='stores'?setPage('import')} onProtocol={navigate} onFiles={()=>setPage('files')} onLogs={()=>setPage('activity')}/>:!data?:page==='overview'?setPage('import')} onExplore={()=>explore()}/>:page==='explore'?explore(null,{},true)} onExit={()=>explore()} key={route.key} session={sessions.current.get(route.key) || (route.resumeExplorer?lastExplorerSession.current:null)} onSession={rememberExplorer} initialPredicate={explorePredicate} initialRevisionId={exploreRevisionId} initialExportIntent={exploreExportIntent} initialProtocolId={exploreTargetId} onProtocolApplied={protocolApplied} onChange={changed} revision={revision} protocols={data.protocols || []} onInspect={openInspection}/>:page==='protocol'?item.protocol_uuid===protocol)} suggestionsError={suggestionsResource.error} onRetrySuggestions={suggestionsResource.reload} initialRecipe={initialRecipe} initialInspection={initialInspection} onRefine={explore} onReviewCandidate={reviewCandidate} onStores={()=>setPage('stores')} id={protocol} revision={revision} onChange={changed} onImport={()=>setPage('import')}/>:page==='activity'?:page==='files'?setPage('stores')}/>:page==='import'?setPage('stores')} jobs={importJobs} suggestions={suggestionsResource.data?.suggestions||[]} approvedHistory={suggestionsResource.data?.approved_history||[]} suggestionsError={suggestionsResource.error} onRetrySuggestions={suggestionsResource.reload} onReviewCandidate={reviewCandidate} onProtocol={navigate}/>:} ;}
+ const data=overview.data;const projectId=registry.data?.current_project_uuid || data?.project?.project_uuid;const registeredProjects=registry.data?.projects || [];const currentProject=registeredProjects.find(project=>project.current)||registeredProjects.find(project=>project.uuid===projectId);const projectName=data?.project?.display_name || currentProject?.name || data?.project?.name || 'Project';useEffect(()=>{document.title=isLauncher?'Rieke OS · Projects':`${projectName} · Rieke OS`;},[projectName,isLauncher]);async function selectProject(project){if(openingProject||!project.available)return;if(project.current){setPage('overview');return;}setOpeningProject(project.name);setProjectError('');try{await flushDesktopDrafts();const result=project.path?await inspectAndOpenProject({directory:project.path,request:api}):await api(`/projects/${project.uuid}/open`,{method:'POST',body:{}});if(result.action==='migrate'||result.action==='restore'||result.action==='choose-root'){setFolderOpen({mode:result.action==='migrate'?'migrate':result.action==='restore'?'restore':'open',directory:project.path,inspection:result.inspection});setOpeningProject(null);return;}window.location.assign(localProjectUrl(result.url,window.location.href));}catch(error){setProjectError(error.message);setOpeningProject(null);}}const setup={setProjectSetup(false);setFolderOpen(true);}} onWorkspaceChanged={registry.reload} registry={registry.data} onSelect={async project=>{await selectProject(project);if(project.current)setProjectSetup(false);}} onCreated={async project=>{registry.reload();await selectProject(project);}} onCancel={()=>setProjectSetup(false)} createOnly={projectSetup==='create'} loading={!!openingProject} error={projectError||registry.error}/>;const setupDialog=projectSetup&&setProjectSetup(false)}>{setup};const launcherSetup=setFolderOpen(true)} onNewProject={()=>setProjectSetup('create')} onWorkspaceChanged={registry.reload} registry={registry.data} onSelect={selectProject} loading={!!openingProject} error={projectError||registry.error}/>;if(desktopDraft.phase==='recovery')return ;if(isLauncher)return setFolderOpen({mode:'prepare',directory})}/>{launcherSetup}{setupDialog}{folderOpen&&setFolderOpen(false)} onTransferComplete={()=>registry.reload()}/>};return / {page==='protocol'?humanize(data?.protocols?.find(p=>p.protocol_uuid===protocol)?.name):page==='explore'?'Search predicate':page==='import'?'Add data store':page==='stores'?'Data stores':page==='cell-qc'?'Cell QC':humanize(page)}openQC(result.cell_uuid,result.epoch_uuid)} onPredicate={searchPredicate}/> Local workspacechanged({kind:'annotations'})}/>
{reviewImportJob&&}setPage('import')}/>{folderOpen&&
setFolderOpen(false)} onFiles={()=>setPage('files')} onTransferComplete={()=>registry.reload()}/>}{setupDialog}{openingProject&& Opening {openingProject}…
}{projectError&&{projectError}
}{page==='cell-qc'?changed({kind:'annotations'})} onTagFilter={searchPredicate} key={`${route.cell_uuid}:${route.key}`} cellUuid={route.cell_uuid} initialEpochUuid={route.epoch_uuid} session={sessions.current.get(route.key)} onSession={rememberQC} revision={revision} onInspect={searchEpoch} onBack={()=>navigation.canBack?navigation.back():setPage('overview')}/>:page==='stores'?setPage('import')} onProtocol={navigate} onFiles={()=>setPage('files')} onLogs={()=>setPage('activity')}/>:!data?:page==='overview'?setPage('import')} onExplore={()=>explore()}/>:page==='explore'?explore(null,{},true)} onExit={()=>explore()} key={route.key} session={sessions.current.get(route.key) || (route.resumeExplorer?lastExplorerSession.current:null)} onSession={rememberExplorer} initialPredicate={explorePredicate} initialRevisionId={exploreRevisionId} initialExportIntent={exploreExportIntent} initialProtocolId={exploreTargetId} onProtocolApplied={protocolApplied} onChange={changed} revision={revision} protocols={data.protocols || []} onInspect={openInspection}/>:page==='protocol'?item.protocol_uuid===protocol)} suggestionsError={suggestionsResource.error} onRetrySuggestions={suggestionsResource.reload} initialRecipe={initialRecipe} initialInspection={initialInspection} onRefine={explore} onReviewCandidate={reviewCandidate} onStores={()=>setPage('stores')} id={protocol} revision={revision} structureRevision={structureRevision} annotationChange={annotationChange} onChange={changed} onImport={()=>setPage('import')}/>:page==='activity'?:page==='files'?setPage('stores')} onFolder={openingProject||!currentProject?undefined:()=>setFolderOpen(true)}/>:page==='import'?setPage('stores')} jobs={importJobs} suggestions={suggestionsResource.data?.suggestions||[]} approvedHistory={suggestionsResource.data?.approved_history||[]} suggestionsError={suggestionsResource.error} onRetrySuggestions={suggestionsResource.reload} onReviewCandidate={reviewCandidate} onProtocol={navigate}/>:} ;}
diff --git a/workspace-app/src/annotationReceiptWorkflow.test.js b/workspace-app/src/annotationReceiptWorkflow.test.js
new file mode 100644
index 0000000..2d504d2
--- /dev/null
+++ b/workspace-app/src/annotationReceiptWorkflow.test.js
@@ -0,0 +1,130 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import {createWorkflowHarness} from './test-support/workflowHarness.js';
+import {confirmAnnotationReceipt,mergeAnnotationReceipts,applyAnnotationReceipts} from './annotationReceipts.js';
+import {createResourceCache} from './resourceCache.js';
+
+const input=h=>h.root.findAllByType('input').find(node=>node.props['aria-label']?.startsWith('Tag ')&&!node.props['aria-label'].startsWith('Tag to add'));
+async function ready(h){await h.waitFor(()=>{try{return !!input(h)&&!input(h).props.disabled;}catch{return false;}});}
+async function save(h,value){await h.act(()=>input(h).props.onChange({target:{value}}));await h.act(()=>input(h).parent.props.onSubmit({preventDefault(){}}));await ready(h);}
+const text=node=>node.children.filter(value=>typeof value==='string').join('').trim();
+async function filterRules(h,logic,rules){
+ await h.act(()=>h.root.findByProps({'aria-controls':'protocol-view-filter-panel'}).props.onClick());
+ await h.act(()=>h.root.findByProps({id:'protocol-view-filter-panel'}).findAllByType('select')[0].props.onChange({target:{value:'rules'}}));
+ await h.act(()=>h.root.findByProps({'aria-label':'Match tag rules'}).props.onChange({target:{value:logic}}));
+ for(let index=0;indexh.root.findAllByType('button').find(node=>text(node)==='Add tag rule').props.onClick());
+ await h.act(()=>{h.root.findByProps({'aria-label':`Tag scope ${index+1}`}).props.onChange({target:{value:rules[index].scope}});h.root.findByProps({'aria-label':`Tag value ${index+1}`}).props.onChange({target:{value:rules[index].value}});});
+ }
+ await h.act(()=>h.root.findByProps({id:'protocol-view-filter-panel'}).props.onSubmit({preventDefault(){}}));
+ await h.waitFor(()=>!h.viewer.treePane.listProps.disabled);
+}
+
+test('mounted tag10 then another10 acknowledges each exact receipt, then composes20 and cell/epoch filters',async()=>{
+ const h=await createWorkflowHarness({total:40});h.fixture.savedTagFilters=true;
+ try{
+ await h.mount();await ready(h);
+ for(const [start,tag] of [[0,'A'],[10,'B']]){
+ await h.waitFor(()=>!h.viewer.treePane.listProps.disabled);
+ const ids=Array.from({length:10},(_,offset)=>`epoch-${start+offset}`);
+ await h.act(()=>h.viewer.treePane.listProps.setTargets(ids));
+ const before=h.fixture.requests.length;
+ await save(h,tag);
+ const calls=h.fixture.requests.slice(before);
+ assert.deepEqual(calls.filter(row=>row.method==='POST').map(row=>row.path),['/annotations/read','/annotations']);
+ assert.deepEqual(calls.find(row=>row.path==='/annotations').body.target_uuids,ids);
+ assert.equal(calls.filter(row=>row.path.startsWith('/epochs/')||row.path.includes('/epochs?')).length,0,'Acknowledgment must not wait for an epoch/page read');
+ assert.equal(input(h).props.disabled,false);
+ assert.match(h.root.findByProps({className:'annotation-result'}).children.join(''),/Saved 10 selected epoch/);
+ for(const id of ids)assert.deepEqual(h.fixture.annotations.get(id),[tag]);
+ }
+ await filterRules(h,'any',[{scope:'effective',value:'A'},{scope:'epoch',value:'B'}]);
+ assert.equal(h.viewer.navigation.total,20);
+ const ast=JSON.parse(new URLSearchParams(h.viewer.treePane.listProps.source.query).get('tag_predicate'));
+ assert.deepEqual(ast.any.map(node=>node.field),['annotations/effective/tags','annotations/epoch/tags']);
+ const cell=h.viewer.treePane.listProps.cells[0];
+ await h.act(()=>h.viewer.treePane.listProps.onSelectCell(cell,{epoch_uuid:'epoch-0',cell_uuid:'cell-0'}));await ready(h);
+ const before=h.fixture.requests.length;
+ await save(h,'cell C');
+ const mutation=h.fixture.requests.slice(before).find(row=>row.path==='/annotations');
+ assert.equal(mutation.body.target_kind,'cell');assert.deepEqual(mutation.body.target_uuids,['cell-0']);
+ assert.equal(h.fixture.cellAnnotations.size,1);assert.equal(h.fixture.annotations.size,20,'Cell inheritance must not create epoch annotation rows');
+ assert.equal(h.viewer.epoch.annotations.cell_tags[0].tag,'cell C');
+ await filterRules(h,'all',[{scope:'cell',value:'cell C'},{scope:'epoch',value:'A'}]);
+ assert.equal(h.viewer.navigation.total,10);assert.equal(h.fixture.mounts,1);
+ }finally{await h.close();}
+});
+
+test('twenty successive direct tags use confirmed per-profile revisions without focused metadata rereads',async()=>{
+ const h=await createWorkflowHarness({total:20});
+ try{
+ await h.mount();await ready(h);const before=h.fixture.requests.length;
+ for(let count=0;count<20;count++){
+ await save(h,`tag ${count}`);
+ assert.equal(h.viewer.epoch.annotations.revisions.epoch.author,count+1);
+ assert.equal(input(h).props.disabled,false);
+ }
+ const calls=h.fixture.requests.slice(before),writes=calls.filter(row=>row.path==='/annotations');
+ assert.equal(writes.length,20);assert.deepEqual(writes.map(row=>row.body.expected_revisions['epoch-0']),Array.from({length:20},(_,i)=>i));
+ assert.equal(calls.filter(row=>row.path==='/epochs/epoch-0?protocol_uuid=protocol-A').length,0);
+ assert.equal(h.viewer.epoch.annotations.epoch_tags.length,20);
+ await h.waitFor(()=>!h.viewer.treePane.listProps.disabled);
+ assert.equal(h.viewer.treePane.listProps.source.queryRevision,'query-20');
+ }finally{await h.close();}
+});
+
+test('receipt eviction reloads the focused target before the next direct edit',async()=>{
+ const h=await createWorkflowHarness({total:100});
+ try{
+ await h.mount();await ready(h);await save(h,'focused first');
+ const before=h.fixture.requests.length;
+ for(let start=1;start<=61;start+=20){
+ await h.waitFor(()=>!h.viewer.treePane.listProps.disabled);
+ await h.act(()=>h.viewer.treePane.listProps.setTargets(Array.from({length:20},(_,offset)=>`epoch-${start+offset}`)));
+ await save(h,`batch ${start}`);
+ }
+ assert.equal(h.viewer.epoch.epoch_uuid,'epoch-0');
+ assert.equal(h.viewer.epoch.annotations.revisions.epoch.author,1);
+ assert.deepEqual(h.viewer.epoch.annotations.epoch_tags.map(chip=>chip.tag),['focused first']);
+ assert.ok(h.fixture.requests.slice(before).some(row=>row.path==='/epochs/epoch-0?protocol_uuid=protocol-A'),'Eviction must refresh the old focused metadata base');
+ await h.act(()=>h.viewer.treePane.listProps.setTargets([]));await ready(h);
+ await save(h,'focused second');
+ const mutation=h.fixture.requests.filter(row=>row.path==='/annotations').at(-1);
+ assert.deepEqual(mutation.body.target_uuids,['epoch-0']);
+ assert.equal(mutation.body.expected_revisions['epoch-0'],1);
+ assert.equal(mutation.status,200);
+ assert.equal(h.viewer.epoch.annotations.revisions.epoch.author,2);
+ }finally{await h.close();}
+});
+
+test('receipt validation rejects wrong target/profile revisions and preserves independent author sets',()=>{
+ const request={target_kind:'cell',target_uuids:['cell'],profile_uuid:'me',expected_revisions:{cell:1}};
+ const chip=(profile,revision,tag)=>({target_kind:'cell',target_uuid:'cell',profile_uuid:profile,author_name:profile,revision,tag});
+ const result={changed:1,targets:{cell:{target_kind:'cell',target_uuid:'cell',revisions:{me:2,other:5},tags:[chip('me',2,'saved'),chip('other',5,'other')]}}};
+ assert.throws(()=>confirmAnnotationReceipt({...result,persistence:{database:'pending'}},request),/could not be verified/);
+ const confirmed=confirmAnnotationReceipt(result,request),store=mergeAnnotationReceipts(new Map(),confirmed);
+ const base={epoch_uuid:'e',cell_uuid:'cell',annotations:{cell_tags:[chip('other',6,'newer')],epoch_tags:[],revisions:{cell:{other:6},epoch:{}}}};
+ const next=applyAnnotationReceipts(base,store);
+ assert.deepEqual(new Set(next.annotations.cell_tags.map(row=>row.tag)),new Set(['saved','newer']));
+ assert.equal(store.size,1);
+ for(const change of ['identity','kind','revision','profile']){
+ const bad=structuredClone(result);
+ if(change==='identity')bad.targets.cell.target_uuid='another';
+ if(change==='kind')bad.targets.cell.target_kind='epoch';
+ if(change==='revision')bad.targets.cell.revisions.me=0;
+ if(change==='profile')bad.targets.cell.tags[0].profile_uuid='invented';
+ assert.throws(()=>confirmAnnotationReceipt(bad,request),/could not be verified/);
+ }
+ let bounded=new Map();
+ for(let count=0;count<100;count++)bounded=mergeAnnotationReceipts(bounded,{version:1,targets:[{target_kind:'cell',target_uuid:String(count),tags:[],revisions:{}}]},{targets:8,bytes:2000});
+ assert.ok(bounded.size<=8);assert.ok([...bounded.values()].reduce((sum,value)=>sum+value.bytes,0)<=2000);
+});
+
+test('confirmed cell receipts invalidate bounded metadata caches without discarding immutable trace windows',()=>{
+ const cache=createResourceCache();
+ cache.put('/epochs/e?protocol_uuid=p',0,{epoch_uuid:'e'});
+ cache.put('/epochs/e/trace?stream_uuid=s',0,{values:[1,2]});
+ cache.invalidateAnnotations({targets:[{target_kind:'cell',target_uuid:'cell'}]});
+ assert.equal(cache.get('/epochs/e?protocol_uuid=p',0),undefined);
+ assert.deepEqual(cache.get('/epochs/e/trace?stream_uuid=s',0),{values:[1,2]});
+});
diff --git a/workspace-app/src/annotationReceipts.js b/workspace-app/src/annotationReceipts.js
new file mode 100644
index 0000000..ac68040
--- /dev/null
+++ b/workspace-app/src/annotationReceipts.js
@@ -0,0 +1,57 @@
+// Display only facts returned by a successful durable annotation POST. Never
+// invent a protocol membership/query revision from an annotation receipt.
+const invalid=()=>{throw new Error('The saved annotation response could not be verified. Refresh tags before editing again.');};
+const record=value=>value!==null&&typeof value==='object'&&!Array.isArray(value);
+const text=value=>typeof value==='string'&&value.length>0;
+const revision=value=>Number.isSafeInteger(value)&&value>=0;
+export const RECEIPT_LIMITS={targets:64,bytes:4*1024*1024};
+
+export function confirmAnnotationReceipt(result,request){
+ const ids=request?.target_uuids,kind=request?.target_kind,profile=request?.profile_uuid;
+ if(!['cell','epoch'].includes(kind)||!text(profile)||!Array.isArray(ids)||!ids.length||new Set(ids).size!==ids.length||ids.some(id=>!text(id))||!record(request.expected_revisions))invalid();
+ if(result?.persistence&&result.persistence.database!=='committed')invalid();
+ if(!revision(result?.changed)||result.changed>ids.length||!record(result.targets)||Object.keys(result.targets).length!==ids.length)invalid();
+ const targets=[];
+ for(const id of ids){
+ const value=result.targets[id],expected=request.expected_revisions[id];
+ if(!revision(expected)||!record(value)||value.target_kind!==kind||value.target_uuid!==id||!record(value.revisions)||!Array.isArray(value.tags)||(value.revisions[profile]??0)255||!revision(chip.revision)||chip.revision<1||chip.revision!==value.revisions[chip.profile_uuid])invalid();
+ const key=JSON.stringify([chip.profile_uuid,chip.tag]);if(seen.has(key))invalid();seen.add(key);
+ }
+ targets.push({target_kind:kind,target_uuid:id,revisions:{...value.revisions},tags:value.tags.map(chip=>({...chip}))});
+ }
+ return {version:1,targets};
+}
+export function fastAnnotationReceipt(receipt){return receipt?.version===1&&receipt.targets.length<=20&&JSON.stringify(receipt.targets).length*2<=RECEIPT_LIMITS.bytes;}
+export function mergeAnnotationReceipts(previous,receipt,limits=RECEIPT_LIMITS){
+ const next=new Map(previous);let bytes=[...next.values()].reduce((sum,item)=>sum+item.bytes,0);
+ for(const target of receipt.targets){
+ const key=JSON.stringify([target.target_kind,target.target_uuid]),size=JSON.stringify(target).length*2;
+ if(next.has(key)){bytes-=next.get(key).bytes;next.delete(key);}
+ if(size<=limits.bytes){next.set(key,{target,bytes:size});bytes+=size;}
+ while(next.size>limits.targets||bytes>limits.bytes){const first=next.keys().next().value;bytes-=next.get(first).bytes;next.delete(first);}
+ }
+ return next;
+}
+export function applyAnnotationReceipts(epoch,store){
+ if(!epoch?.annotations||!store.size)return epoch;
+ const original=epoch.annotations,annotations={...original,revisions:{...original.revisions}};let changed=false;
+ for(const kind of ['cell','epoch']){
+ const id=kind==='cell'?epoch.cell_uuid:epoch.epoch_uuid,target=store.get(JSON.stringify([kind,id]))?.target;
+ if(!target)continue;
+ let chips=original[`${kind}_tags`]||[],versions=original.revisions?.[kind]||{};
+ for(const [profile,version] of Object.entries(target.revisions)){
+ if(version<(versions[profile]??0))continue;
+ chips=[...chips.filter(chip=>chip.profile_uuid!==profile),...target.tags.filter(chip=>chip.profile_uuid===profile)];
+ versions={...versions,[profile]:version};changed=true;
+ }
+ annotations[`${kind}_tags`]=chips;annotations.revisions[kind]=versions;
+ }
+ if(!changed)return epoch;
+ annotations.effective_tags=[...(annotations.cell_tags||[]),...(annotations.epoch_tags||[])];
+ return {...epoch,annotations};
+}
+export function annotationFilterNeedsRefresh(filters={}){return ['tag','tagged','tag_predicate'].some(key=>Object.hasOwn(filters,key));}
diff --git a/workspace-app/src/api.js b/workspace-app/src/api.js
index 68a8a65..aa05144 100644
--- a/workspace-app/src/api.js
+++ b/workspace-app/src/api.js
@@ -1,7 +1,12 @@
import {startResourceRequest,visibleResourceState} from './resourceRequest.js';
import {cachedResourceRequest,epochResourceCache,prefetchEpochMetadata,requestEpochWithTrace} from './resourceCache.js';
import { useCallback, useEffect, useState } from 'react';
-export async function api(path, options = {}) {
+import {trackWrite} from './desktopLifecycle.js';
+export function api(path,options={}){
+ const operation=requestApi(path,options);
+ return ['POST','PUT','PATCH','DELETE'].includes((options.method||'GET').toUpperCase())?trackWrite(operation):operation;
+}
+async function requestApi(path, options = {}) {
const response = await fetch(`/api${path}`, {
...options, headers: { 'Content-Type': 'application/json', 'X-Workspace-Request': '1', ...options.headers },
body: options.body === undefined ? undefined : JSON.stringify(options.body),
diff --git a/workspace-app/src/appUpdates.js b/workspace-app/src/appUpdates.js
index 415eda7..f6269cb 100644
--- a/workspace-app/src/appUpdates.js
+++ b/workspace-app/src/appUpdates.js
@@ -1,11 +1,21 @@
-const releaseRoot='https://github.com/maxwellsdm1867/epicTreeGUI/releases';
+const releaseRoot='https://github.com/maxwellsdm1867/Rieke-OS/releases';
export function releaseLink(value){
if(typeof value!=='string')return null;
- try{const url=new URL(value);return url.origin==='https://github.com'&&(url.pathname==='/maxwellsdm1867/epicTreeGUI/releases'||url.pathname.startsWith('/maxwellsdm1867/epicTreeGUI/releases/tag/'))&&!url.username&&!url.password?url.href:null;}catch{return null;}
+ try{const url=new URL(value);return url.origin==='https://github.com'&&(url.pathname==='/maxwellsdm1867/Rieke-OS/releases'||url.pathname.startsWith('/maxwellsdm1867/Rieke-OS/releases/tag/'))&&!url.username&&!url.password?url.href:null;}catch{return null;}
}
export function updateNotice(status){
const version=typeof status?.available==='string'?status.available:status?.available?.version;
- return status?.state==='update_available'&&version?{version,message:`Rieke OS ${version} is available`,url:releaseLink(status.release_url)||releaseRoot}:null;
+ return (status?.state==='update_available'||['Available','Downloading','Validating','Ready','Draining','Installing'].includes(status?.state))&&version?{version,message:`Rieke OS ${version} is available`,url:releaseLink(status.release_url)||releaseRoot}:null;
+}
+
+// A failed refresh does not erase a release we already discovered. A successful
+// response (including a withdrawn release) replaces the previous status.
+export function mergeUpdateCheck(previous,result){
+ if(result?.state!=='error')return result;
+ if(!updateNotice(previous))return {...previous,...result};
+ return {...previous,...result,state:previous.state,available:previous.available,
+ release_url:previous.release_url,release_notes:previous.release_notes,
+ checked_at:previous.checked_at,check_error:result.message};
}
export function updateLabel(status,busy=false){
diff --git a/workspace-app/src/appUpdates.test.js b/workspace-app/src/appUpdates.test.js
index b79c7e1..52d4967 100644
--- a/workspace-app/src/appUpdates.test.js
+++ b/workspace-app/src/appUpdates.test.js
@@ -1,13 +1,34 @@
import test from 'node:test';
import assert from 'node:assert/strict';
-import {releaseLink,updateNotice,updateLabel,watchAppUpdates} from './appUpdates.js';
-test('only an available release creates a notification',()=>{
+import {releaseLink,updateNotice,updateLabel,watchAppUpdates,mergeUpdateCheck} from './appUpdates.js';
+test('only an available release creates an update indicator',()=>{
for(const state of ['unavailable','unconfigured','error','up_to_date'])assert.equal(updateNotice({state,available:'1.0.0'}),null);
assert.equal(updateNotice({state:'update_available',available:{version:'1.2.0'}}).version,'1.2.0');
});
+test('a failed refresh retains a known update until a successful check replaces it',()=>{
+ const previous={state:'update_available',available:'1.2.0',installed:'1.1.0',
+ release_url:'https://github.com/maxwellsdm1867/Rieke-OS/releases/tag/v1.2.0',
+ release_notes:'Changes',checked_at:'2026-09-29T00:00:00Z',can_stage:true};
+ const failed=mergeUpdateCheck(previous,{state:'error',available:null,release_url:null,
+ checked_at:'2026-09-29T00:15:00Z',message:'Offline',can_stage:false});
+ assert.equal(updateNotice(failed).version,'1.2.0');
+ assert.equal(failed.release_url,previous.release_url);
+ assert.equal(failed.release_notes,previous.release_notes);
+ assert.equal(failed.checked_at,previous.checked_at);
+ assert.equal(failed.can_stage,false);
+ assert.equal(failed.check_error,'Offline');
+ for(const state of ['up_to_date','unavailable']){
+ const refreshed=mergeUpdateCheck(failed,{state,available:null});
+ assert.equal(updateNotice(refreshed),null);
+ assert.equal(refreshed.check_error,undefined);
+ }
+ assert.equal(mergeUpdateCheck(null,{state:'error'}).state,'error');
+ assert.equal(mergeUpdateCheck({state:'up_to_date',installed:'1.2.0'},{state:'error'}).installed,'1.2.0');
+ assert.equal(updateNotice(mergeUpdateCheck(previous,{state:'update_available',available:'1.3.0'})).version,'1.3.0');
+});
test('release links cannot navigate to executable or untrusted destinations',()=>{
- for(const value of ['javascript:alert(1)','https://github.com.evil.test/releases','https://github.com/other/repo/releases','https://user:password@github.com/maxwellsdm1867/epicTreeGUI/releases'])assert.equal(releaseLink(value),null);
- assert.ok(releaseLink('https://github.com/maxwellsdm1867/epicTreeGUI/releases/tag/v1.0.0'));
+ for(const value of ['javascript:alert(1)','https://github.com.evil.test/releases','https://github.com/other/repo/releases','https://user:password@github.com/maxwellsdm1867/Rieke-OS/releases'])assert.equal(releaseLink(value),null);
+ assert.ok(releaseLink('https://github.com/maxwellsdm1867/Rieke-OS/releases/tag/v1.0.0'));
});
test('automatically checks at startup, periodically, and on return after elapsed interval',()=>{
let calls=0,tick,visible,time=0,cleared=false;
diff --git a/workspace-app/src/appUpdatesLifecycle.test.js b/workspace-app/src/appUpdatesLifecycle.test.js
new file mode 100644
index 0000000..e4af0a9
--- /dev/null
+++ b/workspace-app/src/appUpdatesLifecycle.test.js
@@ -0,0 +1,97 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import React from 'react';
+import TestRenderer,{act} from 'react-test-renderer';
+import {createServer} from 'vite';
+import {fileURLToPath} from 'node:url';
+
+const testing = {state:'Current',installed:'0.1.3',channel:'unsigned-testing',manual_updates:true};
+function text(node){if(typeof node==='string')return node;if(!node)return '';return (Array.isArray(node)?node:node.children||[]).map(text).join('');}
+async function harness(initial=testing,{source=false}={}){
+ const old={window:globalThis.window,document:globalThis.document,fetch:globalThis.fetch};
+ const fixture={status:initial,downloads:0,restarts:0,checks:0,requests:[]};let listener;
+ const bridge={status:async()=>fixture.status,onStatus:fn=>{listener=fn;return()=>{listener=null;};},
+ checkForUpdates:async()=>{fixture.checks++;return fixture.checkResult||fixture.status;},
+ downloadUpdate:async()=>{fixture.downloads++;if(fixture.downloadError)throw Error(fixture.downloadError);return fixture.downloadResult||fixture.status;},
+ restartToUpdate:async()=>{fixture.restarts++;return fixture.restartResult||{ready:false,reason:'An import is still active.'};},openReleaseNotes:async()=>{}};
+ globalThis.window=source?{}:{riekeDesktop:bridge};globalThis.document={body:{nodeType:1},visibilityState:'visible',addEventListener(){},removeEventListener(){}};
+ if(source)globalThis.fetch=async(url,options)=>{
+ fixture.requests.push({url,method:options.method,headers:options.headers});let value;
+ if(url==='/api/app/updates/check'){fixture.checks++;value=fixture.status;}
+ else if(url==='/api/app/updates/stage'){fixture.downloads++;value={state:'complete',result:{version:'1.2.0'}};}
+ else throw Error('Unexpected source update endpoint');
+ return {ok:true,json:async()=>value};
+ };
+ const server=await createServer({root:fileURLToPath(new URL('..',import.meta.url)),configFile:false,
+ server:{middlewareMode:true,hmr:false,ws:false},ssr:{noExternal:['react-dom']},appType:'custom',logLevel:'error',esbuild:{jsx:'automatic'},
+ plugins:[{name:'update-dialog-browser-boundary',enforce:'pre',resolveId:id=>id==='react-dom'?'\0update-portal':undefined,
+ load:id=>id==='\0update-portal'?'export const createPortal=children=>children;':undefined}]});
+ const {default:AppUpdates}=await server.ssrLoadModule('/src/components/AppUpdates.jsx');let renderer;
+ await act(async()=>{renderer=TestRenderer.create(React.createElement(AppUpdates),{createNodeMock:node=>node.type==='dialog'?{showModal(){},close(){}}:null});});
+ const h={fixture,get text(){return text(renderer.toJSON());},get buttons(){return renderer.root.findAllByType('button');},
+ async click(name){const button=h.buttons.find(node=>text(node).trim()===name||node.props['aria-label']===name);assert.ok(button,`Button missing: ${name}`);await act(async()=>button.props.onClick());},
+ async open(){await act(async()=>h.buttons.find(node=>node.props['aria-haspopup']==='dialog').props.onClick());},
+ async emit(status){fixture.status=status;await act(async()=>listener(status));},
+ async close(){try{await act(async()=>renderer.unmount());}finally{await server.close();for(const [key,value]of Object.entries(old)){if(value===undefined)delete globalThis[key];else globalThis[key]=value;}}}};
+ return h;
+}
+test('unsigned testing explains background checks and explicit downloads without promising automatic installation',async()=>{
+ const h=await harness();try{await h.open();assert.match(h.text,/Unsigned testing/);assert.match(h.text,/checked automatically/);
+ assert.match(h.text,/choose when to download/i);assert.doesNotMatch(h.text,/download quietly|download automatically/);
+ assert.equal(h.fixture.downloads,0);assert.equal(h.fixture.restarts,0);
+ }finally{await h.close();}
+});
+test('a background release notification shows its version without downloading or restarting',async()=>{
+ const h=await harness();try{
+ await h.emit({...testing,state:'Available',available:'0.1.4',can_download:true});
+ assert.match(h.text,/0\.1\.4/);assert.equal(h.fixture.downloads,0);assert.equal(h.fixture.restarts,0);
+ }finally{await h.close();}
+});
+test('checking offers a download and only an explicit download prepares the desktop update',async()=>{
+ const h=await harness();try{
+ h.fixture.checkResult={...testing,state:'Available',available:'0.1.4',can_download:true};
+ h.fixture.downloadResult={...testing,state:'Ready',available:'0.1.4',can_download:false};
+ await h.open();await h.click('Check for updates');assert.equal(h.fixture.downloads,0);
+ await h.click('Download update');assert.equal(h.fixture.downloads,1);assert.equal(h.fixture.restarts,0);
+ assert.match(h.text,/Update ready/);
+ }finally{await h.close();}
+});
+test('restart is explicit and an active import keeps the prepared update and dialog usable',async()=>{
+ const h=await harness({...testing,state:'Ready',available:'0.1.4',can_download:false});try{
+ await h.open();assert.equal(h.fixture.restarts,0);
+ await h.click('Restart to update');assert.equal(h.fixture.restarts,1);
+ assert.match(h.text,/An import is still active/);assert.match(h.text,/Update ready/);
+ assert.equal(h.buttons.find(node=>text(node).trim()==='Restart to update').props.disabled,false);
+ await h.click('Close updates');assert.doesNotMatch(h.text,/An import is still active/);
+ }finally{await h.close();}
+});
+test('successful restart handoff disables further update actions while the app installs',async()=>{
+ const h=await harness({...testing,state:'Ready',available:'0.1.4',can_download:false});try{
+ h.fixture.restartResult={ready:true,installing:true};await h.open();await h.click('Restart to update');
+ assert.match(h.text,/Restarting to update/);assert.equal(h.buttons.find(node=>text(node).trim()==='Check for updates').props.disabled,true);
+ assert.equal(h.buttons.some(node=>text(node).trim()==='Restart to update'),false);
+ }finally{await h.close();}
+});
+test('source installation preserves its authorized HTTP staging flow and next-launch instructions',async()=>{
+ const h=await harness({state:'update_available',installed:'1.1.0',available:'1.2.0',can_stage:true,update_token:'source-update-token'},{source:true});
+ try{await h.open();assert.doesNotMatch(h.text,/Unsigned testing/);await h.click('Download update');
+ assert.match(h.text,/next launch will use this version/);assert.equal(h.fixture.restarts,0);
+ assert.equal(h.buttons.some(node=>text(node).trim()==='Restart to update'),false);
+ const request=h.fixture.requests.find(request=>request.url==='/api/app/updates/stage');
+ assert.equal(request.method,'POST');assert.equal(request.headers['X-Rieke-Update-Token'],'source-update-token');
+ }finally{await h.close();}
+});
+test('a failed desktop download reports the failure and never enables restart',async()=>{
+ const h=await harness({...testing,state:'Available',available:'0.1.4',can_download:true});try{
+ h.fixture.downloadError='The downloaded app failed checksum verification.';await h.open();await h.click('Download update');
+ assert.match(h.text,/failed checksum verification/);assert.equal(h.fixture.restarts,0);
+ assert.equal(h.buttons.some(node=>text(node).trim()==='Restart to update'),false);
+ await h.click('Close updates');assert.match(h.text,/0\.1\.4/);
+ }finally{await h.close();}
+});
+test('prepared testing update notice explains explicit restart rather than ordinary next launch',async()=>{
+ const h=await harness({...testing,state:'Ready',available:'0.1.4'});try{
+ const notice=h.buttons.find(node=>node.props['aria-haspopup']==='dialog');
+ assert.match(notice.props.title,/choose Restart to update/);assert.doesNotMatch(notice.props.title,/next launch/);
+ }finally{await h.close();}
+});
diff --git a/workspace-app/src/components/AnnotationTags.jsx b/workspace-app/src/components/AnnotationTags.jsx
index e8f11d7..8c8d43c 100644
--- a/workspace-app/src/components/AnnotationTags.jsx
+++ b/workspace-app/src/components/AnnotationTags.jsx
@@ -1,27 +1,39 @@
-import {useEffect,useId,useRef,useState} from 'react';
+import {useEffect,useId,useLayoutEffect,useRef,useState} from 'react';
import {MessageCircle,Plus,X,Search,ChevronDown,UserRound,CornerDownRight} from 'lucide-react';
import {api,useResource,number} from '../api.js';
import {useAnnotationProfile} from '../annotationProfile.js';
import {annotationTagColor,annotationChange,annotationGroups,canRemoveAnnotation,annotationPredicate,bulkAnnotationChange,navigateAfterTagSave} from '../annotationTags.js';
import './AnnotationTags.css';
+import {confirmAnnotationReceipt,fastAnnotationReceipt} from '../annotationReceipts.js';
+import {epochResourceCache} from '../resourceCache.js';
-export default function AnnotationTags({epoch,revision,disabled=false,onChange,onFilter,focusRequest=0,epochFocusRequest=0,onNavigateEpoch,tools,children,selectedEpochs=[],targetScope=null}){
+export default function AnnotationTags({epoch,revision,disabled=false,onChange,onFilter,focusRequest=0,epochFocusRequest=0,onNavigateEpoch,tools,children,selectedEpochs=[],targetScope=null,refreshWithEpoch=false,reconcileReceipt=false}){
const {profileUuid,profileName,openProfile,loading:profileLoading,error:profileError}=useAnnotationProfile();
- const [refreshIdentity,setRefreshIdentity]=useState(null);
+ const [refreshAfter,setRefreshAfter]=useState(null);
const [tabLocked,setTabLocked]=useState(()=>{try{return localStorage.getItem('workspace.tags.tabNavigation')!=='false';}catch{return true;}});
const handledEpochFocus=useRef(0),restoreInput=useRef(false);
function toggleTabLock(checked){setTabLocked(checked);try{localStorage.setItem('workspace.tags.tabNavigation',String(checked));}catch{}input.current?.focus();}
const [manualScope,setScope]=useState('epoch'),[value,setValue]=useState(''),[query,setQuery]=useState(''),[open,setOpen]=useState(false),[active,setActive]=useState(-1),[busy,setBusy]=useState(false),[error,setError]=useState(''),[message,setMessage]=useState('');
const scope=targetScope||manualScope;
const input=useRef(null),handledFocus=useRef(0),listId=useId(),identity=epoch?.epoch_uuid,currentIdentity=useRef(identity);currentIdentity.current=identity;
- const remoteNeeded=!epoch?.annotations||refreshIdentity===identity;
+ const refreshPending=refreshAfter?.identity===identity&&refreshAfter.annotations===epoch?.annotations;
+ const remoteNeeded=!epoch?.annotations||((!refreshWithEpoch||refreshAfter?.force)&&refreshPending);
+ const awaitingEpoch=refreshWithEpoch&&!refreshAfter?.force&&refreshPending;
+ const scopeIdentity=JSON.stringify([identity,epoch?.cell_uuid,scope,profileUuid,revision,selectedEpochs]);
+ const committedScope=useRef(null),generation=useRef(0),mounted=useRef(false),mutation=useRef(null);
+ const navigationIdentity=JSON.stringify([identity,epoch?.cell_uuid,scope,profileUuid,selectedEpochs]),committedNavigation=useRef(navigationIdentity);
+ useLayoutEffect(()=>{committedNavigation.current=navigationIdentity;},[navigationIdentity]);
+ useLayoutEffect(()=>{
+ mounted.current=true;committedScope.current=scopeIdentity;generation.current++;
+ return()=>{mounted.current=false;committedScope.current=null;generation.current++;mutation.current?.abort();};
+ },[scopeIdentity]);
const annotations=useResource(identity&&remoteNeeded?`/epochs/${identity}/annotations`:null,revision);
const annotationData=remoteNeeded?(annotations.data||epoch?.annotations):epoch?.annotations;
const suggestions=useResource(open?`/annotation-tags?q=${encodeURIComponent(query)}&limit=12`:null,revision,120);
const groups=annotationGroups(annotationData),items=suggestions.data?.tags||[];
- const editingLocked=disabled||busy||(remoteNeeded&&annotations.loading)||!!annotations.error||!annotationData;
+ const editingLocked=disabled||busy||awaitingEpoch||(remoteNeeded&&annotations.loading)||!!annotations.error||!annotationData;
const locked=editingLocked||profileLoading||!profileUuid;
- useEffect(()=>{setValue('');setQuery('');setActive(-1);setMessage('');setError('');setOpen(false);setScope('epoch');setRefreshIdentity(null);},[identity]);
+ useEffect(()=>{setValue('');setQuery('');setActive(-1);setMessage('');setError('');setOpen(false);setScope('epoch');setRefreshAfter(null);},[identity]);
// One request debounce; clear the active choice immediately when the text changes.
useEffect(()=>{if(focusRequest&&focusRequest!==handledFocus.current&&!editingLocked&&!profileLoading){setScope(selectedEpochs.length?'selected':'epoch');if(!profileUuid){openProfile?.();return;}handledFocus.current=focusRequest;input.current?.focus();setOpen(true);}},[focusRequest,editingLocked,profileLoading,profileUuid,selectedEpochs.length]);
useEffect(()=>{
@@ -32,23 +44,48 @@ export default function AnnotationTags({epoch,revision,disabled=false,onChange,o
useEffect(()=>setActive(-1),[query]);
useEffect(()=>{if(scope==='selected'&&!selectedEpochs.length)setScope('epoch');},[scope,selectedEpochs.length]);
async function mutate(tag,targetKind=scope,remove=false){
- if(locked)return false;restoreInput.current=true;setBusy(true);setError('');setMessage('');
- try{const body=targetKind==='selected'?bulkAnnotationChange({targetUuids:selectedEpochs,profileUuid,tag,read:await api('/annotations/read',{method:'POST',body:{target_kind:'epoch',target_uuids:[...new Set(selectedEpochs)]}})}):annotationChange({epoch,targetKind,profileUuid,annotations:annotationData,tag,remove});
+ if(locked||mutation.current||committedScope.current!==scopeIdentity)return false;
+ const controller=new AbortController(),token=generation.current;
+ mutation.current=controller;
+ const isCurrent=()=>mounted.current&&generation.current===token&&committedScope.current===scopeIdentity;
+ restoreInput.current=true;setBusy(true);setError('');setMessage('');
+ try{
+ let body;
+ if(targetKind==='selected'){
+ const read=await api('/annotations/read',{method:'POST',signal:controller.signal,body:{target_kind:'epoch',target_uuids:[...new Set(selectedEpochs)]}});
+ if(!isCurrent())return false;
+ body=bulkAnnotationChange({targetUuids:selectedEpochs,profileUuid,tag,read});
+ }else body=annotationChange({epoch,targetKind,profileUuid,annotations:annotationData,tag,remove});
+ if(!isCurrent())return false;
+ // A submitted durable mutation is never cancelled or automatically retried.
const result=await api('/annotations',{method:'POST',body});
- if(!result||!Object.hasOwn(result,'changed'))throw new Error('The server did not return an annotation receipt. Refresh tags before retrying.');
- if(currentIdentity.current===identity){setValue('');setQuery('');setActive(-1);setOpen(false);setMessage(`${remove?'Removed':'Saved'} ${targetKind==='selected'?`${number(body.target_uuids.length)} selected epoch`:targetKind} tag “${tag}”.`);setRefreshIdentity(identity);annotations.reload();}onChange?.(result);return true;
- }catch(error){if(currentIdentity.current===identity)setError(error.message);return false;}finally{setBusy(false);}
+ let confirmed=null;
+ if(reconcileReceipt){
+ try{confirmed=confirmAnnotationReceipt(result,body);}catch(error){onChange?.(result);throw error;}
+ epochResourceCache.invalidateAnnotations(confirmed);
+ }else if(!result||!Object.hasOwn(result,'changed'))throw new Error('The server did not return an annotation receipt. Refresh tags before retrying.');
+ if(isCurrent()){
+ setValue('');setQuery('');setActive(-1);setOpen(false);
+ setMessage(`${remove?'Removed':'Saved'} ${targetKind==='selected'?`${number(body.target_uuids.length)} selected epoch`:targetKind} tag “${tag}”.`);
+ setRefreshAfter(fastAnnotationReceipt(confirmed)?null:{identity,annotations:epoch?.annotations});
+ // Inspector already refreshes the authoritative epoch with its new view
+ // revision. Standalone tag editors retain their explicit annotation read.
+ if(!refreshWithEpoch)annotations.reload();
+ }
+ onChange?.(result,isCurrent()?confirmed:null);return true;
+ }catch(error){if(isCurrent())setError(error.message);return false;}
+ finally{if(mutation.current===controller)mutation.current=null;if(mounted.current)setBusy(false);}
}
async function tagKeys(event){
if(scope!=='epoch'||event.target!==input.current||!onNavigateEpoch||!tabLocked||event.key!=='Tab'||event.altKey||event.ctrlKey||event.metaKey||event.isComposing||event.nativeEvent?.isComposing)return;
if(event.target.closest('dialog,[role="dialog"],.annotation-legacy'))return;
event.preventDefault();event.stopPropagation();
if(editingLocked||profileLoading)return;
- const direction=event.shiftKey?-1:1,current=identity,draft=open&&items[active]?items[active].tag:value;
+ const direction=event.shiftKey?-1:1,current=identity,navigationAtSave=committedNavigation.current,draft=open&&items[active]?items[active].tag:value;
setOpen(false);
// Never discard a draft: save first, and stay here if that save fails.
if(draft.trim()&&!profileUuid){openProfile?.();return;}
- await navigateAfterTagSave({draft,save:tag=>mutate(tag),isCurrent:()=>currentIdentity.current===current,navigate:onNavigateEpoch,direction});
+ await navigateAfterTagSave({draft,save:tag=>mutate(tag),isCurrent:()=>currentIdentity.current===current&&committedNavigation.current===navigationAtSave,navigate:direction=>onNavigateEpoch(direction,{afterSave:!!draft.trim()}),direction});
}
function choose(tag){setValue(tag);setQuery(tag.trim());setOpen(false);setActive(-1);input.current?.focus();}
function submitTag(event){
@@ -71,7 +108,7 @@ export default function AnnotationTags({epoch,revision,disabled=false,onChange,o
{scope==='cell'&&Tags apply to {Number.isFinite(annotationData?.cell_epoch_count)?number(annotationData.cell_epoch_count):'all'} epochs in this cell.
}
{if(!event.currentTarget.contains(event.relatedTarget))setOpen(false);}}>
{open&&
{suggestions.loading?Finding tags…:suggestions.error?Suggestions unavailable; typed tags can still be saved.:items.length?items.map((item,index)=>):Type a tag, then click Add tag or press Enter.}
}
- {(profileError||annotations.error||error)&&{error||annotations.error||profileError}
}
+ {(profileError||annotations.error||error)&&{error||annotations.error||profileError}
}
{remoteNeeded&&annotations.loading&&!annotationData?Loading annotations…:<>{scope==='epoch'&&Direct epoch tags{chips(groups.epoch,'epoch')}{!groups.epoch.length&&None}
}{scope!=='selected'&&{scope==='cell'?'Cell tags':'Inherited from cell'} {!targetScope&&}{chips(groups.cell,'cell')}{!groups.cell.length&&None}
}>}
{message&&{message}}
{children&&scope==='epoch'&& Dataset-only tags
These existing tags belong to this protocol’s selection; they are separate from shared cell and epoch annotations.
{children} }
diff --git a/workspace-app/src/components/AppUpdates.css b/workspace-app/src/components/AppUpdates.css
index cd81c67..71305fc 100644
--- a/workspace-app/src/components/AppUpdates.css
+++ b/workspace-app/src/components/AppUpdates.css
@@ -1,7 +1,5 @@
-.app-update-button{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;font-size:12px}.app-update-button.has-update{color:#673d99;background:#f1e9fb;border-color:#b898db}.app-update-notice{position:fixed;right:20px;top:70px;z-index:200;display:flex;align-items:center;gap:12px;max-width:calc(100vw - 48px);padding:16px;border:1px solid #b898db;border-radius:12px;background:#fff;box-shadow:0 8px 32px #35204526;color:#39264c}.app-update-notice strong{display:block}.app-update-notice div>button{margin-top:8px}.app-update-dialog{width:min(480px,calc(100vw - 40px));padding:24px;border:1px solid #d7cce2;border-radius:14px;color:#352845;background:white}.app-update-dialog::backdrop{background:#21152d66}.app-update-dialog header,.app-update-dialog footer{display:flex;align-items:center;justify-content:space-between;gap:16px}.app-update-dialog h2{margin:0;font-size:20px}.app-update-dialog p{line-height:1.6}.app-update-dialog footer{margin-top:24px}.app-update-dialog small{color:#6c6177}
+.app-update-button{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;font-size:12px}.app-update-button.has-update{color:#673d99;background:#f1e9fb;border-color:#b898db}.app-update-dialog{width:min(480px,calc(100vw - 40px));padding:24px;border:1px solid #d7cce2;border-radius:14px;color:#352845;background:white}.app-update-dialog::backdrop{background:#21152d66}.app-update-dialog header,.app-update-dialog footer{display:flex;align-items:center;justify-content:space-between;gap:16px}.app-update-dialog h2{margin:0;font-size:20px}.app-update-dialog p{line-height:1.6}.app-update-dialog footer{margin-top:24px}.app-update-dialog small{color:#6c6177}
-.launcher-release-tools{display:flex;justify-content:center;margin:24px auto}.app-update-nav{width:100%}.app-update-dot{width:7px;height:7px;border-radius:50%;background:#8756b0;margin-left:auto;flex-shrink:0}
+.launcher-release-tools{display:flex;justify-content:center;margin:24px auto}.app-update-nav{width:100%}.app-update-nav.has-update{color:#583579;background:#e7dcf0;font-weight:650}.app-update-nav.has-update:hover{background:#dfd0eb}.app-update-badge{margin-left:auto;flex-shrink:0;padding:2px 6px;border-radius:5px;background:#76518e;color:#fff;font-size:9px;line-height:1.5;font-weight:650}
.project-closed-notice{max-width:760px;margin:24px auto;padding:20px;border:1px solid #bfb0d0;border-radius:12px;background:#f8f4fc}.project-closed-notice code{display:block;overflow-wrap:anywhere;user-select:all}.project-closed-notice p{line-height:1.5}
-
-body:has(.import-toast) .app-update-notice{top:160px}
diff --git a/workspace-app/src/components/AppUpdates.jsx b/workspace-app/src/components/AppUpdates.jsx
index ad638a8..2d56420 100644
--- a/workspace-app/src/components/AppUpdates.jsx
+++ b/workspace-app/src/components/AppUpdates.jsx
@@ -2,43 +2,61 @@ import {useEffect,useRef,useState} from 'react';
import {createPortal} from 'react-dom';
import {ArrowUpCircle,RefreshCw,X} from 'lucide-react';
import {api} from '../api.js';
-import {updateNotice,releaseLink,watchAppUpdates} from '../appUpdates.js';
+import {updateNotice,releaseLink,watchAppUpdates,mergeUpdateCheck} from '../appUpdates.js';
import './AppUpdates.css';
+import {desktopBridge} from '../desktopLifecycle.js';
-function UpdateDialog({status,busy,error,onCheck,onClose,onDownload,download}){
+function UpdateDialog({status,busy,operation,error,onCheck,onClose,onDownload,onRestart,download}){
const dialog=useRef(null);
useEffect(()=>{dialog.current.showModal();const element=dialog.current;return()=>element.close();},[]);
const installed=typeof status?.installed==='string'?status.installed:status?.installed?.version;
const notice=updateNotice(status),url=releaseLink(status?.release_url);
+ const desktop=!!desktopBridge();
+ const testing=desktop&&status?.channel==='unsigned-testing';
+ const ready=status?.state==='Ready'||!!notice&&(status?.staged_version===notice.version||download?.state==='complete'&&download.result?.version===notice.version);
return createPortal(,document.body);
}
export default function AppUpdates({sidebar=false}){
- const [status,setStatus]=useState(null),[busy,setBusy]=useState(false),[error,setError]=useState(''),[open,setOpen]=useState(false),[dismissed,setDismissed]=useState(null),[noticePaused,setNoticePaused]=useState(false);
+ const [status,setStatus]=useState(null),[busy,setBusy]=useState(false),[error,setError]=useState(''),[open,setOpen]=useState(false);
const alive=useRef(false),checking=useRef(false);
const [download,setDownload]=useState(null);
+ const [operation,setOperation]=useState(null);
async function check(force=false){
if(checking.current)return;checking.current=true;setBusy(true);setError('');
- try{const result=await api('/app/updates/check',{method:'POST',body:force?{force:true}:{}});if(alive.current){setStatus(result);if(result.download)setDownload(result.download);}}
- catch(error){if(alive.current){setError(error.message);setStatus(previous=>({...previous,state:'error',message:'Could not check for updates. The current app remains usable.'}));}}
+ try{const bridge=desktopBridge();const result=bridge?await bridge.checkForUpdates():await api('/app/updates/check',{method:'POST',body:force?{force:true}:{}});if(alive.current){setStatus(previous=>mergeUpdateCheck(previous,result));if(result.download)setDownload(result.download);}}
+ catch(error){if(alive.current){setError(error.message);setStatus(previous=>mergeUpdateCheck(previous,{state:'error',message:'Could not check for updates. The current app remains usable.'}));}}
finally{checking.current=false;if(alive.current)setBusy(false);}
}
async function stage(){
- setError('');
- try{const result=await api('/app/updates/stage',{method:'POST',body:{},headers:{'X-Rieke-Update-Token':status.update_token}});if(alive.current)setDownload(result);}
+ setError('');setOperation('download');
+ try{const bridge=desktopBridge();if(bridge){const result=await bridge.downloadUpdate();if(alive.current&&result?.state)setStatus(result);}
+ else{const result=await api('/app/updates/stage',{method:'POST',body:{},headers:{'X-Rieke-Update-Token':status.update_token}});if(alive.current)setDownload(result);}}
catch(error){if(alive.current)setError(error.message);}
+ finally{if(alive.current)setOperation(null);}
+ }
+ async function restart(){
+ setError('');setOperation('restart');
+ try{const result=await desktopBridge().restartToUpdate();if(alive.current){if(!result?.ready)setError(result?.reason||'The app is waiting for its current work to finish.');else if(result.installing)setStatus(previous=>({...previous,state:'Installing'}));}}
+ catch(error){if(alive.current)setError(error.message);}
+ finally{if(alive.current)setOperation(null);}
}
useEffect(()=>{
if(download?.state!=='running')return;
@@ -46,10 +64,10 @@ export default function AppUpdates({sidebar=false}){
async function poll(){try{const result=await api('/app/updates/download');if(!cancelled){setError('');setDownload(result);if(result.state==='running')timer=setTimeout(poll,1500);}}catch(error){if(!cancelled){setError(`Update status unavailable: ${error.message}`);timer=setTimeout(poll,5000);}}}
timer=setTimeout(poll,1000);return()=>{cancelled=true;clearTimeout(timer);};
},[download?.state]);
- useEffect(()=>{alive.current=true;const stop=watchAppUpdates(()=>check());return()=>{alive.current=false;stop();};},[]);
+ useEffect(()=>{alive.current=true;const bridge=desktopBridge();if(bridge){bridge.status().then(result=>{if(alive.current)setStatus(result);}).catch(()=>{});const stop=bridge.onStatus(result=>{if(alive.current)setStatus(result);});return()=>{alive.current=false;stop();};}const stop=watchAppUpdates(()=>check());return()=>{alive.current=false;stop();};},[]);
const notice=updateNotice(status);
- useEffect(()=>{if(!notice||noticePaused||dismissed===notice.version)return;const timer=setTimeout(()=>setDismissed(notice.version),10000);return()=>clearTimeout(timer);},[notice?.version,noticePaused,dismissed]);
- return <>
- {notice&&dismissed!==notice.version&&setNoticePaused(true)} onMouseLeave={()=>setNoticePaused(false)} onFocus={()=>setNoticePaused(true)} onBlur={event=>{if(!event.currentTarget.contains(event.relatedTarget))setNoticePaused(false);}}>
{notice.message}
}
- {open&&check(true)} onDownload={stage} download={download} onClose={()=>setOpen(false)}/>}>;
+ const ready=status?.state==='Ready'||!!notice&&(status?.staged_version===notice.version||download?.state==='complete'&&download.result?.version===notice.version);
+ const detail=ready?`Rieke OS ${notice?.version||status?.available||''} is ready${status?.channel==='unsigned-testing'?' — choose Restart to update':' for the next launch'}`:notice?.message;
+ return <>
+ {open&&check(true)} onDownload={stage} onRestart={restart} download={download} onClose={()=>setOpen(false)}/>}>;
}
diff --git a/workspace-app/src/components/BrowseFilesButton.jsx b/workspace-app/src/components/BrowseFilesButton.jsx
new file mode 100644
index 0000000..7ae72e7
--- /dev/null
+++ b/workspace-app/src/components/BrowseFilesButton.jsx
@@ -0,0 +1,12 @@
+import {useRef} from 'react';
+import {FolderOpen} from 'lucide-react';
+
+export default function BrowseFilesButton({label='Browse files…',accept,multiple=false,disabled=false,onChoose,className='button'}){
+ const input=useRef(null);
+ function choose(event){
+ const files=Array.from(event.target.files||[]);
+ event.target.value='';
+ if(files.length)onChoose?.(files);
+ }
+ return <>>;
+}
diff --git a/workspace-app/src/components/DesktopDraftRecovery.jsx b/workspace-app/src/components/DesktopDraftRecovery.jsx
new file mode 100644
index 0000000..307b057
--- /dev/null
+++ b/workspace-app/src/components/DesktopDraftRecovery.jsx
@@ -0,0 +1,18 @@
+import {useState} from 'react';
+export default function DesktopDraftRecovery({draft}){
+ const [busy,setBusy]=useState(false),[error,setError]=useState('');
+ async function act(operation){
+ setBusy(true);setError('');
+ try{const result=await operation();if(result?.ready===false)setError(result.reason||'The app is waiting for its active work to finish.');}
+ catch{setError('The saved view could not be recovered. Its existing copy is preserved.');}
+ finally{setBusy(false);}
+ }
+ return
+ Saved view needs recovery
{draft.message}
+ {error&&{error}
}
+
+ {draft.resetAllowed?:}
+
+
+ ;
+}
diff --git a/workspace-app/src/components/ExportSelectionDialog.jsx b/workspace-app/src/components/ExportSelectionDialog.jsx
index c38ca62..dfb24a9 100644
--- a/workspace-app/src/components/ExportSelectionDialog.jsx
+++ b/workspace-app/src/components/ExportSelectionDialog.jsx
@@ -4,21 +4,23 @@ import CandidateExportPanel from './CandidateExportPanel.jsx';
import ProtocolApplyPanel from './ProtocolApplyPanel.jsx';
import NewPinnedProtocol from './NewPinnedProtocol.jsx';
import {exportProtocolGroups,pinProtocolPreference} from '../exportProtocolTargets.js';
+import {useProjectPreference} from '../useProjectPreference.js';
import './ExportSelectionDialog.css';
export default function ExportSelectionDialog({candidate,protocols,projectId,initialProtocolId,defaultName,defaultFormat,disabled,onClose,onApplied,onChanged}){
const dialog=useRef(null),[mode,setMode]=useState(null),[busy,setBusy]=useState(false),[pinError,setPinError]=useState(''),[appliedId,setAppliedId]=useState(null);
- const key=`rieke-os.sidebar.protocols.v1.${projectId}`;
- function preferences(){try{const value=JSON.parse(localStorage.getItem(key)||'{}');return value&&typeof value==='object'&&!Array.isArray(value)?value:{};}catch{return {};}}
- const groups=exportProtocolGroups(protocols,preferences(),initialProtocolId);
+ const shortcuts=useProjectPreference(projectId,'protocol_shortcuts');
+ const groups=exportProtocolGroups(protocols,shortcuts.value,initialProtocolId);
useEffect(()=>{const el=dialog.current;el.showModal();return()=>el.close();},[]);
- function finishPin(id){
+ async function finishPin(id){
+ setAppliedId(id);setBusy(true);
try{
if(!projectId)throw new Error('Project identity unavailable');
- localStorage.setItem(key,JSON.stringify(pinProtocolPreference(preferences(),id)));
- window.dispatchEvent(new Event('rieke-protocol-shortcuts-changed'));
+ await shortcuts.update(previous=>pinProtocolPreference(previous,id));
+ setPinError('');
onApplied?.(id);
- }catch(error){setAppliedId(id);setPinError('Dataset updated, but the sidebar shortcut could not be saved. Retry pinning without applying the dataset again.');onChanged?.();}
+ }catch(error){setPinError('Dataset updated, but the sidebar shortcut could not be saved. Retry pinning without applying the dataset again.');onChanged?.();}
+ finally{setBusy(false);}
}
return