sandesh-relay 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sandesh_relay-0.2.2/.codacy.yaml +9 -0
- sandesh_relay-0.2.2/.github/workflows/publish-npm.yml +124 -0
- sandesh_relay-0.2.2/.github/workflows/publish-pypi.yml +124 -0
- sandesh_relay-0.2.2/.gitignore +16 -0
- sandesh_relay-0.2.2/CLAUDE.md +319 -0
- sandesh_relay-0.2.2/LICENSE +675 -0
- sandesh_relay-0.2.2/PKG-INFO +292 -0
- sandesh_relay-0.2.2/README.md +266 -0
- sandesh_relay-0.2.2/RELEASING.md +254 -0
- sandesh_relay-0.2.2/bin/sandesh +9 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-001-mcp-server.md +114 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-002-read-tools.md +82 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-003-mutating-tools.md +102 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-004-e2e-smoke.md +111 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-005-retire-status.md +98 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-006-docstrings-instructions.md +156 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-008-packaging.md +236 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-009-aur-pkgbuild.md +143 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-010-pypi-publish.md +181 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-011-mcp-registry.md +137 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-013-pi-verbs-extension.md +191 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-014-pi-native-wake.md +148 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-015-pi-package-listing.md +138 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-016-pi-error-handling-promptsnippet.md +133 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-017-migration-engine.md +301 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-018-migration-installer-integration.md +177 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-019-pi-tombstone-and-smoke.md +157 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-020-pypi-packaging-hardening.md +116 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-021-npm-pi-packaging-release-integrity.md +196 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-022-global-db-tracker-consolidation.md +137 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-023-cross-project-messaging.md +139 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-024-project-lifecycle-verbs.md +131 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-025-mcp-surface-update.md +77 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-026-inbox-filters.md +75 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-027-fts-search-engine.md +105 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-028-mcp-search-surface.md +69 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-029-projects-listing-tombstoned.md +51 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-030-pre-release-cleanup-sweep.md +87 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-031-pi-wake-followup.md +53 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-032-pi-surface-catchup.md +99 -0
- sandesh_relay-0.2.2/docs/changes/CR-SAN-034-release-pipeline-branch-gated.md +160 -0
- sandesh_relay-0.2.2/docs/changes/README.md +73 -0
- sandesh_relay-0.2.2/docs/research/DN-pi-wake.md +159 -0
- sandesh_relay-0.2.2/docs/research/DN-windows-notifier.md +63 -0
- sandesh_relay-0.2.2/docs/research/PRD-db-migration.md +154 -0
- sandesh_relay-0.2.2/docs/research/PRD-distribution.md +143 -0
- sandesh_relay-0.2.2/docs/research/PRD-global-store.md +242 -0
- sandesh_relay-0.2.2/docs/research/PRD-inbox-search.md +126 -0
- sandesh_relay-0.2.2/docs/research/PRD-mcp-server.md +293 -0
- sandesh_relay-0.2.2/docs/research/PRD-pi-extension.md +168 -0
- sandesh_relay-0.2.2/docs/research/PRD-project-lifecycle.md +153 -0
- sandesh_relay-0.2.2/install.sh +128 -0
- sandesh_relay-0.2.2/integrations/pi/.gitignore +4 -0
- sandesh_relay-0.2.2/integrations/pi/LICENSE +675 -0
- sandesh_relay-0.2.2/integrations/pi/README.md +97 -0
- sandesh_relay-0.2.2/integrations/pi/bun.lock +316 -0
- sandesh_relay-0.2.2/integrations/pi/package.json +40 -0
- sandesh_relay-0.2.2/integrations/pi/src/execute.test.ts +873 -0
- sandesh_relay-0.2.2/integrations/pi/src/index.test.ts +261 -0
- sandesh_relay-0.2.2/integrations/pi/src/index.ts +803 -0
- sandesh_relay-0.2.2/integrations/pi/src/package.test.ts +263 -0
- sandesh_relay-0.2.2/integrations/pi/src/prereq.test.ts +410 -0
- sandesh_relay-0.2.2/integrations/pi/src/smoke.test.ts +204 -0
- sandesh_relay-0.2.2/integrations/pi/src/surface_catchup.test.ts +696 -0
- sandesh_relay-0.2.2/integrations/pi/src/unregister.test.ts +366 -0
- sandesh_relay-0.2.2/integrations/pi/src/version_gate.test.ts +868 -0
- sandesh_relay-0.2.2/integrations/pi/src/version_sync.test.ts +65 -0
- sandesh_relay-0.2.2/integrations/pi/src/wake.test.ts +873 -0
- sandesh_relay-0.2.2/integrations/pi/src/wake_lifecycle.test.ts +715 -0
- sandesh_relay-0.2.2/integrations/pi/src/workflow.test.ts +398 -0
- sandesh_relay-0.2.2/integrations/pi/tsconfig.json +15 -0
- sandesh_relay-0.2.2/packaging/aur/.SRCINFO +17 -0
- sandesh_relay-0.2.2/packaging/aur/PKGBUILD +27 -0
- sandesh_relay-0.2.2/pyproject.toml +46 -0
- sandesh_relay-0.2.2/sandesh/__init__.py +9 -0
- sandesh_relay-0.2.2/sandesh/cli.py +621 -0
- sandesh_relay-0.2.2/sandesh/data/usage-scenarios.md +347 -0
- sandesh_relay-0.2.2/sandesh/mcp_server.py +726 -0
- sandesh_relay-0.2.2/sandesh/migrate.py +463 -0
- sandesh_relay-0.2.2/sandesh/migrations/.gitkeep +0 -0
- sandesh_relay-0.2.2/sandesh/migrations/0001-baseline.sql +46 -0
- sandesh_relay-0.2.2/sandesh/migrations/0002-drop-message-status.rollback.sql +6 -0
- sandesh_relay-0.2.2/sandesh/migrations/0002-drop-message-status.sql +25 -0
- sandesh_relay-0.2.2/sandesh/migrations/0003-project-tracker.rollback.sql +19 -0
- sandesh_relay-0.2.2/sandesh/migrations/0003-project-tracker.sql +45 -0
- sandesh_relay-0.2.2/sandesh/migrations/0004-xproj-grant.rollback.sql +18 -0
- sandesh_relay-0.2.2/sandesh/migrations/0004-xproj-grant.sql +41 -0
- sandesh_relay-0.2.2/sandesh/migrations/0005-message-fts.rollback.sql +5 -0
- sandesh_relay-0.2.2/sandesh/migrations/0005-message-fts.sql +20 -0
- sandesh_relay-0.2.2/sandesh/notify.py +103 -0
- sandesh_relay-0.2.2/sandesh/sandesh_db.py +1193 -0
- sandesh_relay-0.2.2/sandesh/schema/.gitkeep +0 -0
- sandesh_relay-0.2.2/sandesh/schema/current-schema.json +238 -0
- sandesh_relay-0.2.2/sandesh/schema/schema.meta.json +45 -0
- sandesh_relay-0.2.2/scripts/release.sh +244 -0
- sandesh_relay-0.2.2/server.json +34 -0
- sandesh_relay-0.2.2/tests/_migrate_helpers.py +29 -0
- sandesh_relay-0.2.2/tests/test_admin_grant.py +1476 -0
- sandesh_relay-0.2.2/tests/test_ci_migration_gate.py +536 -0
- sandesh_relay-0.2.2/tests/test_cleanup_sweep.py +852 -0
- sandesh_relay-0.2.2/tests/test_consolidate_skip.py +656 -0
- sandesh_relay-0.2.2/tests/test_consolidation.py +1218 -0
- sandesh_relay-0.2.2/tests/test_fts_cli.py +986 -0
- sandesh_relay-0.2.2/tests/test_fts_index.py +1123 -0
- sandesh_relay-0.2.2/tests/test_fts_search.py +1066 -0
- sandesh_relay-0.2.2/tests/test_global_store.py +954 -0
- sandesh_relay-0.2.2/tests/test_inbox_filters.py +932 -0
- sandesh_relay-0.2.2/tests/test_inbox_filters_cli.py +503 -0
- sandesh_relay-0.2.2/tests/test_install.py +1226 -0
- sandesh_relay-0.2.2/tests/test_lifecycle_archive.py +1214 -0
- sandesh_relay-0.2.2/tests/test_lifecycle_cli.py +1637 -0
- sandesh_relay-0.2.2/tests/test_lifecycle_e2e.py +444 -0
- sandesh_relay-0.2.2/tests/test_lifecycle_tombstone.py +1371 -0
- sandesh_relay-0.2.2/tests/test_mcp_e2e.py +1128 -0
- sandesh_relay-0.2.2/tests/test_mcp_lifecycle_tools.py +591 -0
- sandesh_relay-0.2.2/tests/test_mcp_missing_extra.py +254 -0
- sandesh_relay-0.2.2/tests/test_mcp_mutating_tools.py +725 -0
- sandesh_relay-0.2.2/tests/test_mcp_project_derivation.py +720 -0
- sandesh_relay-0.2.2/tests/test_mcp_read_tools.py +319 -0
- sandesh_relay-0.2.2/tests/test_mcp_search_surface.py +1064 -0
- sandesh_relay-0.2.2/tests/test_mcp_server.py +107 -0
- sandesh_relay-0.2.2/tests/test_mcp_surface.py +467 -0
- sandesh_relay-0.2.2/tests/test_migrate.py +5677 -0
- sandesh_relay-0.2.2/tests/test_migrate_global.py +730 -0
- sandesh_relay-0.2.2/tests/test_migration_0003.py +889 -0
- sandesh_relay-0.2.2/tests/test_migration_0004.py +1126 -0
- sandesh_relay-0.2.2/tests/test_migration_docs_boundary.py +489 -0
- sandesh_relay-0.2.2/tests/test_package.py +85 -0
- sandesh_relay-0.2.2/tests/test_pkgbuild.py +410 -0
- sandesh_relay-0.2.2/tests/test_project_scoping.py +779 -0
- sandesh_relay-0.2.2/tests/test_projects_listing_cli.py +479 -0
- sandesh_relay-0.2.2/tests/test_publish_npm_workflow.py +298 -0
- sandesh_relay-0.2.2/tests/test_publish_pypi_workflow.py +458 -0
- sandesh_relay-0.2.2/tests/test_publish_workflow.py +433 -0
- sandesh_relay-0.2.2/tests/test_pyproject.py +441 -0
- sandesh_relay-0.2.2/tests/test_release_script.py +929 -0
- sandesh_relay-0.2.2/tests/test_releasing_doc.py +237 -0
- sandesh_relay-0.2.2/tests/test_sandesh.py +225 -0
- sandesh_relay-0.2.2/tests/test_server_json.py +439 -0
- sandesh_relay-0.2.2/tests/test_tombstone_read_rules.py +728 -0
- sandesh_relay-0.2.2/tests/test_usage_resource_packaging.py +194 -0
- sandesh_relay-0.2.2/tests/test_version_scheme.py +326 -0
- sandesh_relay-0.2.2/tests/test_xproj_send.py +850 -0
- sandesh_relay-0.2.2/tests/test_xproj_visibility_wake.py +495 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Codacy analysis config — paths to exclude (Java glob syntax).
|
|
3
|
+
# Keeps static analysis focused on Sandesh's own code, not dev/test scaffolding
|
|
4
|
+
# or vendored dependencies. (The .codacy/ CLI cache itself is gitignored.)
|
|
5
|
+
exclude_paths:
|
|
6
|
+
- ".venv/**"
|
|
7
|
+
- "test-reports/**"
|
|
8
|
+
- "**/__pycache__/**"
|
|
9
|
+
- ".codacy/**"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
name: Publish Pi extension to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch: # manual dry-run path
|
|
7
|
+
pull_request: # build-check only (no publish)
|
|
8
|
+
push:
|
|
9
|
+
branches: [develop]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build-check:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: oven-sh/setup-bun@v2
|
|
20
|
+
- uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: '22'
|
|
23
|
+
registry-url: 'https://registry.npmjs.org'
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
working-directory: integrations/pi
|
|
26
|
+
run: bun install
|
|
27
|
+
- name: Type-check
|
|
28
|
+
working-directory: integrations/pi
|
|
29
|
+
run: bunx tsc --noEmit
|
|
30
|
+
- name: Test
|
|
31
|
+
working-directory: integrations/pi
|
|
32
|
+
run: bun test
|
|
33
|
+
- name: Pack check
|
|
34
|
+
working-directory: integrations/pi
|
|
35
|
+
run: npm pack --dry-run
|
|
36
|
+
|
|
37
|
+
publish-npm:
|
|
38
|
+
needs: build-check
|
|
39
|
+
if: github.event_name == 'release'
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
environment: npm
|
|
42
|
+
permissions:
|
|
43
|
+
id-token: write # OIDC trusted publishing
|
|
44
|
+
contents: read
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
with:
|
|
48
|
+
fetch-depth: 0 # guard needs origin/main history for the ancestry check
|
|
49
|
+
- name: Guard — tag ref + on-main ancestry
|
|
50
|
+
run: |
|
|
51
|
+
if ! [[ "$GITHUB_REF" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
52
|
+
echo "::error::refusing to publish: $GITHUB_REF is not a clean version tag (^refs/tags/v[0-9]+.[0-9]+.[0-9]+$)"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
git fetch --no-tags origin main
|
|
56
|
+
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
|
|
57
|
+
echo "::error::refusing to publish: $GITHUB_SHA is not an ancestor of origin/main"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
- uses: oven-sh/setup-bun@v2
|
|
61
|
+
- uses: actions/setup-node@v4
|
|
62
|
+
with:
|
|
63
|
+
node-version: '22'
|
|
64
|
+
registry-url: 'https://registry.npmjs.org'
|
|
65
|
+
- name: Install dependencies
|
|
66
|
+
working-directory: integrations/pi
|
|
67
|
+
run: bun install
|
|
68
|
+
- name: Verify release tag matches package versions (Arm B)
|
|
69
|
+
env:
|
|
70
|
+
GITHUB_REF_NAME: ${{ github.ref_name }}
|
|
71
|
+
run: |
|
|
72
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
73
|
+
echo "Release tag version: $VERSION"
|
|
74
|
+
PKG_VERSION=$(node -p "require('./integrations/pi/package.json').version")
|
|
75
|
+
SERVER_VERSION=$(node -p "require('./server.json').version")
|
|
76
|
+
SERVER_PKG_VERSION=$(node -p "require('./server.json').packages[0].version")
|
|
77
|
+
echo "package.json: $PKG_VERSION"
|
|
78
|
+
echo "server.json .version: $SERVER_VERSION"
|
|
79
|
+
echo "server.json .packages[0].version: $SERVER_PKG_VERSION"
|
|
80
|
+
if [ "$VERSION" != "$PKG_VERSION" ]; then
|
|
81
|
+
echo "::error::package.json version ($PKG_VERSION) != release tag ($VERSION)"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
if [ "$VERSION" != "$SERVER_VERSION" ]; then
|
|
85
|
+
echo "::error::server.json .version ($SERVER_VERSION) != release tag ($VERSION)"
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
if [ "$VERSION" != "$SERVER_PKG_VERSION" ]; then
|
|
89
|
+
echo "::error::server.json .packages[0].version ($SERVER_PKG_VERSION) != release tag ($VERSION)"
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
- name: Upgrade npm (>= 11.5.1 for OIDC trusted publishing)
|
|
93
|
+
run: npm install -g npm@latest
|
|
94
|
+
- name: Publish to npm
|
|
95
|
+
working-directory: integrations/pi
|
|
96
|
+
run: npm publish --access public
|
|
97
|
+
# no token secret — OIDC trusted publishing only (provenance auto)
|
|
98
|
+
|
|
99
|
+
dry-run:
|
|
100
|
+
needs: build-check
|
|
101
|
+
if: github.event_name == 'workflow_dispatch'
|
|
102
|
+
runs-on: ubuntu-latest
|
|
103
|
+
steps:
|
|
104
|
+
- uses: actions/checkout@v4
|
|
105
|
+
- uses: oven-sh/setup-bun@v2
|
|
106
|
+
- uses: actions/setup-node@v4
|
|
107
|
+
with:
|
|
108
|
+
node-version: '22'
|
|
109
|
+
registry-url: 'https://registry.npmjs.org'
|
|
110
|
+
- name: Install dependencies
|
|
111
|
+
working-directory: integrations/pi
|
|
112
|
+
run: bun install
|
|
113
|
+
- name: Type-check
|
|
114
|
+
working-directory: integrations/pi
|
|
115
|
+
run: bunx tsc --noEmit
|
|
116
|
+
- name: Test
|
|
117
|
+
working-directory: integrations/pi
|
|
118
|
+
run: bun test
|
|
119
|
+
- name: Pack check
|
|
120
|
+
working-directory: integrations/pi
|
|
121
|
+
run: npm pack --dry-run
|
|
122
|
+
- name: Publish dry-run
|
|
123
|
+
working-directory: integrations/pi
|
|
124
|
+
run: npm publish --dry-run --access public
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch: # manual TestPyPI dry-run
|
|
7
|
+
pull_request: # build-check only (no publish)
|
|
8
|
+
push:
|
|
9
|
+
branches: [develop, main]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0 # hatch-vcs needs tags for the version
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.x"
|
|
24
|
+
- name: Build sdist + wheel
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade build twine
|
|
27
|
+
python -m build
|
|
28
|
+
twine check dist/*
|
|
29
|
+
- uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
- name: Schema snapshot-sync gate (migrations ↔ current-schema.json)
|
|
34
|
+
run: |
|
|
35
|
+
python -m pip install --quiet '.[migrate]'
|
|
36
|
+
export XDG_DATA_HOME="$(mktemp -d)"
|
|
37
|
+
sandesh setup --project ci
|
|
38
|
+
sandesh migrate --all
|
|
39
|
+
sandesh migrate --dump-schema > "$XDG_DATA_HOME/dumped.json"
|
|
40
|
+
python -c "import json,sys; a=json.load(open('$XDG_DATA_HOME/dumped.json')); b=json.load(open('sandesh/schema/current-schema.json')); sys.exit(0 if a==b else 1)"
|
|
41
|
+
echo "✓ current-schema.json in sync with migrations"
|
|
42
|
+
|
|
43
|
+
create-release:
|
|
44
|
+
needs: build
|
|
45
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
with:
|
|
50
|
+
fetch-depth: 0 # need full tag history to detect the tag at HEAD
|
|
51
|
+
- name: Create GitHub Release for version tag at HEAD (idempotent)
|
|
52
|
+
env:
|
|
53
|
+
GH_TOKEN: ${{ secrets.RELEASE_PAT }} # PAT so 'release: published' re-fires (default GITHUB_TOKEN would not)
|
|
54
|
+
run: |
|
|
55
|
+
TAG=$(git tag --points-at HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
|
|
56
|
+
if [ -z "$TAG" ]; then
|
|
57
|
+
echo "::notice::no version tag at HEAD — ordinary main commit, nothing to release"
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
61
|
+
echo "release exists for $TAG — nothing to do"
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
gh release create "$TAG" --generate-notes
|
|
65
|
+
|
|
66
|
+
publish-pypi:
|
|
67
|
+
needs: build
|
|
68
|
+
if: github.event_name == 'release'
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
environment: pypi
|
|
71
|
+
permissions:
|
|
72
|
+
id-token: write # OIDC trusted publishing
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v4
|
|
75
|
+
with:
|
|
76
|
+
fetch-depth: 0 # guard needs origin/main history for the ancestry check
|
|
77
|
+
- name: Guard — tag ref + on-main ancestry
|
|
78
|
+
run: |
|
|
79
|
+
if ! [[ "$GITHUB_REF" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
80
|
+
echo "::error::refusing to publish: $GITHUB_REF is not a clean version tag (^refs/tags/v[0-9]+.[0-9]+.[0-9]+$)"
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
git fetch --no-tags origin main
|
|
84
|
+
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
|
|
85
|
+
echo "::error::refusing to publish: $GITHUB_SHA is not an ancestor of origin/main"
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
- uses: actions/download-artifact@v4
|
|
89
|
+
with:
|
|
90
|
+
name: dist
|
|
91
|
+
path: dist/
|
|
92
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
93
|
+
# no password / no token — trusted publishing; attestations default-on
|
|
94
|
+
|
|
95
|
+
publish-testpypi:
|
|
96
|
+
needs: build
|
|
97
|
+
if: github.event_name == 'workflow_dispatch'
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
environment: testpypi
|
|
100
|
+
permissions:
|
|
101
|
+
id-token: write
|
|
102
|
+
steps:
|
|
103
|
+
- uses: actions/checkout@v4
|
|
104
|
+
with:
|
|
105
|
+
fetch-depth: 0 # hatch-vcs needs tags to derive the version
|
|
106
|
+
- uses: actions/setup-python@v5
|
|
107
|
+
with:
|
|
108
|
+
python-version: "3.x"
|
|
109
|
+
- name: Version sanity — reject PEP 440 local segment ('+')
|
|
110
|
+
run: |
|
|
111
|
+
python -m pip install --upgrade build
|
|
112
|
+
VER=$(python -m build --sdist --no-isolation 2>/dev/null >/dev/null; ls dist/*.tar.gz 2>/dev/null | sed -E 's#.*/[a-z_-]+-(.+)\.tar\.gz#\1#' | head -1)
|
|
113
|
+
echo "derived version: $VER"
|
|
114
|
+
if echo "$VER" | grep -q '+'; then
|
|
115
|
+
echo "::error::version '$VER' contains a '+' local segment — TestPyPI rejects PEP 440 local versions"
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
- uses: actions/download-artifact@v4
|
|
119
|
+
with:
|
|
120
|
+
name: dist
|
|
121
|
+
path: dist/
|
|
122
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
123
|
+
with:
|
|
124
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
*.pyo
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
# runtime data lives under $XDG_DATA_HOME/sandesh/, never in the repo
|
|
6
|
+
*.db
|
|
7
|
+
# dev/test tooling — not part of the stdlib-only deliverable
|
|
8
|
+
.venv/
|
|
9
|
+
.env
|
|
10
|
+
test-reports/
|
|
11
|
+
.coverage
|
|
12
|
+
coverage.lcov
|
|
13
|
+
# Codacy CLI scaffolding (local static-analysis tooling, not part of the repo)
|
|
14
|
+
.codacy/
|
|
15
|
+
# Kiro IDE steering / lean-ctx agent tooling (local, not part of the deliverable)
|
|
16
|
+
.kiro/
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# CLAUDE.md — Sandesh Project Context
|
|
2
|
+
|
|
3
|
+
Read this before changing anything. It captures the architecture, the **locked
|
|
4
|
+
design decisions** (don't re-litigate them without reason), the gotchas, and the
|
|
5
|
+
Wave-2 roadmap. The code is small and stdlib-only — pair this doc with the four
|
|
6
|
+
`.py` files and you have the whole picture.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What Sandesh Is
|
|
11
|
+
|
|
12
|
+
**Sandesh** (संदेश — Sanskrit/Hindi for *message / dispatch*) is a tiny, **standalone,
|
|
13
|
+
multi-project messaging system for cooperating agent/orchestrator sessions**. It is a
|
|
14
|
+
SQLite-backed maildir + a blocking mailbox watcher. **Pure Python stdlib — no
|
|
15
|
+
third-party dependencies.**
|
|
16
|
+
|
|
17
|
+
### Origin & use case (why it exists)
|
|
18
|
+
It was extracted from a "Model-B" parallel-orchestration workflow: a **Mainline**
|
|
19
|
+
coordinator session plus worker **Track** sessions running in parallel. Those sessions
|
|
20
|
+
**cannot message each other directly**, and — critically — **re-invoking a *sleeping*
|
|
21
|
+
agent turn is only possible via the host's background-task mechanism** (e.g. Claude
|
|
22
|
+
Code's `run_in_background` tool, or Cron). A hook / MCP server / plain daemon *cannot*
|
|
23
|
+
push a turn into an idle session. Sandesh is the relay that works within that limit:
|
|
24
|
+
|
|
25
|
+
- a durable, queryable **mailbox** each session reads on demand, and
|
|
26
|
+
- a blocking **`notify` watcher** a session launches *in the background* so that when
|
|
27
|
+
mail addressed to it arrives, the watcher exits → the host wakes the agent → it
|
|
28
|
+
fetches.
|
|
29
|
+
|
|
30
|
+
Addresses **represent orchestrators** (`'Mainline - <Project>'`, `'Track <N> - <Project>'`),
|
|
31
|
+
but nothing is Claude-specific anymore — it's a general agent-messaging primitive.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Project Classification: Standalone Python CLI tool (+ planned MCP server)
|
|
36
|
+
|
|
37
|
+
- Pure stdlib (`sqlite3`, `argparse`, `os`, `signal`, `uuid`, `socket`). No venv needed.
|
|
38
|
+
- **Source of truth = this repo.** It is *installed* (copied) to the XDG data dir; edits
|
|
39
|
+
here require a re-`./install.sh` to take effect on the installed binary.
|
|
40
|
+
- Provenance note: an earlier, pre-standalone copy lived in the user's Claude dotfiles
|
|
41
|
+
at `~/.claude/scripts/sandesh/` (it imported a Claude-specific `schedule_db`). That
|
|
42
|
+
copy is **superseded** by this repo and should be removed from the dotfiles to avoid
|
|
43
|
+
divergence.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Status & Roadmap
|
|
48
|
+
|
|
49
|
+
- **Waves 1–8 — DONE** (through **v0.2.0**): the standalone CLI + `notify` watcher
|
|
50
|
+
(Wave 1), the MCP server `sandesh-mcp` (Wave 2), packaging/PyPI workflow, the Pi
|
|
51
|
+
extension (Wave 4), the schema-migration subsystem + installer auto-migrate (Wave 5,
|
|
52
|
+
CR-SAN-017/018), **the global store** (Wave 6, CR-SAN-022..025 — one global DB,
|
|
53
|
+
project tracker, cross-project grants, archive→tombstone lifecycle, install-assigned
|
|
54
|
+
super-admin, MCP 9→11 tools), **inbox search** (Wave 7, CR-SAN-026..028 — composable
|
|
55
|
+
filters incl. the `sender_project` proxy stream, FTS5 `search`/`reindex`, MCP
|
|
56
|
+
11→12 tools), pre-release housekeeping (CR-SAN-029/030), and the **Pi catch-up**
|
|
57
|
+
(Wave 8, CR-SAN-031/032 — wake `deliverAs: followUp` hardening, Pi 9→12-tool parity,
|
|
58
|
+
≥0.2.0 CLI session gate). Design contracts: `docs/research/PRD-global-store.md`,
|
|
59
|
+
`PRD-inbox-search.md`, `PRD-pi-extension.md` §8 (all AGREED).
|
|
60
|
+
- **Next:** registry publishes (PyPI trusted-publisher registration is a maintainer
|
|
61
|
+
action — RELEASING.md), and adoption by the originating orchestration workflow —
|
|
62
|
+
a separate, deliberate step (seed an addressbook, sessions run `notify`, migrate
|
|
63
|
+
off the old file-note relay).
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## File Layout
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
sandesh/ (this repo — source of truth)
|
|
71
|
+
├── sandesh/ the Python package (dist name: sandesh-relay; version from git tags via hatch-vcs)
|
|
72
|
+
│ ├── sandesh_db.py the library: schema + all operations (no CLI, no I/O loop)
|
|
73
|
+
│ ├── cli.py argparse CLI over the library (one binary, all subcommands)
|
|
74
|
+
│ ├── notify.py the blocking mailbox watcher (run() + a thin main)
|
|
75
|
+
│ ├── mcp_server.py the MCP adapter (12 tools; optional [mcp] extra)
|
|
76
|
+
│ ├── migrate.py the yoyo-backed migration engine (optional [migrate] extra)
|
|
77
|
+
│ ├── migrations/ 0001-baseline … 0005-message-fts (+ rollbacks)
|
|
78
|
+
│ ├── schema/current-schema.json committed snapshot (CI gate: == migrate --dump-schema)
|
|
79
|
+
│ └── data/usage-scenarios.md the sandesh://usage MCP resource content
|
|
80
|
+
├── integrations/pi/ the Pi extension (bun/TS; npm @anthill-tec/sandesh-pi; 12 tools + native wake)
|
|
81
|
+
├── install.sh builds a venv at $XDG_DATA_HOME/sandesh/.venv, pip-installs [mcp,migrate],
|
|
82
|
+
│ symlinks launchers, then migrate --all → consolidate → reindex → admin assign
|
|
83
|
+
├── tests/ 41 test files (run against a temp store; no install needed)
|
|
84
|
+
├── README.md / RELEASING.md / pyproject.toml
|
|
85
|
+
└── CLAUDE.md (this file)
|
|
86
|
+
|
|
87
|
+
Installed (by install.sh) + runtime data:
|
|
88
|
+
~/.local/share/sandesh/ ($XDG_DATA_HOME/sandesh)
|
|
89
|
+
├── .venv/ the installed package + entry points
|
|
90
|
+
├── sandesh.db the ONE global DB (WAL) — all projects; address, message,
|
|
91
|
+
│ message_recipient, notifier, project, admin (+ message_fts index)
|
|
92
|
+
└── projects/<project_id>/
|
|
93
|
+
├── messages/msg-<id>.md message bodies (full absolute paths stored in the DB)
|
|
94
|
+
└── sandesh.db.pre-global legacy per-project DB, kept as backup after consolidation
|
|
95
|
+
~/.local/bin/sandesh symlink → ~/.local/share/sandesh/.venv/bin/sandesh (PATH entry)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Architecture
|
|
101
|
+
|
|
102
|
+
### The store — XDG, one global DB, projectid-scoped
|
|
103
|
+
Every operation carries a **`project_id`**, but all projects share **ONE global
|
|
104
|
+
database** at `<data_home>/sandesh/sandesh.db` (WAL mode; `data_home` =
|
|
105
|
+
`$XDG_DATA_HOME` or `~/.local/share`). The `project` tracker table enrolls each
|
|
106
|
+
project; per-project body files live under
|
|
107
|
+
`<data_home>/sandesh/projects/<project_id>/messages/`. There is **no git/CWD
|
|
108
|
+
inference** — projectid is explicit (an MCP daemon has no CWD). The CLI accepts
|
|
109
|
+
`--project` (before *or* after the subcommand) or `$SANDESH_PROJECT`.
|
|
110
|
+
`sandesh_db.db_path()`/`connect()` open the global DB;
|
|
111
|
+
`sandesh_db.store_dir(project_id)` builds the body-folder path;
|
|
112
|
+
`sandesh_db.setup(project_id)` enrolls + provisions (idempotent; refuses a
|
|
113
|
+
tombstoned id).
|
|
114
|
+
|
|
115
|
+
### Five tables (`sandesh_db._SCHEMA`)
|
|
116
|
+
| table | holds |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `address` | the **addressbook** — durable identities, PK `address` (rejects dupes), `active` soft-delete, `project` (the address's `<Project>` part — the exact-match scoping key) |
|
|
119
|
+
| `message` | the **envelope** — `subject` (NOT NULL), `kind`, `in_reply_to`, `body_path` (NULL = subject-only; else a FULL absolute path) |
|
|
120
|
+
| `message_recipient` | **per-message addressees** — (`message_id`, `recipient`, `role` to/cc, `read_at`); PK `(message_id, recipient)` |
|
|
121
|
+
| `notifier` | **per-session watcher liveness** — PK `recipient`, `pid`, `token` (uuid/launch), `heartbeat_at`, `tombstone` |
|
|
122
|
+
| `project` | the **project tracker** — PK `project_id`, `state` (CHECK `active`\|`archived`\|`tombstoned`), `created_at`/`archived_at`/`tombstoned_at` |
|
|
123
|
+
|
|
124
|
+
### Modules
|
|
125
|
+
- **`sandesh_db.py`** — the entire model + operations. Stateless functions taking a
|
|
126
|
+
`con` (sqlite connection) and, where bodies are involved, a `store` dir. No printing,
|
|
127
|
+
no loops. This is what the CLI and (future) MCP server both call.
|
|
128
|
+
- **`cli.py`** — argparse front end. `_ctx(args)` → `(project, store, con)`. Address
|
|
129
|
+
defaults: `--from`/`--to` → `$SANDESH_ADDRESS` → `$WF_TRACK`.
|
|
130
|
+
- **`notify.py`** — `run(project_id, address, timeout)` is the blocking poll loop;
|
|
131
|
+
`main()` is the `sandesh notify` entry. Interval from `$SANDESH_POLL_SECONDS`
|
|
132
|
+
(default 10, floor 3).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Locked semantics (the design — change only with reason)
|
|
137
|
+
|
|
138
|
+
1. **To wakes / Cc silent.** Both `to` and `cc` get `message_recipient` rows and are
|
|
139
|
+
read by `fetch`; the difference is the **wake**: `notify` polls
|
|
140
|
+
`unread_to()` = `role='to' AND read_at IS NULL`. Cc is delivered + readable but
|
|
141
|
+
never wakes — it's swept up on the recipient's next `fetch`. Conserves agent turns.
|
|
142
|
+
2. **`all-tracks` broadcast — sender-project-scoped.** A reserved recipient keyword;
|
|
143
|
+
`send` expands it to all **active** addresses **in the sender's project**, minus the
|
|
144
|
+
sender. Per-recipient rows mean every recipient's watcher fires on its own row (the
|
|
145
|
+
sender gets none). *Re-opened by design (CR-SAN-022):* the original wording relied on
|
|
146
|
+
per-project stores for isolation; `docs/research/PRD-global-store.md` (AGREED
|
|
147
|
+
2026-06-11) replaced those with the single global DB, so the scoping is now explicit
|
|
148
|
+
via `address.project` — and **cross-project messaging requires the admin's
|
|
149
|
+
per-project grant** (CR-SAN-023): `sandesh grant --cross-project --project <id>
|
|
150
|
+
--by <admin>` — one-time, inherited by every participant of the granted project,
|
|
151
|
+
revoked project-wide (`revoke --cross-project`). Without it, `send` to a foreign
|
|
152
|
+
project fails with `cross-project sending not approved for project '<id>' — ask the
|
|
153
|
+
Sandesh admin`. The `all-tracks` broadcast stays sender-project-scoped regardless of
|
|
154
|
+
grant.
|
|
155
|
+
3. **Per-recipient read.** `read_at` lives on `message_recipient`, not `message` — a
|
|
156
|
+
broadcast/cc stays unread for the others after one reads it.
|
|
157
|
+
4. **Subject-only ⟷ file-body.** `subject` is mandatory (the minimal content). No
|
|
158
|
+
`--body`/`--body-file` ⇒ `body_path` NULL, **no file written**. With a body, it's an
|
|
159
|
+
md file under `messages/`, and the DB stores its **full absolute path**.
|
|
160
|
+
5. **Keep history; read=seen is the only signal.** Nothing is deleted. `read_at`
|
|
161
|
+
(per recipient, on `message_recipient`) is the sole "seen" signal — there is no
|
|
162
|
+
`message.status` disposition machine (no open/actioned/closed). A request stays
|
|
163
|
+
visible in history; whether it has been *acted on* is conveyed by replies, not a
|
|
164
|
+
status column. (CR-SAN-017 0002 dropped `message.status`; new≡migrated stores have
|
|
165
|
+
no status column.) **Lifecycle exception (CR-SAN-024):** `archive` still deletes
|
|
166
|
+
nothing (read-only, reversible), but `tombstone` is the deliberate, admin-only
|
|
167
|
+
exception — it purges the project's *internal* messages + recipient rows and
|
|
168
|
+
deletes its body folder (cross-project envelopes survive; their bodies are lost).
|
|
169
|
+
Standard reads then hide the tombstoned project's traffic: `inbox`/`fetch` filter
|
|
170
|
+
it out, and `thread` renders the exact warning `incomplete chain — message(s)
|
|
171
|
+
removed (project tombstoned)` where a chain passes through purged nodes.
|
|
172
|
+
6. **Reply threading.** `message.in_reply_to` links a reply to its parent; `reply`
|
|
173
|
+
defaults `to`=parent's sender and subject=`Re: …` (no `Re: Re:`). `thread` walks the
|
|
174
|
+
chain. `fetch` shows `↳ re #N "<parent subject>"`. (`reply` has no `--resolves`
|
|
175
|
+
flag — see #5.)
|
|
176
|
+
7. **Crash-safe liveness.** `notifier_live()` treats a row as dead if its `pid` is gone
|
|
177
|
+
OR `heartbeat_at` is older than `HEARTBEAT_STALE_SECS` (60). A clean exit removes the
|
|
178
|
+
row (token-guarded); a SIGKILL leaves a stale row the next `notifier_acquire()` reaps.
|
|
179
|
+
This is the self-heal that compensates for unreliable shutdown hooks.
|
|
180
|
+
8. **Cooperative tombstone eviction.** You can't cross-session-kill another's watcher.
|
|
181
|
+
`notifier_tombstone(recipient)` sets a flag the watcher sees on its next poll →
|
|
182
|
+
it self-terminates (exit 3). `unregister` of a *live* address tombstones first,
|
|
183
|
+
returns `('tombstoned', pid)`; the caller retries once it's offline, then soft-deletes.
|
|
184
|
+
**Terminology (two concepts, one word):** the `notifier.tombstone` column here is the
|
|
185
|
+
*per-watcher cooperative-shutdown flag*; the project lifecycle state `tombstoned`
|
|
186
|
+
(CR-SAN-024, see #5) is the *permanent retirement of a whole project*. They are
|
|
187
|
+
unrelated mechanisms — `archive`/`tombstone` merely *use* the per-watcher flag to
|
|
188
|
+
evict live watchers before changing the project state.
|
|
189
|
+
9. **Removal authorization.** `Mainline` may unregister anyone; any address may
|
|
190
|
+
unregister itself. (Honor-system; all-local cooperative orchestrators.)
|
|
191
|
+
10. **Validated address format** — `'<Orchestrator> - <Project>'`, regex
|
|
192
|
+
`^(Mainline|Track \d+) - [A-Za-z][A-Za-z0-9_]*$`, and the `<Project>` part must equal
|
|
193
|
+
the `project_id`. Caught at `register` and `send`. (Extend the orchestrator
|
|
194
|
+
alternation in `ADDRESS_RE` if new roles appear.)
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## The wake mechanism (the most important constraint)
|
|
199
|
+
|
|
200
|
+
Re-invoking a **sleeping** agent turn is **exclusive to the host's background-task
|
|
201
|
+
mechanism** (Claude Code's `run_in_background` tool; or Cron/scheduled wakeups). Verified
|
|
202
|
+
against the Claude Code hooks docs:
|
|
203
|
+
- `SessionStart`/`SessionEnd` hooks exist, but a hook-spawned process **cannot wake the
|
|
204
|
+
agent**, and `SessionEnd` is unreliable on crash/SIGKILL.
|
|
205
|
+
- An MCP server **cannot** push a turn into an idle session either.
|
|
206
|
+
|
|
207
|
+
**Therefore:** the `notify` watcher must be launched by the agent via its background-task
|
|
208
|
+
tool (which is what re-invokes it on exit). MCP (Wave 2) replaces the *verbs* (send/
|
|
209
|
+
fetch/…), **not** the wake. Keep `notify` as a standalone process. This is *why* the
|
|
210
|
+
liveness table is crash-safe rather than relying on a shutdown hook.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## How to run
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# tests (no install needed — run against a temp store; per-file, discovery is broken)
|
|
218
|
+
PYTHONPATH=. .venv/bin/python tests/<test_file>.py # dev venv has [mcp,migrate]
|
|
219
|
+
|
|
220
|
+
# install / re-install (venv at ~/.local/share/sandesh/.venv + migrate/consolidate/reindex)
|
|
221
|
+
SANDESH_ADMIN=<name> ./install.sh
|
|
222
|
+
|
|
223
|
+
# use (installed launcher; ~/.local/bin must be on PATH, else call by full path)
|
|
224
|
+
sandesh setup --project Demo
|
|
225
|
+
sandesh --project Demo register --address "Mainline - Demo" --kind mainline
|
|
226
|
+
sandesh --project Demo send --from "Track 1 - Demo" --to "Mainline - Demo" --subject "ping"
|
|
227
|
+
sandesh --project Demo notify --to "Mainline - Demo" # blocks; run in background
|
|
228
|
+
sandesh --project Demo fetch --to "Mainline - Demo"
|
|
229
|
+
|
|
230
|
+
# project lifecycle (CR-SAN-024) — two-tier authz, all three accept --dry-run
|
|
231
|
+
sandesh archive --project Demo --by "Mainline - Demo" # read-only, reversible (own Mainline)
|
|
232
|
+
sandesh unarchive --project Demo --by "Mainline - Demo" # back to active (own Mainline)
|
|
233
|
+
sandesh tombstone --project Demo --by <admin> --yes # PERMANENT; archived-only;
|
|
234
|
+
# ONLY the install-assigned super-admin;
|
|
235
|
+
# interactive confirm unless --yes
|
|
236
|
+
sandesh tombstone --project Demo --by <admin> --dry-run # purge counts, writes nothing
|
|
237
|
+
```
|
|
238
|
+
Env: `$SANDESH_PROJECT`, `$SANDESH_ADDRESS` (caller's own address),
|
|
239
|
+
`$SANDESH_POLL_SECONDS`. `notify` exit codes: `0` mail / `2` timeout / `3` tombstoned /
|
|
240
|
+
`4` evicted / `5` dedup / `1` error.
|
|
241
|
+
|
|
242
|
+
### Model-B usage pattern (how an orchestrator uses it)
|
|
243
|
+
Each session, at start: ensure its address is registered, then launch
|
|
244
|
+
`sandesh --project <P> notify --to "<self>"` **via the host's run_in_background tool**.
|
|
245
|
+
On wake (exit 0) → `sandesh fetch --to "<self>"` → act → relaunch `notify`. Send with
|
|
246
|
+
`send`/`reply`. On exit-3 (tombstoned) do **not** relaunch.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Gotchas (learned while building — don't rediscover them)
|
|
251
|
+
|
|
252
|
+
- **argparse `--project` in both positions:** the shared `--project` (on the parent and
|
|
253
|
+
every subparser via `parents=[common]`) uses `default=argparse.SUPPRESS` so an absent
|
|
254
|
+
one doesn't clobber the value given in the other position. Removing SUPPRESS breaks
|
|
255
|
+
`sandesh --project X <cmd>`.
|
|
256
|
+
- **SQLite has no real BOOLEAN.** `active`/`tombstone` are declared `BOOLEAN` (readable)
|
|
257
|
+
but stored as integers `0`/`1`; `sqlite3` returns ints, so compare truthily
|
|
258
|
+
(`if row["tombstone"]:`), never `is True`. (`TRUE`/`FALSE` literals work since 3.23.)
|
|
259
|
+
- **Body paths are absolute.** `send` stores the full path; `fetch` opens it directly
|
|
260
|
+
(with a legacy relative-path fallback under `store`).
|
|
261
|
+
- **`store` vs `con`.** Functions that touch body files take a `store` dir; pure-DB
|
|
262
|
+
functions take only `con`. Keep that split.
|
|
263
|
+
- **`sqlite_sequence`** appears once any AUTOINCREMENT table exists — it's sqlite-internal,
|
|
264
|
+
harmless.
|
|
265
|
+
- **The launcher** resolves its own real path via `readlink -f`, so it works through the
|
|
266
|
+
`~/.local/bin/sandesh` symlink. Don't hardcode the install path.
|
|
267
|
+
- **Schema changes ship via the migration subsystem.** Don't hand-evolve a store's schema —
|
|
268
|
+
add a migration. The `sandesh migrate` CLI command (gated behind the optional **`[migrate]`**
|
|
269
|
+
extra — `yoyo` + `jsonschema`) applies pending migrations; `--status`/`--rollback`/`--check`
|
|
270
|
+
inspect and reverse them. On update the installer **auto-migrates** the global DB
|
|
271
|
+
(`install.sh` runs `sandesh migrate --all`), and the committed
|
|
272
|
+
`sandesh/schema/current-schema.json` snapshot must stay in sync with `migrations/` — the CI
|
|
273
|
+
gate in `publish-pypi.yml` asserts `migrate --dump-schema` equals that snapshot.
|
|
274
|
+
- **`migrate` no longer accepts `--project`** (CR-SAN-022): the global DB is the single
|
|
275
|
+
migration target, so the subcommand has no per-project routing — `sandesh migrate
|
|
276
|
+
--project X` is a CLI error.
|
|
277
|
+
- **`setup` of a tombstoned project refuses** (PRD O1): the tracker row is terminal; the
|
|
278
|
+
raised error message contains `retired (tombstoned)` and the row is left unchanged.
|
|
279
|
+
- **Legacy per-project stores are auto-consolidated by the installer.** `install.sh` runs
|
|
280
|
+
`sandesh consolidate` (stdlib-only, idempotent) after the migrate block: it imports each
|
|
281
|
+
`projects/<id>/sandesh.db` into the global DB (ids remapped, reply chains relinked,
|
|
282
|
+
body files unmoved), enrolls the project, and keeps the legacy file as
|
|
283
|
+
`sandesh.db.pre-global`.
|
|
284
|
+
- **The super-admin is NOT an address.** A single-row `admin` table (`CHECK (id = 1)`)
|
|
285
|
+
holds the Sandesh admin's name — never messageable, registrable, or listable. It is
|
|
286
|
+
assigned ONLY at install time via `$SANDESH_ADMIN` (an inline venv-python call in
|
|
287
|
+
`install.sh`); there is deliberately NO CLI or MCP surface to create/change it, and a
|
|
288
|
+
different-name re-assign is refused (`refusing to silently re-assign`).
|
|
289
|
+
- **`register` requires enrollment.** Registering into a project with no tracker row
|
|
290
|
+
fails with `unknown project '<id>'` — run `setup` first (it enrolls the project).
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## The MCP server (shipped — Wave 2 + 6 + 7)
|
|
295
|
+
|
|
296
|
+
`sandesh/mcp_server.py` (FastMCP, stdio; the optional **`[mcp]`** extra) exposes **12 tools**:
|
|
297
|
+
setup, register, unregister, addressbook, send, reply, inbox, fetch, thread (Wave 2),
|
|
298
|
+
archive, unarchive (Wave 6 — tombstone/grant/revoke/admin are NEVER exposed), and search
|
|
299
|
+
(Wave 7). Inbox/fetch carry the six filter params; `project_id` is optional everywhere it
|
|
300
|
+
can be derived (and accepted-but-unused on the recipient-keyed tools). Errors map
|
|
301
|
+
`ValueError`/`PermissionError` → `ToolError`. **The wake is NOT in MCP** — `notify` stays a
|
|
302
|
+
background process (the agent's host re-invokes it; see the wake section above). The Pi
|
|
303
|
+
extension (`integrations/pi/`) mirrors the same 12-tool surface over the CLI, with a native
|
|
304
|
+
wake loop (`sendUserMessage(…, {deliverAs:"followUp"})`) and a ≥0.2.0 CLI session gate.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Conventions
|
|
309
|
+
|
|
310
|
+
- **Git:** the user works git-flow style (feature branches off `develop`).
|
|
311
|
+
**Commit/push only when asked; branch before committing on the default branch.**
|
|
312
|
+
- **Commit messages: NEVER add Claude attribution** ("Generated with Claude",
|
|
313
|
+
"Co-Authored-By: Claude"). Clean, technical messages only.
|
|
314
|
+
- **New dependency?** The core's whole virtue is stdlib-only — runtime deps live ONLY
|
|
315
|
+
behind the optional extras (`[mcp]` = the MCP SDK; `[migrate]` = yoyo + jsonschema);
|
|
316
|
+
adding one is a deliberate decision; read the real upstream API first.
|
|
317
|
+
- Keep `sandesh_db.py` pure (model + ops, no printing/looping); presentation in `cli.py`,
|
|
318
|
+
the loop in `notify.py`, the MCP protocol in `mcp_server.py`.
|
|
319
|
+
```
|