diff --git a/.changeset/bounded-daemon-maintenance-shutdown.md b/.changeset/bounded-daemon-maintenance-shutdown.md deleted file mode 100644 index 5915262bd3..0000000000 --- a/.changeset/bounded-daemon-maintenance-shutdown.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tracedecay": patch ---- - -Keep daemon shutdown bounded while background maintenance is active, use lightweight read-only presence probes during exact-root store selection and identity-conflict reporting, and avoid repeating successful full integrity scans during crash recovery. diff --git a/.changeset/consolidation-maintenance-telemetry.md b/.changeset/consolidation-maintenance-telemetry.md deleted file mode 100644 index a24b4e304e..0000000000 --- a/.changeset/consolidation-maintenance-telemetry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tracedecay": patch ---- - -Authorize migration-owned staged session databases under the exclusive maintenance scope, and omit only volatile hook analytics from confirmation fingerprints while preserving its bytes in backups and consolidated artifacts. diff --git a/.changeset/daemon-status-headline.md b/.changeset/daemon-status-headline.md deleted file mode 100644 index ba3b2ffa43..0000000000 --- a/.changeset/daemon-status-headline.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tracedecay": patch ---- - -`tracedecay daemon status` now leads with the daemon's own state from its socket probe (`state: running` when it answers initialize) and reports the service manager on a separate `service manager:` line, so a shell that cannot reach the systemd user manager no longer reads a serving daemon as unavailable. diff --git a/.changeset/exact-root-inventory-fast-path.md b/.changeset/exact-root-inventory-fast-path.md deleted file mode 100644 index 27daaa62ea..0000000000 --- a/.changeset/exact-root-inventory-fast-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tracedecay": patch ---- - -Avoid redundant same-project selector opens and full session-table row counts while resolving a healthy exact-root project store, including branch-scoped graphs and preserved duplicate manifests, keeping graph calls responsive when session history lives on slower storage. diff --git a/.changeset/prove-daemon-and-store-observation.md b/.changeset/prove-daemon-and-store-observation.md deleted file mode 100644 index 5a33569b86..0000000000 --- a/.changeset/prove-daemon-and-store-observation.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tracedecay": patch ---- - -Doctor and daemon status now require an initialize answer before calling a daemon live, and a retained code-index or store admission is no longer reported as observed health. diff --git a/.changeset/session-correlation-presence-fast-path.md b/.changeset/session-correlation-presence-fast-path.md deleted file mode 100644 index 7123a56e53..0000000000 --- a/.changeset/session-correlation-presence-fast-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tracedecay": patch ---- - -Keep `tracedecay_sessions_for` responsive on large session stores by using bounded row-presence probes for empty-index reporting instead of full correlation-table counts. diff --git a/.changeset/v2-foundation-crates.md b/.changeset/v2-foundation-crates.md deleted file mode 100644 index 789a1835ec..0000000000 --- a/.changeset/v2-foundation-crates.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tracedecay": patch ---- - -Land the stacked V2 foundation: ten workspace crates (api, application, host-integration, hooks, policy, private-fs, rusqlite-runtime, store, temporal-query, tool-catalog) and the V2 domain contracts, with protobuf node kinds now unconditional graph vocabulary. Hook spool roots are refused unless private to the current owner (and gain a private ACL on Windows), and Windows lock contention in the hook admission ledger and storage sidecar locks is typed as busy/contended instead of surfacing as I/O errors. diff --git a/.github/workflows/hawk.yml b/.github/workflows/hawk.yml index 1277b29c11..f9701b0246 100644 --- a/.github/workflows/hawk.yml +++ b/.github/workflows/hawk.yml @@ -1,12 +1,9 @@ # Hawk visibility lint. Explicit optional channel, not PR tip evidence. # -# Retrigger note: keep this workflow off pull_request CI until Hawk is clean. -# # Kept off pull_request CI on purpose: tip green must not depend on -# continue-on-error or a forged dashboard app-dist seed. Dispatch or wait for -# the weekly schedule until the Hawk backlog is clean enough to promote into -# required CI without softening -D warnings. -# Unarchive retrigger: keep Hawk optional until -D warnings is clean. +# continue-on-error or a forged dashboard app-dist seed. Dispatch it on demand +# until the Hawk backlog is clean enough to promote into required CI without +# softening -D warnings. name: Hawk # On demand only; see distribution-acceptance.yml for why nothing runs on a diff --git a/crates/tracedecay-host-admission/src/broker_group_commit_test.rs b/crates/tracedecay-host-admission/src/broker_group_commit_test.rs index e15d95f87b..ca25b2aab3 100644 --- a/crates/tracedecay-host-admission/src/broker_group_commit_test.rs +++ b/crates/tracedecay-host-admission/src/broker_group_commit_test.rs @@ -12,6 +12,8 @@ fn open_broker(dir: &std::path::Path) -> Arc { Arc::new(HostAdmissionBroker::new(runtime)) } +// The held runtime guard is the in-flight batch the admissions queue behind. +#[allow(clippy::await_holding_lock)] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn admissions_queued_behind_a_batch_share_the_next_durable_batch() { let spool = tempfile::tempdir().unwrap(); @@ -46,9 +48,9 @@ async fn admissions_queued_behind_a_batch_share_the_next_durable_batch() { } seqs.sort_unstable(); assert_eq!(seqs, (2..=9).collect::>()); - // One intent publish (file and directory) and one frame sync for all - // eight; one-at-a-time appends paid three metadata publishes each. - assert_eq!(barriers.syncs(), 3); + // One intent publish (file, plus directory on Unix) and one frame sync + // for all eight; one-at-a-time appends paid three metadata publishes each. + assert_eq!(barriers.syncs(), 2 + u64::from(cfg!(unix))); } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] @@ -103,15 +105,20 @@ async fn a_commit_waits_on_no_barrier_and_the_next_batch_publishes_its_watermark let barriers = sync_latency::inject(spool.path(), Duration::ZERO); assert_eq!(commit_leased(&broker).await, first.seq); assert_eq!(barriers.syncs(), 0); + drop(barriers); // The daemon dies before any later publish: the commit replays once more. drop(broker); + // Reopening publishes once to clear the first batch's reconciled append + // intent; count only the barriers after it. let broker = open_broker(spool.path()); assert_eq!(broker.pending_replay_count().await.unwrap(), 1); + let barriers = sync_latency::inject(spool.path(), Duration::ZERO); assert_eq!(commit_leased(&broker).await, first.seq); + assert_eq!(barriers.syncs(), 0); let second = broker.admit("source", b"second").await.unwrap(); // The second batch's intent publish and frame sync carry the watermark. - assert_eq!(barriers.syncs(), 3); + assert_eq!(barriers.syncs(), 2 + u64::from(cfg!(unix))); drop(broker); let (mut runtime, report) = diff --git a/crates/tracedecay-host-admission/src/lib.rs b/crates/tracedecay-host-admission/src/lib.rs index 6ddae48cc2..d0a2e9bbb2 100644 --- a/crates/tracedecay-host-admission/src/lib.rs +++ b/crates/tracedecay-host-admission/src/lib.rs @@ -1472,6 +1472,9 @@ fn classify_error(error: &ObservationApplicationError) -> HostAdmissionOutcome { } } +#[cfg(test)] +#[path = "broker_group_commit_test.rs"] +mod broker_group_commit_test; #[cfg(test)] #[path = "host_admission_batch_test.rs"] mod host_admission_batch_test; diff --git a/dogfood-journey.md b/dogfood-journey.md deleted file mode 100644 index 1f87a8a934..0000000000 --- a/dogfood-journey.md +++ /dev/null @@ -1,306 +0,0 @@ -# TraceDecay #1281 real-project dogfood journey - -Date: 2026-09-15 - -## Scope - -This journey ran the installed `tracedecay` CLI from commit -`2314bd9a349479e965bc43030713ca2580b8b6d0` against two real TraceDecay -checkouts: - -- `/fast/tmp/td-1281-dogfood-sol`, the linked worktree for this branch. -- `/fast/projects/tracedecay`, the canonical checkout used for the successful - lexical read. - -The branch is based on tip `46698151afab`. No fixture project or operator -TraceDecay data was used. - -## Isolation - -Every CLI command used these values: - -```bash -STATE=/fast/tmp/td-1281-dogfood-state-2314bd9 -BIN="$STATE/install/bin/tracedecay" -export HOME="$STATE/home" -export USERPROFILE="$STATE/home" -export XDG_CONFIG_HOME="$STATE/config" -export XDG_RUNTIME_DIR="$STATE/runtime" -export TRACEDECAY_HOME="$STATE/home/.tracedecay" -export TRACEDECAY_DATA_DIR="$STATE/profile" -export TRACEDECAY_PROFILE_DIR="$STATE/profile" -export TRACEDECAY_GLOBAL_DB="$STATE/profile/global.db" -export TRACEDECAY_DAEMON_SOCKET="$STATE/runtime/daemon.sock" -``` - -The isolated daemon used -`/fast/tmp/td-1281-dogfood-state-2314bd9/runtime/daemon.sock`. Its first start -refused mode `0755` on the runtime directory. After the directory changed to -mode `0700`, the daemon reported `daemon_ready`. This guard prevented fallback -to the operator daemon. - -The installed artifact identified itself as: - -```text -tracedecay 0.1.0-beta.37+2314bd9a349479e965bc43030713ca2580b8b6d0 -``` - -The artifact was built and installed with these recorded commands: - -- `cc-21907` ran `cargo build --release -p tracedecay-cli --bin tracedecay`. -- `cc-21908` ran `cargo install --path crates/tracedecay-cli --root /fast/tmp/td-1281-dogfood-state-2314bd9/install --locked --force` after `cc-21907`. - -The earlier `cc-21881` build completed before the branch rebase. The replacement -build and install attempts `cc-21900` and `cc-21902` were interrupted, so this -journey did not use their artifacts. - -## Typed state results - -| Required state | Result | Evidence | -| --- | --- | --- | -| Missing registry | Blocked | Both shipped CLI routes reject before `tracedecay_project_list` can return its documented `status: "unavailable"` payload. | -| Stale index | Hit | Search returned `freshness.state: "possibly_stale"` with `staleness_state: "indexing"`. | -| Unavailable authority | Hit | Status returned `storage_health.generation_census.state: "unavailable"` and `reason: "authority_unavailable"`. | -| Successful real-project path | Hit | `grep` scanned 81 files and returned five matches from `/fast/projects/tracedecay`. | - -### Missing registry cannot reach the tool handler - -Before enrollment, the exact tool call was: - -```bash -"$BIN" tool project_list \ - --project /fast/tmp/td-1281-dogfood-sol \ - --args '{"format":"json","limit":5}' \ - --json -``` - -It exited with code 1 before returning a JSON tool response: - -```text -Error: config error: daemon tool call failed: config error: no TraceDecay index found at '/fast/tmp/td-1281-dogfood-sol': project is not enrolled in the authenticated profile; run 'tracedecay init' first -``` - -The projectless form also exited with code 1: - -```bash -cd /fast/tmp -"$BIN" tool project_list \ - --args '{"format":"json","limit":5}' \ - --json -``` - -```text -Error: config error: daemon tool call failed: tracedecay_project_list requires an initialized code project -``` - -The catalog describes `project_list` as a profile registry read, and its handler -has a typed missing-registry result. The shipped binding still requires an -active initialized project. That routing requirement makes the typed result -unreachable through the CLI or MCP before enrollment. - -### Enrollment and stale index - -The linked worktree enrolled through the shipped command: - -```bash -"$BIN" init /fast/tmp/td-1281-dogfood-sol --fresh -``` - -```text -initialized /fast/tmp/td-1281-dogfood-sol; daemon code-index reconciliation requested -``` - -The first search used the whole MCP argument object: - -```bash -"$BIN" tool search \ - --project /fast/tmp/td-1281-dogfood-sol \ - --args '{"query":"CodeIndexWorktreeFreshnessV1","limit":5,"format":"json"}' \ - --json -``` - -Recorded response shape: - -```json -{ - "status": "unavailable", - "reason": "linked_worktree_disabled", - "code_generation": null, - "freshness": { - "state": "possibly_stale", - "indexing": { - "staleness_state": "indexing", - "rebuild_in_flight": true, - "reason": "linked_worktree_disabled" - } - }, - "coverage": { - "exact": {"status": "unavailable", "reason": "linked_worktree_disabled"}, - "lexical": {"status": "unavailable", "reason": "linked_worktree_disabled"}, - "graph": {"status": "unavailable", "reason": "linked_worktree_disabled"} - } -} -``` - -This response hit the required stale-index state on the requested real -worktree. The linked-worktree policy prevented a successful lexical or graph -read there, so the successful read used the canonical real checkout. - -### Unavailable authority - -The cold status call was: - -```bash -"$BIN" tool status \ - --project /fast/tmp/td-1281-dogfood-sol \ - --args '{"include_branch_diagnostics":false,"format":"json"}' \ - --json -``` - -Recorded response shape: - -```json -{ - "code_index_freshness": { - "status": "warming", - "worktree": { - "staleness_state": "indexing", - "coverage": "partial_refresh_in_progress", - "rebuild_in_flight": true - } - }, - "graph_statistics": { - "state": "unavailable", - "reason": "exact_scope_generation_not_ready" - }, - "storage_health": { - "generation_census": { - "state": "unavailable", - "reason": "authority_unavailable" - } - } -} -``` - -The command exited with code 0 and preserved the unavailable authority as a -typed state instead of returning zero graph counts. - -### Successful lexical path on the canonical checkout - -The canonical checkout enrolled into the same isolated profile: - -```bash -"$BIN" init /fast/projects/tracedecay -``` - -```text -initialized /fast/projects/tracedecay; daemon code-index reconciliation requested -``` - -The successful lexical command was: - -```bash -"$BIN" tool grep \ - --project /fast/projects/tracedecay \ - --args '{"pattern":"CodeIndexWorktreeFreshnessV1","fixed_strings":true,"case_sensitive":true,"path_glob":"crates/**/*.rs","max_results":5,"format":"json"}' \ - --json -``` - -Recorded response shape: - -```json -{ - "coverage": { - "completeness": "partial", - "requested_domains": ["source"], - "returned": 5, - "visited": 30871 - }, - "files_scanned": 81, - "match_count": 5, - "graph_enrichment": { - "status": "unavailable", - "reason_code": "code-graph-unavailable", - "retryable": true - }, - "results": [ - { - "file": "crates/tracedecay-contracts/src/code_index_freshness.rs", - "line": 180, - "text": "pub struct CodeIndexWorktreeFreshnessV1 {" - } - ], - "truncated": true -} -``` - -The process exited with code 0. The source search returned real checkout data -while it kept unavailable graph enrichment typed. - -## Real-project graph activation blocker - -The canonical index sealed a generation for 5,090 files. Status reported the -build phase as `ready`, but `code_graph_serving` remained `pending`, -`staleness_state` remained `indexing`, and `rebuild_in_flight` remained true. -The daemon logged: - -```text -verified graph head did not match the partitioned manifest; replay the exact sealed generation to repair its quarantined graph projection -``` - -A later search waited for the full two-minute tool deadline and exited with: - -```text -reason_code=tool_dispatch_deadline_exceeded retryable=true: tool 'tracedecay_search' exceeded its absolute deadline before commit; worker settlement is Settling -``` - -The final status shape was: - -```json -{ - "code_index_freshness": { - "status": "warming", - "worktree": { - "code_graph_serving": {"state": "pending"}, - "staleness_state": "indexing", - "rebuild_in_flight": true, - "progress": { - "phase": "ready", - "completed_files": 5090, - "total_files": 5090, - "committed_chunks": 393200, - "committed_imports": 83757, - "committed_payload_bytes": 681754235 - } - } - }, - "graph_statistics": { - "state": "unavailable", - "reason": "exact_scope_generation_not_ready" - }, - "retrieval_serving": { - "status": "serving", - "condition": "rebuilding", - "freshness": "last_complete_stale" - } -} -``` - -The lexical journey succeeded, but the graph journey did not converge. - -## Additional CLI contract mismatch - -`tracedecay init --help` advertises `--yes` for moved-store adoption. This -command: - -```bash -"$BIN" init /fast/tmp/td-1281-dogfood-sol --fresh --yes -``` - -was rejected before initialization: - -```text -Error: config error: --component, --dry-run, --yes, and --adopt are only valid with install, update-plugin, reinstall, or uninstall -``` - -Removing `--yes` allowed the documented `--fresh` path to complete. diff --git a/scripts/mcp-conformance-smoke.sh b/scripts/mcp-conformance-smoke.sh index 8248a08a3b..3049df0d68 100755 --- a/scripts/mcp-conformance-smoke.sh +++ b/scripts/mcp-conformance-smoke.sh @@ -35,7 +35,7 @@ INIT_STDERR="" run_smoke() { local work_dir="$1" local fixture="$2" - local tools_a tools_b call_out res_out + local tools_a tools_b call_out res_out unknown_out local diagnostics_out affected_out test_map_out test_map_err symbol_json impact_out impact_err node_id local failures=0 @@ -263,11 +263,14 @@ NODE fail "resources/list exposes tracedecay://status" fi - # 6. Error path: unknown tool must fail with a nonzero exit code. - if inspect --method tools/call --tool-name definitely_not_a_tool >/dev/null 2>&1; then - fail "tools/call unknown tool exits nonzero" + # 6. Error path: an unknown tool is a typed refusal, which MCP delivers as + # an isError tool result rather than a JSON-RPC error. + unknown_out="$work_dir/unknown.json" + if inspect --method tools/call --tool-name definitely_not_a_tool > "$unknown_out" 2>/dev/null && + json_assert "$unknown_out" 'j.isError === true && j.structuredContent?.problem?.code === "unknown_tool"'; then + ok "tools/call unknown tool is a typed refusal" else - ok "tools/call unknown tool exits nonzero" + fail "tools/call unknown tool is a typed refusal" fi if ((failures > 0)); then