Migrates TODO.md into pql and removes it. Six tickets: the double- ingestion bug with its full investigation preserved, and an epic covering the four stub endpoints in src/main.py. Markdown TODO lists cannot express blocking, parentage or status, and nothing notices when they go stale. Tickets travel with the repo — .pql/changelog/ is committed and replayed by the git hooks, while the databases are ignored and rebuildable with `pql plan rebuild`. Replaces the feature-branch mandate with the workspace convention: linear history, no merge commits, work on main or a short-lived branch that is fast-forwarded away. tatlock remains the one repo that requires branches. Adds a committed .claude/settings.json. `pql init` writes one containing only allow rules, which is the wrong shape — an allowlist with no floor under it. Every git deny appears in both `git <verb>` and `git * <verb>` form; the second catches `git -C <path>`, and without it the git denies would be decorative. .gitignore gains two entries. `.claude/settings.local.json` was only protected by a global gitignore on this machine, so the protection did not travel with the repo. The .pql rules ignore everything except the changelog, deliberately, since that file is what makes tickets portable. Co-Authored-By: Claude <noreply@anthropic.com>
44 lines
6.1 KiB
SQL
44 lines
6.1 KiB
SQL
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QDAA8EWH7D2KKQF3TBHXR', 'description', NULL, 'Dockerfile runs `uvicorn ... --workers 2`, and `startup_event` in src/main.py creates a
|
|
WikiChangeListener per worker. Each opens its own PostgreSQL LISTEN connection on
|
|
`wiki_page_changes`, and Postgres delivers NOTIFY to *every* listening session — so both
|
|
workers process the same event.
|
|
|
|
Proven against production v1.9.1 on 2026-08-08:
|
|
- pg_stat_activity shows two library_desk_listener sessions, both LISTEN "wiki_page_changes"
|
|
- "Wiki.js change listener started successfully" is logged twice at startup
|
|
- a single pg_notify(''wiki_page_changes'', ''claude-probe-no-colons'') produced exactly two
|
|
"Invalid notification payload" lines, one per worker (that payload is rejected before any
|
|
ingestion runs, so it is a side-effect-free probe)
|
|
|
|
The debounce in wiki_change_listener.py (self._recent_notifications) is an in-process dict
|
|
and cannot dedupe across workers. No Redis lock or other cross-process guard exists in the
|
|
process_wiki_page_change path in src/routers/webhooks.py.
|
|
|
|
IMPACT: each edit runs ingestion twice — duplicate embedding generation against Ollama plus
|
|
duplicate Qdrant and Neo4j writes. On tower-of-joy, Ollama shares the GPU with
|
|
Speaches/Whisper, so the wasted work has a cost beyond CPU.
|
|
|
|
NOT YET ESTABLISHED: whether this is merely wasteful or actually corrupting. Check whether
|
|
repeated ingestion of the same page creates duplicate Neo4j entities/relationships or
|
|
duplicate Qdrant points — that changes the severity.
|
|
|
|
OPTIONS TO WEIGH:
|
|
- run the listener in one place only (single-worker sidecar, or elect an owner) so the
|
|
subscription is singular by construction
|
|
- keep per-worker listeners and add a short-TTL Redis claim lock keyed on page_id, so only
|
|
the first worker to claim a notification processes it. Redis is already a dependency —
|
|
the job manager uses it.
|
|
|
|
Whatever approach is taken must preserve the reconnect supervision added in v1.9.1 and its
|
|
tests in tests/test_wiki_change_listener.py.
|
|
|
|
Migrated from TODO.md 2026-08-08.', NULL, '2026-08-08 21:53:25', '2026-08-08 21:53:25.051', '2026-08-08 21:53:25.051', NULL, '14eeab6aa32e7b47edf3bdf1fdb572a8', 2) ON CONFLICT(hash) DO NOTHING;
|
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QF94YZ1RRP8MYEZCZKJRM', 'parent_id', NULL, 'T-2', NULL, '2026-08-08 21:53:37', '2026-08-08 21:53:37.143', '2026-08-08 21:53:37.143', NULL, 'd793037ced050d17996991b71199c4e6', 2) ON CONFLICT(hash) DO NOTHING;
|
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QF9KJ96C95T493C8WHQM0', 'parent_id', NULL, 'T-2', NULL, '2026-08-08 21:53:37', '2026-08-08 21:53:37.178', '2026-08-08 21:53:37.178', NULL, 'a9feaf6cf70f87d9f3e12e73311120d8', 2) ON CONFLICT(hash) DO NOTHING;
|
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QFAHYRXZBK58NY73K3YSM', 'parent_id', NULL, 'T-2', NULL, '2026-08-08 21:53:37', '2026-08-08 21:53:37.179', '2026-08-08 21:53:37.179', NULL, 'dfbe9ea63d608c433b8af3291c6ade71', 2) ON CONFLICT(hash) DO NOTHING;
|
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QFA3CB42112FNPTADNCCG', 'parent_id', NULL, 'T-2', NULL, '2026-08-08 21:53:37', '2026-08-08 21:53:37.179', '2026-08-08 21:53:37.179', NULL, 'ecd603354edd2fc778a38e75782a10a1', 2) ON CONFLICT(hash) DO NOTHING;
|
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QF94YZ1RRP8MYEZCZKJRM', 'description', NULL, 'Check which documents need updating based on content hashes. Used by Scheduler to determine what changed since last sync. Needs: query existing documents by path, compare content hashes, return list of updates needed. Migrated from TODO.md 2026-08-08.', NULL, '2026-08-08 21:53:37', '2026-08-08 21:53:37.284', '2026-08-08 21:53:37.284', NULL, 'f0dc060e8f3931fa30026d5476d6b906', 2) ON CONFLICT(hash) DO NOTHING;
|
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QF9KJ96C95T493C8WHQM0', 'description', NULL, 'Get processing status for a document. Needs a status tracking system (Redis or database) and per-document ingestion progress. Migrated from TODO.md 2026-08-08.', NULL, '2026-08-08 21:53:37', '2026-08-08 21:53:37.421', '2026-08-08 21:53:37.421', NULL, '9ac8b8db277ef5f96fec157e6e1db2e7', 2) ON CONFLICT(hash) DO NOTHING;
|
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QFA3CB42112FNPTADNCCG', 'description', NULL, 'Get indexing status for an entire repository. Needs repository-level statistics and tracking of which documents from a repo are indexed. Migrated from TODO.md 2026-08-08.', NULL, '2026-08-08 21:53:37', '2026-08-08 21:53:37.573', '2026-08-08 21:53:37.573', NULL, 'cf4fca0273bf1de3fa9d2c63ff97323a', 2) ON CONFLICT(hash) DO NOTHING;
|
|
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FY6QFAHYRXZBK58NY73K3YSM', 'description', NULL, 'Check for duplicate or highly similar documents using vector similarity and graph analysis. Needs: get document embedding from Qdrant, find similar vectors above threshold, check graph relationships, return candidates with similarity scores. Migrated from TODO.md 2026-08-08.', NULL, '2026-08-08 21:53:37', '2026-08-08 21:53:37.698', '2026-08-08 21:53:37.698', NULL, 'a96ed5423c39f04a515770ad6d60fb10', 2) ON CONFLICT(hash) DO NOTHING;
|